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/KeyguardAbsKeyInputViewController.java2
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java35
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java9
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java56
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java9
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java58
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/Dependency.java35
-rw-r--r--packages/SystemUI/src/com/android/systemui/Prefs.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/ScreenDecorations.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java81
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java142
-rw-r--r--packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java94
-rw-r--r--packages/SystemUI/src/com/android/systemui/assist/PhoneStateMonitor.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java159
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java359
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java77
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleEntry.java98
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java512
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java101
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleLogger.java82
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleLoggerImpl.java103
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java225
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleTaskView.java181
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java73
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/Bubbles.java133
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/DismissView.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/ManageEducationView.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/MultiWindowTaskListener.java177
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/ObjectWrapper.java46
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/RelativeTouchListener.kt (renamed from packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/StackEducationView.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/TaskView.java308
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/storage/BubblePersistentRepository.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleVolatileRepository.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeLog.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/dagger/DozeScope.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/emergency/EmergencyGesture.java40
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/Lifecycle.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/ToastLog.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaBrowserFactory.java49
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaData.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaScrollView.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java77
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserFactory.java48
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java96
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java100
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonDrawable.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java169
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java83
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/Pip.java170
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java448
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java553
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/PipSnapAlgorithm.java204
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/PipSurfaceTransactionHelper.java144
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java1091
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/PipUiEventLogger.java116
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipAccessibilityInteractionConnection.java269
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipAppOpsListener.java97
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipController.java490
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipMediaController.java276
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java397
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuIconsAlgorithm.java86
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuView.java495
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java665
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java528
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchGesture.java42
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java1115
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java389
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipUpdateThread.java60
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipUtils.java60
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/tv/PipControlButtonView.java207
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/tv/PipController.java762
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java61
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsViewController.java263
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java197
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java279
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/tv/dagger/TvPipComponent.java61
-rw-r--r--packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt42
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java42
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFragment.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSHost.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanel.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java316
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java380
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java56
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java121
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/Recents.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsImplementation.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/ScreenInternalAudioRecorder.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java47
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java57
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/MediaTransferManager.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java318
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandRegistry.kt204
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationSectionHeadersModule.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java82
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java39
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java13
-rwxr-xr-xpackages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java40
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java126
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplies.java223
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java38
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartRepliesAndActionsInflater.kt464
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java316
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java67
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/SmartRepliesInflationModule.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/tv/TvNotificationPanel.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioRecordingDisclosureBar.java221
-rw-r--r--packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java139
-rw-r--r--packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java83
-rw-r--r--packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt59
-rw-r--r--packages/SystemUI/src/com/android/systemui/toast/ToastUI.java74
-rw-r--r--packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/tuner/TunerService.java45
-rw-r--r--packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java28
-rw-r--r--packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java161
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/UserCreator.java82
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/UserModule.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/DismissCircleView.java62
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt350
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/animation/FloatProperties.kt141
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt1071
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt473
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt699
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorImpl.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java106
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java63
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java149
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java76
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java95
242 files changed, 6726 insertions, 15639 deletions
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
index a65accf1ff15..4ffd22c73116 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
@@ -107,7 +107,7 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey
if (shouldLockout(deadline)) {
handleAttemptLockout(deadline);
} else {
- mView.resetState();
+ resetState();
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 5ad8cad8195a..272954df6dd6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -104,6 +104,8 @@ public class KeyguardClockSwitch extends RelativeLayout {
private boolean mSupportsDarkText;
private int[] mColorPalette;
+ private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
+
public KeyguardClockSwitch(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -128,6 +130,35 @@ public class KeyguardClockSwitch extends RelativeLayout {
return mClockPlugin != null;
}
+ /**
+ * Update lock screen mode for testing different layouts
+ */
+ public void updateLockScreenMode(int mode) {
+ mLockScreenMode = mode;
+ RelativeLayout.LayoutParams statusAreaLP = (RelativeLayout.LayoutParams)
+ mKeyguardStatusArea.getLayoutParams();
+ RelativeLayout.LayoutParams clockLP = (RelativeLayout.LayoutParams)
+ mSmallClockFrame.getLayoutParams();
+
+ if (mode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) {
+ statusAreaLP.removeRule(RelativeLayout.BELOW);
+ statusAreaLP.addRule(RelativeLayout.LEFT_OF, R.id.clock_view);
+ statusAreaLP.addRule(RelativeLayout.ALIGN_PARENT_START);
+
+ clockLP.addRule(RelativeLayout.ALIGN_PARENT_END);
+ clockLP.width = ViewGroup.LayoutParams.WRAP_CONTENT;
+ } else {
+ statusAreaLP.removeRule(RelativeLayout.LEFT_OF);
+ statusAreaLP.removeRule(RelativeLayout.ALIGN_PARENT_START);
+ statusAreaLP.addRule(RelativeLayout.BELOW, R.id.clock_view);
+
+ clockLP.removeRule(RelativeLayout.ALIGN_PARENT_END);
+ clockLP.width = ViewGroup.LayoutParams.MATCH_PARENT;
+ }
+
+ requestLayout();
+ }
+
@Override
protected void onFinishInflate() {
super.onFinishInflate();
@@ -363,6 +394,10 @@ public class KeyguardClockSwitch extends RelativeLayout {
* these cases.
*/
void setKeyguardShowingHeader(boolean hasHeader) {
+ if (mLockScreenMode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL) {
+ hasHeader = false;
+ }
+
if (mShowingHeader == hasHeader) {
return;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
index 9ffa658da0e8..351369c51364 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
@@ -163,16 +163,18 @@ public class KeyguardHostViewController extends ViewController<KeyguardHostView>
@Inject
public KeyguardHostViewController(KeyguardHostView view,
KeyguardUpdateMonitor keyguardUpdateMonitor,
- KeyguardSecurityContainerController keyguardSecurityContainerController,
AudioManager audioManager,
TelephonyManager telephonyManager,
- ViewMediatorCallback viewMediatorCallback) {
+ ViewMediatorCallback viewMediatorCallback,
+ KeyguardSecurityContainerController.Factory
+ keyguardSecurityContainerControllerFactory) {
super(view);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
- mKeyguardSecurityContainerController = keyguardSecurityContainerController;
mAudioManager = audioManager;
mTelephonyManager = telephonyManager;
mViewMediatorCallback = viewMediatorCallback;
+ mKeyguardSecurityContainerController = keyguardSecurityContainerControllerFactory.create(
+ mSecurityCallback);
}
/** Initialize the Controller. */
@@ -188,7 +190,6 @@ public class KeyguardHostViewController extends ViewController<KeyguardHostView>
mViewMediatorCallback.setNeedsInput(mKeyguardSecurityContainerController.needsInput());
mKeyguardUpdateMonitor.registerCallback(mUpdateCallback);
mView.setOnKeyListener(mOnKeyListener);
- mKeyguardSecurityContainerController.setSecurityCallback(mSecurityCallback);
mKeyguardSecurityContainerController.showPrimarySecurityScreen(false);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 64676e55b038..1c23605a8516 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -68,8 +68,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
private final UiEventLogger mUiEventLogger;
private final KeyguardStateController mKeyguardStateController;
private final KeyguardSecurityViewFlipperController mSecurityViewFlipperController;
+ private final SecurityCallback mSecurityCallback;
- private SecurityCallback mSecurityCallback;
private SecurityMode mCurrentSecurityMode = SecurityMode.Invalid;
private KeyguardSecurityCallback mKeyguardSecurityCallback = new KeyguardSecurityCallback() {
@@ -145,8 +145,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
}
};
- @Inject
- KeyguardSecurityContainerController(KeyguardSecurityContainer view,
+ private KeyguardSecurityContainerController(KeyguardSecurityContainer view,
AdminSecondaryLockScreenController.Factory adminSecondaryLockScreenControllerFactory,
LockPatternUtils lockPatternUtils,
KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -154,6 +153,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
MetricsLogger metricsLogger,
UiEventLogger uiEventLogger,
KeyguardStateController keyguardStateController,
+ SecurityCallback securityCallback,
KeyguardSecurityViewFlipperController securityViewFlipperController) {
super(view);
mLockPatternUtils = lockPatternUtils;
@@ -162,6 +162,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
mMetricsLogger = metricsLogger;
mUiEventLogger = uiEventLogger;
mKeyguardStateController = keyguardStateController;
+ mSecurityCallback = securityCallback;
mSecurityViewFlipperController = securityViewFlipperController;
mAdminSecondaryLockScreenController = adminSecondaryLockScreenControllerFactory.create(
mKeyguardSecurityCallback);
@@ -269,10 +270,6 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
}
}
- public void setSecurityCallback(SecurityCallback securityCallback) {
- mSecurityCallback = securityCallback;
- }
-
/**
* Shows the next security screen if there is one.
* @param authenticated true if the user entered the correct authentication
@@ -450,4 +447,49 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
mCurrentSecurityMode = securityMode;
return getCurrentSecurityController();
}
+
+ static class Factory {
+
+ private final KeyguardSecurityContainer mView;
+ private final AdminSecondaryLockScreenController.Factory
+ mAdminSecondaryLockScreenControllerFactory;
+ private final LockPatternUtils mLockPatternUtils;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final KeyguardSecurityModel mKeyguardSecurityModel;
+ private final MetricsLogger mMetricsLogger;
+ private final UiEventLogger mUiEventLogger;
+ private final KeyguardStateController mKeyguardStateController;
+ private final KeyguardSecurityViewFlipperController mSecurityViewFlipperController;
+
+ @Inject
+ Factory(KeyguardSecurityContainer view,
+ AdminSecondaryLockScreenController.Factory
+ adminSecondaryLockScreenControllerFactory,
+ LockPatternUtils lockPatternUtils,
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ KeyguardSecurityModel keyguardSecurityModel,
+ MetricsLogger metricsLogger,
+ UiEventLogger uiEventLogger,
+ KeyguardStateController keyguardStateController,
+ KeyguardSecurityViewFlipperController securityViewFlipperController) {
+ mView = view;
+ mAdminSecondaryLockScreenControllerFactory = adminSecondaryLockScreenControllerFactory;
+ mLockPatternUtils = lockPatternUtils;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mKeyguardSecurityModel = keyguardSecurityModel;
+ mMetricsLogger = metricsLogger;
+ mUiEventLogger = uiEventLogger;
+ mKeyguardStateController = keyguardStateController;
+ mSecurityViewFlipperController = securityViewFlipperController;
+ }
+
+ public KeyguardSecurityContainerController create(
+ SecurityCallback securityCallback) {
+ return new KeyguardSecurityContainerController(mView,
+ mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils,
+ mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger,
+ mKeyguardStateController, securityCallback, mSecurityViewFlipperController);
+ }
+
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index 6e111745627f..9ef2def04ec1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -79,6 +79,11 @@ public class KeyguardStatusView extends GridLayout implements
private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
@Override
+ public void onLockScreenModeChanged(int mode) {
+ updateLockScreenMode(mode);
+ }
+
+ @Override
public void onTimeChanged() {
refreshTime();
}
@@ -255,6 +260,10 @@ public class KeyguardStatusView extends GridLayout implements
mClockView.refresh();
}
+ private void updateLockScreenMode(int mode) {
+ mClockView.updateLockScreenMode(mode);
+ }
+
private void updateTimeZone(TimeZone timeZone) {
mClockView.onTimeZoneChanged(timeZone);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index a8dd03f668e2..bb8a99bb8cd8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -56,7 +56,7 @@ import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricSourceType;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
import android.hardware.face.FaceManager;
-import android.hardware.face.FaceSensorProperties;
+import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
@@ -100,8 +100,8 @@ 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.statusbar.StatusBarStateController;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.util.Assert;
@@ -181,6 +181,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private static final int MSG_USER_STOPPED = 340;
private static final int MSG_USER_REMOVED = 341;
private static final int MSG_KEYGUARD_GOING_AWAY = 342;
+ private static final int MSG_LOCK_SCREEN_MODE = 343;
+
+ public static final int LOCK_SCREEN_MODE_NORMAL = 0;
+ public static final int LOCK_SCREEN_MODE_LAYOUT_1 = 1;
/** Biometric authentication state: Not listening. */
private static final int BIOMETRIC_STATE_STOPPED = 0;
@@ -264,6 +268,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private final ArrayList<WeakReference<KeyguardUpdateMonitorCallback>>
mCallbacks = Lists.newArrayList();
private ContentObserver mDeviceProvisionedObserver;
+ private ContentObserver mLockScreenModeObserver;
private boolean mSwitchingUser;
@@ -287,6 +292,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private boolean mLockIconPressed;
private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
private final Executor mBackgroundExecutor;
+ private int mLockScreenMode;
/**
* Short delay before restarting fingerprint authentication after a successful try. This should
@@ -1337,7 +1343,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private CancellationSignal mFaceCancelSignal;
private FingerprintManager mFpm;
private FaceManager mFaceManager;
- private List<FaceSensorProperties> mFaceSensorProperties;
+ private List<FaceSensorPropertiesInternal> mFaceSensorProperties;
private boolean mFingerprintLockedOut;
private TelephonyManager mTelephonyManager;
@@ -1695,6 +1701,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
case MSG_KEYGUARD_GOING_AWAY:
handleKeyguardGoingAway((boolean) msg.obj);
break;
+ case MSG_LOCK_SCREEN_MODE:
+ handleLockScreenMode();
+ break;
default:
super.handleMessage(msg);
break;
@@ -1779,7 +1788,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE)) {
mFaceManager = (FaceManager) context.getSystemService(Context.FACE_SERVICE);
- mFaceSensorProperties = mFaceManager.getSensorProperties();
+ mFaceSensorProperties = mFaceManager.getSensorPropertiesInternal();
}
if (mFpm != null || mFaceManager != null) {
@@ -1797,7 +1806,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mIsAutomotive = isAutomotive();
- ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
+ TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
mUserManager = context.getSystemService(UserManager.class);
mIsPrimaryUser = mUserManager.isPrimaryUser();
int user = ActivityManager.getCurrentUser();
@@ -1829,6 +1838,23 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
}
}
+
+ updateLockScreenMode();
+ mLockScreenModeObserver = new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ updateLockScreenMode();
+ mHandler.sendEmptyMessage(MSG_LOCK_SCREEN_MODE);
+ }
+ };
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.SHOW_NEW_LOCKSCREEN),
+ false, mLockScreenModeObserver);
+ }
+
+ private void updateLockScreenMode() {
+ mLockScreenMode = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.SHOW_NEW_LOCKSCREEN, 0);
}
private final UserSwitchObserver mUserSwitchObserver = new UserSwitchObserver() {
@@ -2351,6 +2377,20 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
/**
+ * Handle {@link #MSG_LOCK_SCREEN_MODE}
+ */
+ private void handleLockScreenMode() {
+ Assert.isMainThread();
+ if (DEBUG) Log.d(TAG, "handleLockScreenMode(" + mLockScreenMode + ")");
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onLockScreenModeChanged(mLockScreenMode);
+ }
+ }
+ }
+
+ /**
* Handle (@line #MSG_TIMEZONE_UPDATE}
*/
private void handleTimeZoneUpdate(String timeZone) {
@@ -2679,6 +2719,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
callback.onClockVisibilityChanged();
callback.onKeyguardVisibilityChangedRaw(mKeyguardIsVisible);
callback.onTelephonyCapable(mTelephonyCapable);
+ callback.onLockScreenModeChanged(mLockScreenMode);
+
for (Entry<Integer, SimData> data : mSimDatas.entrySet()) {
final SimData state = data.getValue();
callback.onSimStateChanged(state.subId, state.slotId, state.simState);
@@ -3023,13 +3065,17 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mContext.getContentResolver().unregisterContentObserver(mDeviceProvisionedObserver);
}
+ if (mLockScreenModeObserver != null) {
+ mContext.getContentResolver().unregisterContentObserver(mLockScreenModeObserver);
+ }
+
try {
ActivityManager.getService().unregisterUserSwitchObserver(mUserSwitchObserver);
} catch (RemoteException e) {
Log.d(TAG, "RemoteException onDestroy. cannot unregister userSwitchObserver");
}
- ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener);
+ TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
mBroadcastDispatcher.unregisterReceiver(mBroadcastReceiver);
mBroadcastDispatcher.unregisterReceiver(mBroadcastAllReceiver);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 37c66639876e..9c2b14945cc2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -325,4 +325,9 @@ public class KeyguardUpdateMonitorCallback {
*/
public void onSecondaryLockscreenRequirementChanged(int userId) { }
+ /**
+ * Called to switch lock screen layout/clock layouts
+ */
+ public void onLockScreenModeChanged(int mode) { }
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 832edf719dbb..9f28e0936d7f 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -37,7 +37,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.systemui.appops.AppOpsController;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
@@ -47,6 +47,7 @@ import com.android.systemui.dump.DumpManager;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.navigationbar.NavigationModeController;
@@ -126,6 +127,7 @@ import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.SystemWindows;
+import java.util.concurrent.Executor;
import java.util.function.Consumer;
import javax.inject.Inject;
@@ -167,6 +169,15 @@ public class Dependency {
* Generic handler on the main thread.
*/
private static final String MAIN_HANDLER_NAME = "main_handler";
+ /**
+ * Generic executor on the main thread.
+ */
+ private static final String MAIN_EXECUTOR_NAME = "main_executor";
+
+ /**
+ * Generic executor on a background thread.
+ */
+ private static final String BACKGROUND_EXECUTOR_NAME = "background_executor";
/**
* An email address to send memory leak reports to by default.
@@ -199,6 +210,17 @@ public class Dependency {
new DependencyKey<>(MAIN_HANDLER_NAME);
/**
+ * Generic executor on the main thread.
+ */
+ public static final DependencyKey<Executor> MAIN_EXECUTOR =
+ new DependencyKey<>(MAIN_EXECUTOR_NAME);
+ /**
+ * Generic executor on a background thread.
+ */
+ public static final DependencyKey<Executor> BACKGROUND_EXECUTOR =
+ new DependencyKey<>(BACKGROUND_EXECUTOR_NAME);
+
+ /**
* An email address to send memory leak reports to by default.
*/
public static final DependencyKey<String> LEAK_REPORT_EMAIL =
@@ -288,7 +310,7 @@ public class Dependency {
@Inject Lazy<KeyguardDismissUtil> mKeyguardDismissUtil;
@Inject Lazy<SmartReplyController> mSmartReplyController;
@Inject Lazy<RemoteInputQuickSettingsDisabler> mRemoteInputQuickSettingsDisabler;
- @Inject Lazy<BubbleController> mBubbleController;
+ @Inject Lazy<Bubbles> mBubbles;
@Inject Lazy<NotificationEntryManager> mNotificationEntryManager;
@Inject Lazy<SensorPrivacyManager> mSensorPrivacyManager;
@Inject Lazy<AutoHideController> mAutoHideController;
@@ -301,6 +323,8 @@ public class Dependency {
@Inject @Named(TIME_TICK_HANDLER_NAME) Lazy<Handler> mTimeTickHandler;
@Nullable
@Inject @Named(LEAK_REPORT_EMAIL_NAME) Lazy<String> mLeakReportEmail;
+ @Inject @Main Lazy<Executor> mMainExecutor;
+ @Inject @Background Lazy<Executor> mBackgroundExecutor;
@Inject Lazy<ClockManager> mClockManager;
@Inject Lazy<ActivityManagerWrapper> mActivityManagerWrapper;
@Inject Lazy<DevicePolicyManagerWrapper> mDevicePolicyManagerWrapper;
@@ -321,6 +345,7 @@ public class Dependency {
@Inject Lazy<DisplayImeController> mDisplayImeController;
@Inject Lazy<RecordingController> mRecordingController;
@Inject Lazy<ProtoTracer> mProtoTracer;
+ @Inject Lazy<MediaOutputDialogFactory> mMediaOutputDialogFactory;
@Inject
public Dependency() {
@@ -336,6 +361,8 @@ public class Dependency {
mProviders.put(BG_LOOPER, mBgLooper::get);
mProviders.put(MAIN_LOOPER, mMainLooper::get);
mProviders.put(MAIN_HANDLER, mMainHandler::get);
+ mProviders.put(MAIN_EXECUTOR, mMainExecutor::get);
+ mProviders.put(BACKGROUND_EXECUTOR, mBackgroundExecutor::get);
mProviders.put(ActivityStarter.class, mActivityStarter::get);
mProviders.put(BroadcastDispatcher.class, mBroadcastDispatcher::get);
@@ -483,7 +510,7 @@ public class Dependency {
mProviders.put(SmartReplyController.class, mSmartReplyController::get);
mProviders.put(RemoteInputQuickSettingsDisabler.class,
mRemoteInputQuickSettingsDisabler::get);
- mProviders.put(BubbleController.class, mBubbleController::get);
+ mProviders.put(Bubbles.class, mBubbles::get);
mProviders.put(NotificationEntryManager.class, mNotificationEntryManager::get);
mProviders.put(ForegroundServiceNotificationListener.class,
mForegroundServiceNotificationListener::get);
@@ -516,6 +543,8 @@ public class Dependency {
mProviders.put(RecordingController.class, mRecordingController::get);
+ mProviders.put(MediaOutputDialogFactory.class, mMediaOutputDialogFactory::get);
+
Dependency.setInstance(this);
}
diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java
index f4c865e1d131..f210d508907c 100644
--- a/packages/SystemUI/src/com/android/systemui/Prefs.java
+++ b/packages/SystemUI/src/com/android/systemui/Prefs.java
@@ -72,11 +72,11 @@ public final class Prefs {
Key.QS_HAS_TURNED_OFF_MOBILE_DATA,
Key.TOUCHED_RINGER_TOGGLE,
Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP,
- Key.HAS_SEEN_BUBBLES_EDUCATION,
- Key.HAS_SEEN_BUBBLES_MANAGE_EDUCATION,
Key.HAS_SEEN_REVERSE_BOTTOM_SHEET,
- Key.CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT
+ Key.CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT,
+ Key.HAS_SEEN_PRIORITY_ONBOARDING
})
+ // TODO: annotate these with their types so {@link PrefsCommandLine} can know how to set them
public @interface Key {
@Deprecated
String OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME = "OverviewLastStackTaskActiveTime";
@@ -121,8 +121,6 @@ public final class Prefs {
String QS_HAS_TURNED_OFF_MOBILE_DATA = "QsHasTurnedOffMobileData";
String TOUCHED_RINGER_TOGGLE = "TouchedRingerToggle";
String HAS_SEEN_ODI_CAPTIONS_TOOLTIP = "HasSeenODICaptionsTooltip";
- String HAS_SEEN_BUBBLES_EDUCATION = "HasSeenBubblesOnboarding";
- String HAS_SEEN_BUBBLES_MANAGE_EDUCATION = "HasSeenBubblesManageOnboarding";
String HAS_SEEN_REVERSE_BOTTOM_SHEET = "HasSeenReverseBottomSheet";
String CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT = "ControlsStructureSwipeTooltipCount";
/** Tracks whether the user has seen the onboarding screen for priority conversations */
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index fbfabd1e4ae7..a4648ee75485 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -86,6 +86,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.qs.SecureSetting;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
@@ -125,6 +126,7 @@ public class ScreenDecorations extends SystemUI implements Tunable {
private final TunerService mTunerService;
private DisplayManager.DisplayListener mDisplayListener;
private CameraAvailabilityListener mCameraListener;
+ private final UserTracker mUserTracker;
//TODO: These are piecemeal being updated to Points for now to support non-square rounded
// corners. for now it is only supposed when reading the intrinsic size from the drawables with
@@ -202,11 +204,13 @@ public class ScreenDecorations extends SystemUI implements Tunable {
public ScreenDecorations(Context context,
@Main Handler handler,
BroadcastDispatcher broadcastDispatcher,
- TunerService tunerService) {
+ TunerService tunerService,
+ UserTracker userTracker) {
super(context);
mMainHandler = handler;
mBroadcastDispatcher = broadcastDispatcher;
mTunerService = tunerService;
+ mUserTracker = userTracker;
}
@Override
@@ -310,7 +314,8 @@ public class ScreenDecorations extends SystemUI implements Tunable {
// Watch color inversion and invert the overlay as needed.
if (mColorInversionSetting == null) {
mColorInversionSetting = new SecureSetting(mContext, mHandler,
- Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED) {
+ Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED,
+ mUserTracker.getUserId()) {
@Override
protected void handleValueChanged(int value, boolean observedChange) {
updateColorInversion(value);
diff --git a/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java b/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java
index 34efa35c37c5..02f34ac3dec0 100644
--- a/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java
@@ -42,8 +42,8 @@ import android.widget.PopupWindow;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.statusbar.CommandQueue;
import java.lang.ref.WeakReference;
@@ -66,11 +66,11 @@ public class SizeCompatModeActivityController extends SystemUI implements Comman
@VisibleForTesting
@Inject
- SizeCompatModeActivityController(Context context, ActivityManagerWrapper am,
+ SizeCompatModeActivityController(Context context, TaskStackChangeListeners listeners,
CommandQueue commandQueue) {
super(context);
mCommandQueue = commandQueue;
- am.registerTaskStackListener(new TaskStackChangeListener() {
+ listeners.registerTaskStackListener(new TaskStackChangeListener() {
@Override
public void onSizeCompatModeActivityChanged(int displayId, IBinder activityToken) {
// Note the callback already runs on main thread.
diff --git a/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java b/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java
index 2365f128532e..47adffc216a5 100644
--- a/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java
@@ -111,9 +111,7 @@ public class SlicePermissionActivity extends Activity implements OnClickListener
final String providerPkg = getIntent().getStringExtra("provider_pkg");
if (providerPkg == null || mProviderPkg.equals(providerPkg)) return;
final String callingPkg = getCallingPkg();
- EventLog.writeEvent(0x534e4554, "159145361", getUid(callingPkg), String.format(
- "pkg %s (disguised as %s) attempted to request permission to show %s slices in %s",
- callingPkg, providerPkg, mProviderPkg, mCallingPkg));
+ EventLog.writeEvent(0x534e4554, "159145361", getUid(callingPkg));
}
@Nullable
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
index 68e404e36bba..69a0d65a6963 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
@@ -16,11 +16,14 @@
package com.android.systemui.accessibility;
+import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
+
import android.annotation.NonNull;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.graphics.PixelFormat;
import android.graphics.PointF;
+import android.os.Bundle;
import android.provider.Settings;
import android.util.MathUtils;
import android.view.Gravity;
@@ -28,6 +31,8 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.widget.ImageView;
import com.android.internal.annotations.VisibleForTesting;
@@ -41,9 +46,15 @@ import com.android.systemui.R;
*/
class MagnificationModeSwitch {
- private static final int DURATION_MS = 5000;
- private static final int START_DELAY_MS = 3000;
- private final Runnable mAnimationTask;
+ @VisibleForTesting
+ static final long FADING_ANIMATION_DURATION_MS = 300;
+ private static final int DEFAULT_FADE_OUT_ANIMATION_DELAY_MS = 3000;
+ // The button visible duration starting from the last showButton() called.
+ private int mVisibleDuration = DEFAULT_FADE_OUT_ANIMATION_DELAY_MS;
+ private final Runnable mFadeInAnimationTask;
+ private final Runnable mFadeOutAnimationTask;
+ @VisibleForTesting
+ boolean mIsFadeOutAnimating = false;
private final Context mContext;
private final WindowManager mWindowManager;
@@ -71,16 +82,53 @@ class MagnificationModeSwitch {
applyResourcesValues();
mImageView.setImageResource(getIconResId(mMagnificationMode));
mImageView.setOnTouchListener(this::onTouch);
+ mImageView.setAccessibilityDelegate(new View.AccessibilityDelegate() {
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+ info.setStateDescription(formatStateDescription());
+ info.setContentDescription(mContext.getResources().getString(
+ R.string.magnification_mode_switch_description));
+ final AccessibilityAction clickAction = new AccessibilityAction(
+ AccessibilityAction.ACTION_CLICK.getId(), mContext.getResources().getString(
+ R.string.magnification_mode_switch_click_label));
+ info.addAction(clickAction);
+ info.setClickable(true);
+ }
+
+ @Override
+ public boolean performAccessibilityAction(View host, int action, Bundle args) {
+ if (action == AccessibilityAction.ACTION_CLICK.getId()) {
+ handleSingleTap();
+ return true;
+ }
+ return super.performAccessibilityAction(host, action, args);
+ }
+ });
- mAnimationTask = () -> {
+ mFadeInAnimationTask = () -> {
+ mImageView.animate()
+ .alpha(1f)
+ .setDuration(FADING_ANIMATION_DURATION_MS)
+ .start();
+ };
+ mFadeOutAnimationTask = () -> {
mImageView.animate()
.alpha(0f)
- .setDuration(DURATION_MS)
+ .setDuration(FADING_ANIMATION_DURATION_MS)
.withEndAction(() -> removeButton())
.start();
+ mIsFadeOutAnimating = true;
};
}
+ private CharSequence formatStateDescription() {
+ final int stringId = mMagnificationMode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW
+ ? R.string.magnification_mode_switch_state_window
+ : R.string.magnification_mode_switch_state_full_screen;
+ return mContext.getResources().getString(stringId);
+ }
+
private void applyResourcesValues() {
final int padding = mContext.getResources().getDimensionPixelSize(
R.dimen.magnification_switch_button_padding);
@@ -93,7 +141,6 @@ class MagnificationModeSwitch {
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
- mImageView.setAlpha(1.0f);
mImageView.animate().cancel();
mLastDown.set(event.getRawX(), event.getRawY());
mLastDrag.set(event.getRawX(), event.getRawY());
@@ -134,9 +181,13 @@ class MagnificationModeSwitch {
if (!mIsVisible) {
return;
}
+ // Reset button status.
+ mImageView.removeCallbacks(mFadeInAnimationTask);
+ mImageView.removeCallbacks(mFadeOutAnimationTask);
mImageView.animate().cancel();
+ mIsFadeOutAnimating = false;
+ mImageView.setAlpha(0f);
mWindowManager.removeView(mImageView);
- // Reset button status.
mIsVisible = false;
mParams.x = 0;
mParams.y = 0;
@@ -150,14 +201,15 @@ class MagnificationModeSwitch {
if (!mIsVisible) {
mWindowManager.addView(mImageView, mParams);
mIsVisible = true;
+ mImageView.postOnAnimation(mFadeInAnimationTask);
}
- mImageView.setAlpha(1.0f);
- // TODO(b/143852371): use accessibility timeout as a delay.
- // Dismiss the magnification switch button after the button is displayed for a period of
- // time.
- mImageView.animate().cancel();
- mImageView.removeCallbacks(mAnimationTask);
- mImageView.postDelayed(mAnimationTask, START_DELAY_MS);
+ if (mIsFadeOutAnimating) {
+ mImageView.animate().cancel();
+ mImageView.setAlpha(1f);
+ }
+ // Refresh the time slot of the fade-out task whenever this method is called.
+ mImageView.removeCallbacks(mFadeOutAnimationTask);
+ mImageView.postOnAnimationDelayed(mFadeOutAnimationTask, mVisibleDuration);
}
void onConfigurationChanged(int configDiff) {
@@ -187,6 +239,7 @@ class MagnificationModeSwitch {
imageView.setClickable(true);
imageView.setFocusable(true);
imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
+ imageView.setAlpha(0f);
return imageView;
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
index 911bf9ef757b..a705ec784c9a 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
@@ -37,6 +37,7 @@ import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.systemui.SystemUI;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.statusbar.CommandQueue;
import javax.inject.Inject;
@@ -66,7 +67,8 @@ public class WindowMagnification extends SystemUI implements WindowMagnifierCall
@Inject
public WindowMagnification(Context context, @Main Handler mainHandler,
- CommandQueue commandQueue, ModeSwitchesController modeSwitchesController) {
+ CommandQueue commandQueue, ModeSwitchesController modeSwitchesController,
+ NavigationModeController navigationModeController) {
super(context);
mHandler = mainHandler;
mLastConfiguration = new Configuration(context.getResources().getConfiguration());
@@ -77,6 +79,9 @@ public class WindowMagnification extends SystemUI implements WindowMagnifierCall
final WindowMagnificationController controller = new WindowMagnificationController(mContext,
mHandler, new SfVsyncFrameCallbackProvider(), null,
new SurfaceControl.Transaction(), this);
+ final int navBarMode = navigationModeController.addListener(
+ controller::onNavigationModeChanged);
+ controller.onNavigationModeChanged(navBarMode);
mWindowMagnificationAnimationController = new WindowMagnificationAnimationController(
mContext, controller);
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 714095631fdb..c3474bb7ca57 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -17,6 +17,8 @@
package com.android.systemui.accessibility;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -30,9 +32,11 @@ import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
+import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
import android.util.Log;
+import android.util.Range;
import android.view.Choreographer;
import android.view.Display;
import android.view.Gravity;
@@ -47,12 +51,17 @@ import android.view.SurfaceView;
import android.view.View;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.systemui.R;
import com.android.systemui.shared.system.WindowManagerWrapper;
+import java.text.NumberFormat;
+import java.util.Locale;
+
/**
* Class to handle adding and removing a window magnification.
*/
@@ -60,6 +69,11 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
MirrorWindowControl.MirrorWindowDelegate {
private static final String TAG = "WindowMagnificationController";
+ // Delay to avoid updating state description too frequently.
+ private static final int UPDATE_STATE_DESCRIPTION_DELAY_MS = 100;
+ // It should be consistent with the value defined in WindowMagnificationGestureHandler.
+ private static final Range<Float> A11Y_ACTION_SCALE_RANGE = new Range<>(2.0f, 8.0f);
+ private static final float A11Y_CHANGE_SCALE_DIFFERENCE = 1.0f;
private final Context mContext;
private final Resources mResources;
private final Handler mHandler;
@@ -95,6 +109,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
private final View.OnLayoutChangeListener mMirrorViewLayoutChangeListener;
private final View.OnLayoutChangeListener mMirrorSurfaceViewLayoutChangeListener;
private final Runnable mMirrorViewRunnable;
+ private final Runnable mUpdateStateDescriptionRunnable;
private View mMirrorView;
private SurfaceView mMirrorSurfaceView;
private int mMirrorSurfaceMargin;
@@ -104,8 +119,13 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
// The boundary of magnification frame.
private final Rect mMagnificationFrameBoundary = new Rect();
+ private int mNavBarMode;
+ private int mNavGestureHeight;
+
private final SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
private Choreographer.FrameCallback mMirrorViewGeometryVsyncCallback;
+ private Locale mLocale;
+ private NumberFormat mPercentFormat;
@Nullable
private MirrorWindowControl mMirrorWindowControl;
@@ -164,6 +184,11 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
mWindowMagnifierCallback.onSourceBoundsChanged(mDisplayId, mSourceBounds);
}
};
+ mUpdateStateDescriptionRunnable = () -> {
+ if (isWindowVisible()) {
+ mMirrorView.setStateDescription(formatStateDescription(mScale));
+ }
+ };
}
private void updateDimensions() {
@@ -175,6 +200,19 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
R.dimen.magnification_drag_view_size);
mOuterBorderSize = mResources.getDimensionPixelSize(
R.dimen.magnification_outer_border_margin);
+ updateNavigationBarDimensions();
+ }
+
+ private void updateNavigationBarDimensions() {
+ if (!supportsSwipeUpGesture()) {
+ mNavGestureHeight = 0;
+ return;
+ }
+ mNavGestureHeight = (mDisplaySize.x > mDisplaySize.y)
+ ? mResources.getDimensionPixelSize(
+ com.android.internal.R.dimen.navigation_bar_height_landscape)
+ : mResources.getDimensionPixelSize(
+ com.android.internal.R.dimen.navigation_bar_gesture_height);
}
/**
@@ -219,6 +257,13 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
}
}
+ /** Handles MirrorWindow position when the navigation bar mode changed. */
+ public void onNavigationModeChanged(int mode) {
+ mNavBarMode = mode;
+ updateNavigationBarDimensions();
+ updateMirrorViewLayout();
+ }
+
/** Handles MirrorWindow position when the device rotation changed. */
private void onRotate() {
final Display display = mContext.getDisplay();
@@ -226,6 +271,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
display.getRealSize(mDisplaySize);
setMagnificationFrameBoundary();
mRotation = display.getRotation();
+ updateNavigationBarDimensions();
if (!isWindowVisible()) {
return;
@@ -292,12 +338,13 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
mMirrorView.addOnLayoutChangeListener(mMirrorViewLayoutChangeListener);
+ mMirrorView.setAccessibilityDelegate(new MirrorWindowA11yDelegate());
+
mWm.addView(mMirrorView, params);
SurfaceHolder holder = mMirrorSurfaceView.getHolder();
holder.addCallback(this);
holder.setFormat(PixelFormat.RGBA_8888);
-
addDragTouchListeners();
}
@@ -380,15 +427,23 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
* moved close to the screen edges.
*/
private void updateMirrorViewLayout() {
+ if (!isWindowVisible()) {
+ return;
+ }
+ final int maxMirrorViewX = mDisplaySize.x - mMirrorView.getWidth();
+ final int maxMirrorViewY = mDisplaySize.y - mMirrorView.getHeight() - mNavGestureHeight;
WindowManager.LayoutParams params =
(WindowManager.LayoutParams) mMirrorView.getLayoutParams();
params.x = mMagnificationFrame.left - mMirrorSurfaceMargin;
params.y = mMagnificationFrame.top - mMirrorSurfaceMargin;
+ // If nav bar mode supports swipe-up gesture, the Y position of mirror view should not
+ // overlap nav bar window to prevent window-dragging obscured.
+ if (supportsSwipeUpGesture()) {
+ params.y = Math.min(params.y, maxMirrorViewY);
+ }
// Translates MirrorView position to make MirrorSurfaceView that is inside MirrorView
// able to move close to the screen edges.
- final int maxMirrorViewX = mDisplaySize.x - mMirrorView.getWidth();
- final int maxMirrorViewY = mDisplaySize.y - mMirrorView.getHeight();
final float translationX;
final float translationY;
if (params.x < 0) {
@@ -526,6 +581,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
final float offsetY = Float.isNaN(centerY) ? 0
: centerY - mMagnificationFrame.exactCenterY();
mScale = Float.isNaN(scale) ? mScale : scale;
+
setMagnificationFrameBoundary();
updateMagnificationFramePosition((int) offsetX, (int) offsetY);
if (!isWindowVisible()) {
@@ -546,6 +602,8 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
return;
}
enableWindowMagnification(scale, Float.NaN, Float.NaN);
+ mHandler.removeCallbacks(mUpdateStateDescriptionRunnable);
+ mHandler.postDelayed(mUpdateStateDescriptionRunnable, UPDATE_STATE_DESCRIPTION_DELAY_MS);
}
/**
@@ -596,4 +654,82 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
private boolean isWindowVisible() {
return mMirrorView != null;
}
+
+ private boolean supportsSwipeUpGesture() {
+ return mNavBarMode == NAV_BAR_MODE_2BUTTON || mNavBarMode == NAV_BAR_MODE_GESTURAL;
+ }
+
+ private CharSequence formatStateDescription(float scale) {
+ // Cache the locale-appropriate NumberFormat. Configuration locale is guaranteed
+ // non-null, so the first time this is called we will always get the appropriate
+ // NumberFormat, then never regenerate it unless the locale changes on the fly.
+ final Locale curLocale = mContext.getResources().getConfiguration().getLocales().get(0);
+ if (!curLocale.equals(mLocale)) {
+ mLocale = curLocale;
+ mPercentFormat = NumberFormat.getPercentInstance(curLocale);
+ }
+ return mPercentFormat.format(scale);
+ }
+
+ private class MirrorWindowA11yDelegate extends View.AccessibilityDelegate {
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+ info.addAction(
+ new AccessibilityAction(R.id.accessibility_action_zoom_in,
+ mContext.getString(R.string.accessibility_control_zoom_in)));
+ info.addAction(new AccessibilityAction(R.id.accessibility_action_zoom_out,
+ mContext.getString(R.string.accessibility_control_zoom_out)));
+ info.addAction(new AccessibilityAction(R.id.accessibility_action_move_up,
+ mContext.getString(R.string.accessibility_control_move_up)));
+ info.addAction(new AccessibilityAction(R.id.accessibility_action_move_down,
+ mContext.getString(R.string.accessibility_control_move_down)));
+ info.addAction(new AccessibilityAction(R.id.accessibility_action_move_left,
+ mContext.getString(R.string.accessibility_control_move_left)));
+ info.addAction(new AccessibilityAction(R.id.accessibility_action_move_right,
+ mContext.getString(R.string.accessibility_control_move_right)));
+
+ info.setContentDescription(mContext.getString(R.string.magnification_window_title));
+ info.setStateDescription(formatStateDescription(getScale()));
+ }
+
+ @Override
+ public boolean performAccessibilityAction(View host, int action, Bundle args) {
+ if (performA11yAction(action)) {
+ return true;
+ }
+ return super.performAccessibilityAction(host, action, args);
+ }
+
+ private boolean performA11yAction(int action) {
+ if (action == R.id.accessibility_action_zoom_in) {
+ final float scale = mScale + A11Y_CHANGE_SCALE_DIFFERENCE;
+ setScale(A11Y_ACTION_SCALE_RANGE.clamp(scale));
+ return true;
+ }
+ if (action == R.id.accessibility_action_zoom_out) {
+ final float scale = mScale - A11Y_CHANGE_SCALE_DIFFERENCE;
+ setScale(A11Y_ACTION_SCALE_RANGE.clamp(scale));
+ return true;
+ }
+ if (action == R.id.accessibility_action_move_up) {
+ move(0, -mSourceBounds.height());
+ return true;
+ }
+ if (action == R.id.accessibility_action_move_down) {
+ move(0, mSourceBounds.height());
+ return true;
+ }
+ if (action == R.id.accessibility_action_move_left) {
+ move(-mSourceBounds.width(), 0);
+ return true;
+ }
+ if (action == R.id.accessibility_action_move_right) {
+ move(mSourceBounds.width(), 0);
+ return true;
+ }
+ return false;
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java
index 1b2e4c6a595e..c1c2de166627 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java
@@ -26,6 +26,8 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ResolveInfo;
+import android.database.ContentObserver;
+import android.net.Uri;
import android.os.Handler;
import android.provider.Settings;
@@ -45,6 +47,7 @@ import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.PackageManagerWrapper;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.statusbar.StatusBarState;
import java.io.PrintWriter;
@@ -66,8 +69,10 @@ import dagger.Lazy;
@SysUISingleton
final class AssistHandleReminderExpBehavior implements BehaviorController {
- private static final String LEARNING_TIME_ELAPSED_KEY = "reminder_exp_learning_time_elapsed";
- private static final String LEARNING_EVENT_COUNT_KEY = "reminder_exp_learning_event_count";
+ private static final Uri LEARNING_TIME_ELAPSED_URI =
+ Settings.Secure.getUriFor(Settings.Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS);
+ private static final Uri LEARNING_EVENT_COUNT_URI =
+ Settings.Secure.getUriFor(Settings.Secure.ASSIST_HANDLES_LEARNING_EVENT_COUNT);
private static final String LEARNED_HINT_LAST_SHOWN_KEY =
"reminder_exp_learned_hint_last_shown";
private static final long DEFAULT_LEARNING_TIME_MS = TimeUnit.DAYS.toMillis(10);
@@ -167,6 +172,7 @@ final class AssistHandleReminderExpBehavior implements BehaviorController {
private final DeviceConfigHelper mDeviceConfigHelper;
private final Lazy<StatusBarStateController> mStatusBarStateController;
private final Lazy<ActivityManagerWrapper> mActivityManagerWrapper;
+ private final Lazy<TaskStackChangeListeners> mTaskStackChangeListeners;
private final Lazy<OverviewProxyService> mOverviewProxyService;
private final Lazy<SysUiState> mSysUiFlagContainer;
private final Lazy<WakefulnessLifecycle> mWakefulnessLifecycle;
@@ -181,6 +187,7 @@ final class AssistHandleReminderExpBehavior implements BehaviorController {
private boolean mIsNavBarHidden;
private boolean mIsLauncherShowing;
private int mConsecutiveTaskSwitches;
+ @Nullable private ContentObserver mSettingObserver;
/** Whether user has learned the gesture. */
private boolean mIsLearned;
@@ -202,6 +209,7 @@ final class AssistHandleReminderExpBehavior implements BehaviorController {
DeviceConfigHelper deviceConfigHelper,
Lazy<StatusBarStateController> statusBarStateController,
Lazy<ActivityManagerWrapper> activityManagerWrapper,
+ Lazy<TaskStackChangeListeners> taskStackChangeListeners,
Lazy<OverviewProxyService> overviewProxyService,
Lazy<SysUiState> sysUiFlagContainer,
Lazy<WakefulnessLifecycle> wakefulnessLifecycle,
@@ -213,6 +221,7 @@ final class AssistHandleReminderExpBehavior implements BehaviorController {
mDeviceConfigHelper = deviceConfigHelper;
mStatusBarStateController = statusBarStateController;
mActivityManagerWrapper = activityManagerWrapper;
+ mTaskStackChangeListeners = taskStackChangeListeners;
mOverviewProxyService = overviewProxyService;
mSysUiFlagContainer = sysUiFlagContainer;
mWakefulnessLifecycle = wakefulnessLifecycle;
@@ -240,7 +249,7 @@ final class AssistHandleReminderExpBehavior implements BehaviorController {
ActivityManager.RunningTaskInfo runningTaskInfo =
mActivityManagerWrapper.get().getRunningTask();
mRunningTaskId = runningTaskInfo == null ? 0 : runningTaskInfo.taskId;
- mActivityManagerWrapper.get().registerTaskStackListener(mTaskStackChangeListener);
+ mTaskStackChangeListeners.get().registerTaskStackListener(mTaskStackChangeListener);
mOverviewProxyService.get().addCallback(mOverviewProxyListener);
mSysUiFlagContainer.get().addCallback(mSysUiStateCallback);
mIsAwake = mWakefulnessLifecycle.get().getWakefulness()
@@ -248,9 +257,22 @@ final class AssistHandleReminderExpBehavior implements BehaviorController {
mWakefulnessLifecycle.get().addObserver(mWakefulnessLifecycleObserver);
mLearningTimeElapsed = Settings.Secure.getLong(
- context.getContentResolver(), LEARNING_TIME_ELAPSED_KEY, /* default = */ 0);
+ context.getContentResolver(),
+ Settings.Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS,
+ /* default = */ 0);
mLearningCount = Settings.Secure.getInt(
- context.getContentResolver(), LEARNING_EVENT_COUNT_KEY, /* default = */ 0);
+ context.getContentResolver(),
+ Settings.Secure.ASSIST_HANDLES_LEARNING_EVENT_COUNT,
+ /* default = */ 0);
+ mSettingObserver = new SettingsObserver(context, mHandler);
+ context.getContentResolver().registerContentObserver(
+ LEARNING_TIME_ELAPSED_URI,
+ /* notifyForDescendants = */ true,
+ mSettingObserver);
+ context.getContentResolver().registerContentObserver(
+ LEARNING_EVENT_COUNT_URI,
+ /* notifyForDescendants = */ true,
+ mSettingObserver);
mLearnedHintLastShownEpochDay = Settings.Secure.getLong(
context.getContentResolver(), LEARNED_HINT_LAST_SHOWN_KEY, /* default = */ 0);
mLastLearningTimestamp = mClock.currentTimeMillis();
@@ -264,13 +286,25 @@ final class AssistHandleReminderExpBehavior implements BehaviorController {
if (mContext != null) {
mBroadcastDispatcher.get().unregisterReceiver(mDefaultHomeBroadcastReceiver);
mBootCompleteCache.get().removeListener(mBootCompleteListener);
- Settings.Secure.putLong(mContext.getContentResolver(), LEARNING_TIME_ELAPSED_KEY, 0);
- Settings.Secure.putInt(mContext.getContentResolver(), LEARNING_EVENT_COUNT_KEY, 0);
+ mContext.getContentResolver().unregisterContentObserver(mSettingObserver);
+ mSettingObserver = null;
+ // putString in order to use overrideableByRestore
+ Settings.Secure.putString(
+ mContext.getContentResolver(),
+ Settings.Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS,
+ Long.toString(0L),
+ /* overrideableByRestore = */ true);
+ // putString in order to use overrideableByRestore
+ Settings.Secure.putString(
+ mContext.getContentResolver(),
+ Settings.Secure.ASSIST_HANDLES_LEARNING_EVENT_COUNT,
+ Integer.toString(0),
+ /* overrideableByRestore = */ true);
Settings.Secure.putLong(mContext.getContentResolver(), LEARNED_HINT_LAST_SHOWN_KEY, 0);
mContext = null;
}
mStatusBarStateController.get().removeCallback(mStatusBarStateListener);
- mActivityManagerWrapper.get().unregisterTaskStackListener(mTaskStackChangeListener);
+ mTaskStackChangeListeners.get().unregisterTaskStackListener(mTaskStackChangeListener);
mOverviewProxyService.get().removeCallback(mOverviewProxyListener);
mSysUiFlagContainer.get().removeCallback(mSysUiStateCallback);
mWakefulnessLifecycle.get().removeObserver(mWakefulnessLifecycleObserver);
@@ -282,8 +316,12 @@ final class AssistHandleReminderExpBehavior implements BehaviorController {
return;
}
- Settings.Secure.putLong(
- mContext.getContentResolver(), LEARNING_EVENT_COUNT_KEY, ++mLearningCount);
+ // putString in order to use overrideableByRestore
+ Settings.Secure.putString(
+ mContext.getContentResolver(),
+ Settings.Secure.ASSIST_HANDLES_LEARNING_EVENT_COUNT,
+ Integer.toString(++mLearningCount),
+ /* overrideableByRestore = */ true);
}
@Override
@@ -460,8 +498,12 @@ final class AssistHandleReminderExpBehavior implements BehaviorController {
mIsLearned =
mLearningCount >= getLearningCount() || mLearningTimeElapsed >= getLearningTimeMs();
- mHandler.post(() -> Settings.Secure.putLong(
- mContext.getContentResolver(), LEARNING_TIME_ELAPSED_KEY, mLearningTimeElapsed));
+ // putString in order to use overrideableByRestore
+ mHandler.post(() -> Settings.Secure.putString(
+ mContext.getContentResolver(),
+ Settings.Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS,
+ Long.toString(mLearningTimeElapsed),
+ /* overrideableByRestore = */ true));
}
private void resetConsecutiveTaskSwitches() {
@@ -589,4 +631,32 @@ final class AssistHandleReminderExpBehavior implements BehaviorController {
+ "="
+ getShowWhenTaught());
}
+
+ private final class SettingsObserver extends ContentObserver {
+
+ private final Context mContext;
+
+ SettingsObserver(Context context, Handler handler) {
+ super(handler);
+ mContext = context;
+ }
+
+ @Override
+ public void onChange(boolean selfChange, @Nullable Uri uri) {
+ if (LEARNING_TIME_ELAPSED_URI.equals(uri)) {
+ mLastLearningTimestamp = mClock.currentTimeMillis();
+ mLearningTimeElapsed = Settings.Secure.getLong(
+ mContext.getContentResolver(),
+ Settings.Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS,
+ /* default = */ 0);
+ } else if (LEARNING_EVENT_COUNT_URI.equals(uri)) {
+ mLearningCount = Settings.Secure.getInt(
+ mContext.getContentResolver(),
+ Settings.Secure.ASSIST_HANDLES_LEARNING_EVENT_COUNT,
+ /* default = */ 0);
+ }
+
+ super.onChange(selfChange, uri);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/PhoneStateMonitor.java b/packages/SystemUI/src/com/android/systemui/assist/PhoneStateMonitor.java
index 50d559b7aeab..61951cc0b5e9 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/PhoneStateMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/PhoneStateMonitor.java
@@ -35,6 +35,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.PackageManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -82,7 +83,6 @@ public final class PhoneStateMonitor {
mStatusBarOptionalLazy = statusBarOptionalLazy;
mStatusBarStateController = Dependency.get(StatusBarStateController.class);
- ActivityManagerWrapper activityManagerWrapper = ActivityManagerWrapper.getInstance();
mDefaultHome = getCurrentDefaultHome();
bootCompleteCache.addListener(() -> mDefaultHome = getCurrentDefaultHome());
IntentFilter intentFilter = new IntentFilter();
@@ -95,12 +95,13 @@ public final class PhoneStateMonitor {
mDefaultHome = getCurrentDefaultHome();
}
}, intentFilter);
- mLauncherShowing = isLauncherShowing(activityManagerWrapper.getRunningTask());
- activityManagerWrapper.registerTaskStackListener(new TaskStackChangeListener() {
- @Override
- public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) {
- mLauncherShowing = isLauncherShowing(taskInfo);
- }
+ mLauncherShowing = isLauncherShowing(ActivityManagerWrapper.getInstance().getRunningTask());
+ TaskStackChangeListeners.getInstance().registerTaskStackListener(
+ new TaskStackChangeListener() {
+ @Override
+ public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) {
+ mLauncherShowing = isLauncherShowing(taskInfo);
+ }
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index bde9a6e7c714..b1ae56a584d2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -36,7 +36,7 @@ import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.FingerprintSensorProperties;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -306,10 +306,10 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
if (mFingerprintManager != null && mFingerprintManager.isHardwareDetected()) {
- final List<FingerprintSensorProperties> fingerprintSensorProperties =
- mFingerprintManager.getSensorProperties();
- for (FingerprintSensorProperties props : fingerprintSensorProperties) {
- if (props.sensorType == FingerprintSensorProperties.TYPE_UDFPS) {
+ final List<FingerprintSensorPropertiesInternal> fingerprintSensorProperties =
+ mFingerprintManager.getSensorPropertiesInternal();
+ for (FingerprintSensorPropertiesInternal props : fingerprintSensorProperties) {
+ if (props.isAnyUdfpsType()) {
mUdfpsController = mUdfpsControllerFactory.get();
break;
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index d79c96ea4774..e3b00495f3dc 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -16,6 +16,7 @@
package com.android.systemui.biometrics;
+import static com.android.internal.util.Preconditions.checkArgument;
import static com.android.internal.util.Preconditions.checkNotNull;
import android.annotation.SuppressLint;
@@ -25,6 +26,7 @@ import android.content.res.TypedArray;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.PowerManager;
import android.os.UserHandle;
@@ -41,6 +43,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.BrightnessSynchronizer;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.DozeReceiver;
@@ -56,7 +59,15 @@ import javax.inject.Inject;
/**
* Shows and hides the under-display fingerprint sensor (UDFPS) overlay, handles UDFPS touch events,
* and coordinates triggering of the high-brightness mode (HBM).
+ *
+ * Note that the current architecture is designed so that a single {@link UdfpsController}
+ * controls/manages all UDFPS sensors. In other words, a single controller is registered with
+ * {@link com.android.server.biometrics.sensors.fingerprint.FingerprintService}, and interfaces such
+ * as {@link FingerprintManager#onPointerDown(int, int, int, float, float)} or
+ * {@link IUdfpsOverlayController#showUdfpsOverlay(int)}should all have
+ * {@code sensorId} parameters.
*/
+@SuppressWarnings("deprecation")
class UdfpsController implements DozeReceiver {
private static final String TAG = "UdfpsController";
// Gamma approximation for the sRGB color space.
@@ -64,6 +75,10 @@ class UdfpsController implements DozeReceiver {
private static final long AOD_INTERRUPT_TIMEOUT_MILLIS = 1000;
private final FingerprintManager mFingerprintManager;
+ // Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
+ // sensors, this, in addition to a lot of the code here, will be updated.
+ @VisibleForTesting
+ final int mUdfpsSensorId;
private final WindowManager mWindowManager;
private final SystemSettings mSystemSettings;
private final DelayableExecutor mFgExecutor;
@@ -103,17 +118,17 @@ class UdfpsController implements DozeReceiver {
public class UdfpsOverlayController extends IUdfpsOverlayController.Stub {
@Override
- public void showUdfpsOverlay() {
+ public void showUdfpsOverlay(int sensorId) {
UdfpsController.this.setShowOverlay(true);
}
@Override
- public void hideUdfpsOverlay() {
+ public void hideUdfpsOverlay(int sensorId) {
UdfpsController.this.setShowOverlay(false);
}
@Override
- public void setDebugMessage(String message) {
+ public void setDebugMessage(int sensorId, String message) {
mView.setDebugMessage(message);
}
}
@@ -165,6 +180,18 @@ class UdfpsController implements DozeReceiver {
mFgExecutor = fgExecutor;
mLayoutParams = createLayoutParams(context);
+ int udfpsSensorId = -1;
+ for (FingerprintSensorPropertiesInternal props :
+ mFingerprintManager.getSensorPropertiesInternal()) {
+ if (props.isAnyUdfpsType()) {
+ udfpsSensorId = props.sensorId;
+ break;
+ }
+ }
+ // At least one UDFPS sensor exists
+ checkArgument(udfpsSensorId != -1);
+ mUdfpsSensorId = udfpsSensorId;
+
mView = (UdfpsView) inflater.inflate(R.layout.udfps_view, null, false);
mHbmPath = resources.getString(R.string.udfps_hbm_sysfs_path);
@@ -347,7 +374,7 @@ class UdfpsController implements DozeReceiver {
fw.write(mHbmEnableCommand);
fw.close();
}
- mFingerprintManager.onFingerDown(x, y, minor, major);
+ mFingerprintManager.onPointerDown(mUdfpsSensorId, x, y, minor, major);
} catch (IOException e) {
mView.hideScrimAndDot();
Log.e(TAG, "onFingerDown | failed to enable HBM: " + e.getMessage());
@@ -355,7 +382,7 @@ class UdfpsController implements DozeReceiver {
}
private void onFingerUp() {
- mFingerprintManager.onFingerUp();
+ mFingerprintManager.onPointerUp(mUdfpsSensorId);
// Hiding the scrim before disabling HBM results in less noticeable flicker.
mView.hideScrimAndDot();
if (mHbmSupported) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
index fbb47e2086b3..57d8dc70cd88 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
@@ -15,8 +15,8 @@
*/
package com.android.systemui.bubbles;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.os.AsyncTask.Status.FINISHED;
-import static android.view.Display.INVALID_DISPLAY;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
@@ -25,6 +25,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Notification;
import android.app.PendingIntent;
+import android.app.Person;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -35,16 +36,18 @@ import android.graphics.Bitmap;
import android.graphics.Path;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
+import android.os.Parcelable;
import android.os.UserHandle;
import android.provider.Settings;
+import android.text.TextUtils;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.InstanceId;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.List;
import java.util.Objects;
/**
@@ -169,11 +172,10 @@ class Bubble implements BubbleViewProvider {
}
@VisibleForTesting(visibility = PRIVATE)
- Bubble(@NonNull final NotificationEntry e,
+ Bubble(@NonNull final BubbleEntry entry,
@Nullable final BubbleController.NotificationSuppressionChangedListener listener,
final BubbleController.PendingIntentCanceledListener intentCancelListener) {
- Objects.requireNonNull(e);
- mKey = e.getKey();
+ mKey = entry.getKey();
mSuppressionListener = listener;
mIntentCancelListener = intent -> {
if (mIntent != null) {
@@ -181,7 +183,7 @@ class Bubble implements BubbleViewProvider {
}
intentCancelListener.onPendingIntentCanceled(this);
};
- setEntry(e);
+ setEntry(entry);
}
@Override
@@ -254,7 +256,8 @@ class Bubble implements BubbleViewProvider {
}
/**
- * Cleanup expanded view for bubbles going into overflow.
+ * Call this to clean up the task for the bubble. Ensure this is always called when done with
+ * the bubble.
*/
void cleanupExpandedView() {
if (mExpandedView != null) {
@@ -268,8 +271,7 @@ class Bubble implements BubbleViewProvider {
}
/**
- * Call when the views should be removed, ensure this is called to clean up ActivityView
- * content.
+ * Call when all the views should be removed/cleaned up.
*/
void cleanupViews() {
cleanupExpandedView();
@@ -294,6 +296,15 @@ class Bubble implements BubbleViewProvider {
}
/**
+ * Sets whether this bubble is considered visually interruptive. This method is purely for
+ * testing.
+ */
+ @VisibleForTesting
+ void setVisuallyInterruptiveForTest(boolean visuallyInterruptive) {
+ mIsVisuallyInterruptive = visuallyInterruptive;
+ }
+
+ /**
* Starts a task to inflate & load any necessary information to display a bubble.
*
* @param callback the callback to notify one the bubble is ready to be displayed.
@@ -379,30 +390,28 @@ class Bubble implements BubbleViewProvider {
/**
* Sets the entry associated with this bubble.
*/
- void setEntry(@NonNull final NotificationEntry entry) {
+ void setEntry(@NonNull final BubbleEntry entry) {
Objects.requireNonNull(entry);
- Objects.requireNonNull(entry.getSbn());
- mLastUpdated = entry.getSbn().getPostTime();
- mIsBubble = entry.getSbn().getNotification().isBubbleNotification();
- mPackageName = entry.getSbn().getPackageName();
- mUser = entry.getSbn().getUser();
+ mLastUpdated = entry.getStatusBarNotification().getPostTime();
+ mIsBubble = entry.getStatusBarNotification().getNotification().isBubbleNotification();
+ mPackageName = entry.getStatusBarNotification().getPackageName();
+ mUser = entry.getStatusBarNotification().getUser();
mTitle = getTitle(entry);
- mIsClearable = entry.isClearable();
- mShouldSuppressNotificationDot = entry.shouldSuppressNotificationDot();
- mShouldSuppressNotificationList = entry.shouldSuppressNotificationList();
- mShouldSuppressPeek = entry.shouldSuppressPeek();
- mChannelId = entry.getSbn().getNotification().getChannelId();
- mNotificationId = entry.getSbn().getId();
- mAppUid = entry.getSbn().getUid();
- mInstanceId = entry.getSbn().getInstanceId();
- mFlyoutMessage = BubbleViewInfoTask.extractFlyoutMessage(entry);
- mShortcutInfo = (entry.getRanking() != null ? entry.getRanking().getShortcutInfo() : null);
- mMetadataShortcutId = (entry.getBubbleMetadata() != null
- ? entry.getBubbleMetadata().getShortcutId() : null);
+ mChannelId = entry.getStatusBarNotification().getNotification().getChannelId();
+ mNotificationId = entry.getStatusBarNotification().getId();
+ mAppUid = entry.getStatusBarNotification().getUid();
+ mInstanceId = entry.getStatusBarNotification().getInstanceId();
+ mFlyoutMessage = extractFlyoutMessage(entry);
if (entry.getRanking() != null) {
+ mShortcutInfo = entry.getRanking().getShortcutInfo();
mIsVisuallyInterruptive = entry.getRanking().visuallyInterruptive();
+ if (entry.getRanking().getChannel() != null) {
+ mIsImportantConversation =
+ entry.getRanking().getChannel().isImportantConversation();
+ }
}
if (entry.getBubbleMetadata() != null) {
+ mMetadataShortcutId = entry.getBubbleMetadata().getShortcutId();
mFlags = entry.getBubbleMetadata().getFlags();
mDesiredHeight = entry.getBubbleMetadata().getDesiredHeight();
mDesiredHeightResId = entry.getBubbleMetadata().getDesiredHeightResId();
@@ -419,12 +428,16 @@ class Bubble implements BubbleViewProvider {
} else if (mIntent != null && entry.getBubbleMetadata().getIntent() == null) {
// Was an intent bubble now it's a shortcut bubble... still unregister the listener
mIntent.unregisterCancelListener(mIntentCancelListener);
+ mIntentActive = false;
mIntent = null;
}
mDeleteIntent = entry.getBubbleMetadata().getDeleteIntent();
}
- mIsImportantConversation =
- entry.getChannel() != null && entry.getChannel().isImportantConversation();
+
+ mIsClearable = entry.isClearable();
+ mShouldSuppressNotificationDot = entry.shouldSuppressNotificationDot();
+ mShouldSuppressNotificationList = entry.shouldSuppressNotificationList();
+ mShouldSuppressPeek = entry.shouldSuppressPeek();
}
@Nullable
@@ -455,14 +468,6 @@ class Bubble implements BubbleViewProvider {
return mIntentActive;
}
- /**
- * @return the display id of the virtual display on which bubble contents is drawn.
- */
- @Override
- public int getDisplayId() {
- return mExpandedView != null ? mExpandedView.getVirtualDisplayId() : INVALID_DISPLAY;
- }
-
public InstanceId getInstanceId() {
return mInstanceId;
}
@@ -477,6 +482,14 @@ class Bubble implements BubbleViewProvider {
}
/**
+ * @return the task id of the task in which bubble contents is drawn.
+ */
+ @Override
+ public int getTaskId() {
+ return mExpandedView != null ? mExpandedView.getTaskId() : INVALID_TASK_ID;
+ }
+
+ /**
* Should be invoked whenever a Bubble is accessed (selected while expanded).
*/
void markAsAccessedAt(long lastAccessedMillis) {
@@ -725,9 +738,75 @@ class Bubble implements BubbleViewProvider {
}
@Nullable
- private static String getTitle(@NonNull final NotificationEntry e) {
- final CharSequence titleCharSeq = e.getSbn().getNotification().extras.getCharSequence(
- Notification.EXTRA_TITLE);
+ private static String getTitle(@NonNull final BubbleEntry e) {
+ final CharSequence titleCharSeq = e.getStatusBarNotification()
+ .getNotification().extras.getCharSequence(Notification.EXTRA_TITLE);
return titleCharSeq == null ? null : titleCharSeq.toString();
}
+
+ /**
+ * Returns our best guess for the most relevant text summary of the latest update to this
+ * notification, based on its type. Returns null if there should not be an update message.
+ */
+ @NonNull
+ static Bubble.FlyoutMessage extractFlyoutMessage(BubbleEntry entry) {
+ Objects.requireNonNull(entry);
+ final Notification underlyingNotif = entry.getStatusBarNotification().getNotification();
+ final Class<? extends Notification.Style> style = underlyingNotif.getNotificationStyle();
+
+ Bubble.FlyoutMessage bubbleMessage = new Bubble.FlyoutMessage();
+ bubbleMessage.isGroupChat = underlyingNotif.extras.getBoolean(
+ Notification.EXTRA_IS_GROUP_CONVERSATION);
+ try {
+ if (Notification.BigTextStyle.class.equals(style)) {
+ // Return the big text, it is big so probably important. If it's not there use the
+ // normal text.
+ CharSequence bigText =
+ underlyingNotif.extras.getCharSequence(Notification.EXTRA_BIG_TEXT);
+ bubbleMessage.message = !TextUtils.isEmpty(bigText)
+ ? bigText
+ : underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT);
+ return bubbleMessage;
+ } else if (Notification.MessagingStyle.class.equals(style)) {
+ final List<Notification.MessagingStyle.Message> messages =
+ Notification.MessagingStyle.Message.getMessagesFromBundleArray(
+ (Parcelable[]) underlyingNotif.extras.get(
+ Notification.EXTRA_MESSAGES));
+
+ final Notification.MessagingStyle.Message latestMessage =
+ Notification.MessagingStyle.findLatestIncomingMessage(messages);
+ if (latestMessage != null) {
+ bubbleMessage.message = latestMessage.getText();
+ Person sender = latestMessage.getSenderPerson();
+ bubbleMessage.senderName = sender != null ? sender.getName() : null;
+ bubbleMessage.senderAvatar = null;
+ bubbleMessage.senderIcon = sender != null ? sender.getIcon() : null;
+ return bubbleMessage;
+ }
+ } else if (Notification.InboxStyle.class.equals(style)) {
+ CharSequence[] lines =
+ underlyingNotif.extras.getCharSequenceArray(Notification.EXTRA_TEXT_LINES);
+
+ // Return the last line since it should be the most recent.
+ if (lines != null && lines.length > 0) {
+ bubbleMessage.message = lines[lines.length - 1];
+ return bubbleMessage;
+ }
+ } else if (Notification.MediaStyle.class.equals(style)) {
+ // Return nothing, media updates aren't typically useful as a text update.
+ return bubbleMessage;
+ } else {
+ // Default to text extra.
+ bubbleMessage.message =
+ underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT);
+ return bubbleMessage;
+ }
+ } catch (ClassCastException | NullPointerException | ArrayIndexOutOfBoundsException e) {
+ // No use crashing, we'll just return null and the caller will assume there's no update
+ // message.
+ e.printStackTrace();
+ }
+
+ return bubbleMessage;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index b6106025a17a..3f94b00d3c60 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -16,6 +16,7 @@
package com.android.systemui.bubbles;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.Notification.FLAG_BUBBLE;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_SELECTED;
@@ -27,13 +28,10 @@ import static android.service.notification.NotificationListenerService.REASON_CL
import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
import static android.service.notification.NotificationStats.DISMISSAL_BUBBLE;
import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.Display.INVALID_DISPLAY;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_CONTROLLER;
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;
@@ -47,6 +45,7 @@ import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.ActivityManager.RunningTaskInfo;
+import android.app.ActivityTaskManager;
import android.app.INotificationManager;
import android.app.Notification;
import android.app.NotificationChannel;
@@ -71,7 +70,6 @@ import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
import android.util.SparseSetArray;
-import android.view.Display;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
@@ -81,17 +79,18 @@ import androidx.annotation.MainThread;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.Dumpable;
import com.android.systemui.bubbles.dagger.BubbleModule;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.PinnedStackListenerForwarder;
+import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.TaskStackChangeListener;
-import com.android.systemui.shared.system.WindowManagerWrapper;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationRemoveInterceptor;
@@ -114,7 +113,10 @@ import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ZenModeController;
-import com.android.systemui.util.FloatingContentCoordinator;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.WindowManagerShellWrapper;
+import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.pip.PinnedStackListenerForwarder;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -130,7 +132,8 @@ import java.util.Objects;
*
* The controller manages addition, removal, and visible state of bubbles on screen.
*/
-public class BubbleController implements ConfigurationController.ConfigurationListener, Dumpable {
+public class BubbleController implements Bubbles, ConfigurationController.ConfigurationListener,
+ Dumpable {
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES;
@@ -168,8 +171,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
private final ShadeController mShadeController;
private final FloatingContentCoordinator mFloatingContentCoordinator;
private final BubbleDataRepository mDataRepository;
- private BubbleLogger mLogger = new BubbleLoggerImpl();
-
+ private BubbleLogger mLogger;
+ private final Handler mMainHandler;
private BubbleData mBubbleData;
private ScrimView mBubbleScrim;
@Nullable private BubbleStackView mStackView;
@@ -231,11 +234,18 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
*/
private int mDensityDpi = Configuration.DENSITY_DPI_UNDEFINED;
+ /**
+ * Last known font scale, used to detect font size changes in {@link #onConfigChanged}.
+ */
+ private float mFontScale = 0;
+
/** Last known direction, used to detect layout direction changes @link #onConfigChanged}. */
private int mLayoutDirection = View.LAYOUT_DIRECTION_UNDEFINED;
private boolean mInflateSynchronously;
+ private MultiWindowTaskListener mTaskListener;
+
// TODO (b/145659174): allow for multiple callbacks to support the "shadow" new notif pipeline
private final List<NotifCallback> mCallbacks = new ArrayList<>();
@@ -345,7 +355,46 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
/**
* Injected constructor. See {@link BubbleModule}.
*/
- public BubbleController(Context context,
+ public static BubbleController create(Context context,
+ NotificationShadeWindowController notificationShadeWindowController,
+ StatusBarStateController statusBarStateController,
+ ShadeController shadeController,
+ @Nullable BubbleStackView.SurfaceSynchronizer synchronizer,
+ ConfigurationController configurationController,
+ NotificationInterruptStateProvider interruptionStateProvider,
+ ZenModeController zenModeController,
+ NotificationLockscreenUserManager notifUserManager,
+ NotificationGroupManagerLegacy groupManager,
+ NotificationEntryManager entryManager,
+ NotifPipeline notifPipeline,
+ FeatureFlags featureFlags,
+ DumpManager dumpManager,
+ FloatingContentCoordinator floatingContentCoordinator,
+ SysUiState sysUiState,
+ INotificationManager notificationManager,
+ @Nullable IStatusBarService statusBarService,
+ WindowManager windowManager,
+ WindowManagerShellWrapper windowManagerShellWrapper,
+ LauncherApps launcherApps,
+ UiEventLogger uiEventLogger,
+ @Main Handler mainHandler,
+ ShellTaskOrganizer organizer) {
+ BubbleLogger logger = new BubbleLogger(uiEventLogger);
+ return new BubbleController(context, notificationShadeWindowController,
+ statusBarStateController, shadeController, new BubbleData(context, logger),
+ synchronizer, configurationController, interruptionStateProvider, zenModeController,
+ notifUserManager, groupManager, entryManager, notifPipeline, featureFlags,
+ dumpManager, floatingContentCoordinator,
+ new BubbleDataRepository(context, launcherApps), sysUiState, notificationManager,
+ statusBarService, windowManager, windowManagerShellWrapper, launcherApps, logger,
+ mainHandler, organizer);
+ }
+
+ /**
+ * Testing constructor.
+ */
+ @VisibleForTesting
+ BubbleController(Context context,
NotificationShadeWindowController notificationShadeWindowController,
StatusBarStateController statusBarStateController,
ShadeController shadeController,
@@ -366,7 +415,11 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
INotificationManager notificationManager,
@Nullable IStatusBarService statusBarService,
WindowManager windowManager,
- LauncherApps launcherApps) {
+ WindowManagerShellWrapper windowManagerShellWrapper,
+ LauncherApps launcherApps,
+ BubbleLogger bubbleLogger,
+ Handler mainHandler,
+ ShellTaskOrganizer organizer) {
dumpManager.registerDumpable(TAG, this);
mContext = context;
mShadeController = shadeController;
@@ -376,6 +429,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
mFloatingContentCoordinator = floatingContentCoordinator;
mDataRepository = dataRepository;
mINotificationManager = notificationManager;
+ mLogger = bubbleLogger;
+ mMainHandler = mainHandler;
mZenModeController.addCallback(new ZenModeController.Callback() {
@Override
public void onZenChanged(int zen) {
@@ -414,7 +469,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
if (bubble.getBubbleIntent() == null) {
return;
}
- if (bubble.isIntentActive()) {
+ if (bubble.isIntentActive()
+ || mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) {
bubble.setPendingIntentCanceled();
return;
}
@@ -438,10 +494,10 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
statusBarStateController.addCallback(mStatusBarStateListener);
mTaskStackListener = new BubbleTaskStackListener();
- ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
+ TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
try {
- WindowManagerWrapper.getInstance().addPinnedStackListener(new BubblesImeListener());
+ windowManagerShellWrapper.addPinnedStackListener(new BubblesImeListener());
} catch (RemoteException e) {
e.printStackTrace();
}
@@ -473,6 +529,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
});
mBubbleIconFactory = new BubbleIconFactory(context);
+ mTaskListener = new MultiWindowTaskListener(mMainHandler, organizer);
launcherApps.registerCallback(new LauncherApps.Callback() {
@Override
@@ -519,6 +576,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
/**
* See {@link NotifCallback}.
*/
+ @Override
public void addNotifCallback(NotifCallback callback) {
mCallbacks.add(callback);
}
@@ -534,6 +592,12 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
}
}
+ private void onBubbleExpandChanged(boolean shouldExpand) {
+ mSysUiState
+ .setFlag(QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED, shouldExpand)
+ .commitUpdate(mContext.getDisplayId());
+ }
+
private void setupNEM() {
mNotificationEntryManager.addNotificationEntryListener(
new NotificationEntryListener() {
@@ -700,6 +764,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
* since we want the scrim's appearance and behavior to be identical to that of the notification
* shade scrim.
*/
+ @Override
public ScrimView getScrimForBubble() {
return mBubbleScrim;
}
@@ -708,6 +773,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
* Called when the status bar has become visible or invisible (either permanently or
* temporarily).
*/
+ @Override
public void onStatusBarVisibilityChanged(boolean visible) {
if (mStackView != null) {
// Hide the stack temporarily if the status bar has been made invisible, and the stack
@@ -725,17 +791,24 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
mInflateSynchronously = inflateSynchronously;
}
- void setOverflowListener(BubbleData.Listener listener) {
+ @Override
+ public void setOverflowListener(BubbleData.Listener listener) {
mOverflowListener = listener;
}
/**
* @return Bubbles for updating overflow.
*/
- List<Bubble> getOverflowBubbles() {
+ @Override
+ public List<Bubble> getOverflowBubbles() {
return mBubbleData.getOverflowBubbles();
}
+ @Override
+ public MultiWindowTaskListener getTaskManager() {
+ return mTaskListener;
+ }
+
/**
* BubbleStackView is lazily created by this method the first time a Bubble is added. This
* method initializes the stack view and adds it to the StatusBar just above the scrim.
@@ -744,8 +817,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
if (mStackView == null) {
mStackView = new BubbleStackView(
mContext, mBubbleData, mSurfaceSynchronizer, mFloatingContentCoordinator,
- mSysUiState, this::onAllBubblesAnimatedOut, this::onImeVisibilityChanged,
- this::hideCurrentInputMethod);
+ this::onAllBubblesAnimatedOut, this::onImeVisibilityChanged,
+ this::hideCurrentInputMethod, this::onBubbleExpandChanged);
mStackView.setStackStartPosition(mPositionFromRemovedStack);
mStackView.addView(mBubbleScrim);
if (mExpandListener != null) {
@@ -778,9 +851,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
- // Start not focusable - we'll become focusable when expanded so the ActivityView
- // can use the IME.
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
PixelFormat.TRANSLUCENT);
@@ -796,16 +868,13 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
mAddedToWindowManager = true;
mWindowManager.addView(mStackView, mWmLayoutParams);
} catch (IllegalStateException e) {
- // This means the stack has already been added. This shouldn't happen, since we keep
- // track of that, but just in case, update the previously added view's layout params.
+ // This means the stack has already been added. This shouldn't happen...
e.printStackTrace();
- updateWmFlags();
}
}
private void onImeVisibilityChanged(boolean imeVisible) {
mImeVisible = imeVisible;
- updateWmFlags();
}
/** Removes the BubbleStackView from the WindowManager if it's there. */
@@ -832,35 +901,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
}
/**
- * Updates the BubbleStackView's WindowManager.LayoutParams, and updates the WindowManager with
- * the new params if the stack has been added.
- */
- private void updateWmFlags() {
- if (mStackView == null) {
- return;
- }
- if (isStackExpanded() && !mImeVisible) {
- // If we're expanded, and the IME isn't visible, we want to be focusable. This ensures
- // that any taps within Bubbles (including on the ActivityView) results in Bubbles
- // receiving focus and clearing it from any other windows that might have it.
- mWmLayoutParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
- } else {
- // If we're collapsed, we don't want to be focusable since tapping on the stack would
- // steal focus from apps. We also don't want to be focusable if the IME is visible,
- mWmLayoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
- }
-
- if (mAddedToWindowManager) {
- try {
- mWindowManager.updateViewLayout(mStackView, mWmLayoutParams);
- } catch (IllegalArgumentException e) {
- // If the stack is somehow not there, ignore the attempt to update it.
- e.printStackTrace();
- }
- }
- }
-
- /**
* Called by the BubbleStackView and whenever all bubbles have animated out, and none have been
* added in the meantime.
*/
@@ -948,6 +988,10 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
mBubbleIconFactory = new BubbleIconFactory(mContext);
mStackView.onDisplaySizeChanged();
}
+ if (newConfig.fontScale != mFontScale) {
+ mFontScale = newConfig.fontScale;
+ mStackView.updateFlyout(mFontScale);
+ }
if (newConfig.getLayoutDirection() != mLayoutDirection) {
mLayoutDirection = newConfig.getLayoutDirection();
mStackView.onLayoutDirectionChanged(mLayoutDirection);
@@ -955,20 +999,15 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
}
}
- boolean inLandscape() {
- return mOrientation == Configuration.ORIENTATION_LANDSCAPE;
- }
-
/**
* Set a listener to be notified of bubble expand events.
*/
+ @Override
public void setExpandListener(BubbleExpandListener listener) {
mExpandListener = ((isExpanding, key) -> {
if (listener != null) {
listener.onBubbleExpandChanged(isExpanding, key);
}
-
- updateWmFlags();
});
if (mStackView != null) {
mStackView.setExpandListener(mExpandListener);
@@ -987,29 +1026,17 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
return mBubbleData.hasBubbles();
}
- /**
- * Whether the stack of bubbles is expanded or not.
- */
+ @Override
public boolean isStackExpanded() {
return mBubbleData.isExpanded();
}
- /**
- * Tell the stack of bubbles to collapse.
- */
+ @Override
public void collapseStack() {
mBubbleData.setExpanded(false /* expanded */);
}
- /**
- * True if either:
- * (1) There is a bubble associated with the provided key and if its notification is hidden
- * from the shade.
- * (2) There is a group summary associated with the provided key that is hidden from the shade
- * because it has been dismissed but still has child bubbles active.
- *
- * False otherwise.
- */
+ @Override
public boolean isBubbleNotificationSuppressedFromShade(NotificationEntry entry) {
String key = entry.getKey();
boolean isSuppressedBubble = (mBubbleData.hasAnyBubbleWithKey(key)
@@ -1021,19 +1048,14 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
return (isSummary && isSuppressedSummary) || isSuppressedBubble;
}
- /**
- * True if:
- * (1) The current notification entry same as selected bubble notification entry and the
- * stack is currently expanded.
- *
- * False otherwise.
- */
+ @Override
public boolean isBubbleExpanded(NotificationEntry entry) {
return isStackExpanded() && mBubbleData != null && mBubbleData.getSelectedBubble() != null
- && mBubbleData.getSelectedBubble().getKey().equals(entry.getKey()) ? true : false;
+ && mBubbleData.getSelectedBubble().getKey().equals(entry.getKey());
}
- void promoteBubbleFromOverflow(Bubble bubble) {
+ @Override
+ public void promoteBubbleFromOverflow(Bubble bubble) {
mLogger.log(bubble, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_BACK_TO_STACK);
bubble.setInflateSynchronously(mInflateSynchronously);
bubble.setShouldAutoExpand(true);
@@ -1041,12 +1063,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
setIsBubble(bubble, true /* isBubble */);
}
- /**
- * Request the stack expand if needed, then select the specified Bubble as current.
- * If no bubble exists for this entry, one is created.
- *
- * @param entry the notification for the bubble to be selected
- */
+ @Override
public void expandStackAndSelectBubble(NotificationEntry entry) {
if (mStatusBarStateListener.getCurrentState() == SHADE) {
mNotifEntryToExpandOnShadeUnlock = null;
@@ -1074,12 +1091,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
}
}
- /**
- * When a notification is marked Priority, expand the stack if needed,
- * then (maybe create and) select the given bubble.
- *
- * @param entry the notification for the bubble to show
- */
+ @Override
public void onUserChangedImportance(NotificationEntry entry) {
try {
int flags = Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
@@ -1095,16 +1107,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
}
/**
- * Directs a back gesture at the bubble stack. When opened, the current expanded bubble
- * is forwarded a back key down/up pair.
- */
- public void performBackPressIfNeeded() {
- if (mStackView != null) {
- mStackView.performBackPressIfNeeded();
- }
- }
-
- /**
* Adds or updates a bubble associated with the provided notification entry.
*
* @param notif the notification associated with this bubble.
@@ -1140,8 +1142,18 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
if (notif.getImportance() >= NotificationManager.IMPORTANCE_HIGH) {
notif.setInterruption();
}
- Bubble bubble = mBubbleData.getOrCreateBubble(notif, null /* persistedBubble */);
- inflateAndAdd(bubble, suppressFlyout, showInShade);
+ if (!notif.getRanking().visuallyInterruptive()
+ && (notif.getBubbleMetadata() != null
+ && !notif.getBubbleMetadata().getAutoExpandBubble())
+ && mBubbleData.hasOverflowBubbleWithKey(notif.getKey())) {
+ // Update the bubble but don't promote it out of overflow
+ Bubble b = mBubbleData.getOverflowBubbleWithKey(notif.getKey());
+ b.setEntry(notifToBubbleEntry(notif));
+ } else {
+ Bubble bubble = mBubbleData.getOrCreateBubble(
+ notifToBubbleEntry(notif), null /* persistedBubble */);
+ inflateAndAdd(bubble, suppressFlyout, showInShade);
+ }
}
void inflateAndAdd(Bubble bubble, boolean suppressFlyout, boolean showInShade) {
@@ -1152,15 +1164,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
mContext, mStackView, mBubbleIconFactory, false /* skipInflation */);
}
- /**
- * Called when a user has indicated that an active notification should be shown as a bubble.
- * <p>
- * This method will collapse the shade, create the bubble without a flyout or dot, and suppress
- * the notification from appearing in the shade.
- *
- * @param entry the notification to change bubble state for.
- * @param shouldBubble whether the notification should show as a bubble or not.
- */
+ @Override
public void onUserChangedBubble(@NonNull final NotificationEntry entry, boolean shouldBubble) {
NotificationChannel channel = entry.getChannel();
final String appPkg = entry.getSbn().getPackageName();
@@ -1199,13 +1203,9 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
}
}
- /**
- * Removes the bubble with the given key.
- * <p>
- * Must be called from the main thread.
- */
@MainThread
- void removeBubble(String key, int reason) {
+ @Override
+ public void removeBubble(String key, int reason) {
if (mBubbleData.hasAnyBubbleWithKey(key)) {
mBubbleData.dismissBubbleWithKey(key, reason);
}
@@ -1237,8 +1237,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
mBubbleData.removeSuppressedSummary(groupKey);
// Remove any associated bubble children with the summary
- final List<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(
- groupKey, mNotificationEntryManager);
+ final List<Bubble> bubbleChildren = getBubblesInGroup(groupKey);
for (int i = 0; i < bubbleChildren.size(); i++) {
removeBubble(bubbleChildren.get(i).getKey(), DISMISS_GROUP_CANCELLED);
}
@@ -1284,6 +1283,25 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
}
}
+ /**
+ * Retrieves any bubbles that are part of the notification group represented by the provided
+ * group key.
+ */
+ private ArrayList<Bubble> getBubblesInGroup(@Nullable String groupKey) {
+ ArrayList<Bubble> bubbleChildren = new ArrayList<>();
+ if (groupKey == null) {
+ return bubbleChildren;
+ }
+ for (Bubble bubble : mBubbleData.getActiveBubbles()) {
+ final NotificationEntry entry =
+ mNotificationEntryManager.getPendingOrActiveNotif(bubble.getKey());
+ if (entry != null && groupKey.equals(entry.getSbn().getGroupKey())) {
+ bubbleChildren.add(bubble);
+ }
+ }
+ return bubbleChildren;
+ }
+
private void setIsBubble(@NonNull final NotificationEntry entry, final boolean isBubble,
final boolean autoExpand) {
Objects.requireNonNull(entry);
@@ -1394,8 +1412,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
}
if (entry != null) {
final String groupKey = entry.getSbn().getGroupKey();
- if (mBubbleData.getBubblesInGroup(
- groupKey, mNotificationEntryManager).isEmpty()) {
+ if (getBubblesInGroup(groupKey).isEmpty()) {
// Time to potentially remove the summary
for (NotifCallback cb : mCallbacks) {
cb.maybeCancelSummary(entry);
@@ -1447,16 +1464,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
}
};
- /**
- * We intercept notification entries (including group summaries) dismissed by the user when
- * there is an active bubble associated with it. We do this so that developers can still
- * cancel it (and hence the bubbles associated with it). However, these intercepted
- * notifications should then be hidden from the shade since the user has cancelled them, so we
- * {@link Bubble#setSuppressNotification}. For the case of suppressed summaries, we also add
- * {@link BubbleData#addSummaryToSuppress}.
- *
- * @return true if we want to intercept the dismissal of the entry, else false.
- */
+ @Override
public boolean handleDismissalInterception(NotificationEntry entry) {
if (entry == null) {
return false;
@@ -1487,8 +1495,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
}
String groupKey = entry.getSbn().getGroupKey();
- ArrayList<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(
- groupKey, mNotificationEntryManager);
+ ArrayList<Bubble> bubbleChildren = getBubblesInGroup(groupKey);
boolean isSuppressedSummary = (mBubbleData.isSummarySuppressed(groupKey)
&& mBubbleData.getSummaryKey(groupKey).equals(entry.getKey()));
boolean isSummary = entry.getSbn().getNotification().isGroupSummary();
@@ -1580,21 +1587,20 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
}
/**
- * The display id of the expanded view, if the stack is expanded and not occluded by the
- * status bar, otherwise returns {@link Display#INVALID_DISPLAY}.
+ * The task id of the expanded view, if the stack is expanded and not occluded by the
+ * status bar, otherwise returns {@link ActivityTaskManager#INVALID_TASK_ID}.
*/
- public int getExpandedDisplayId(Context context) {
+ private int getExpandedTaskId() {
if (mStackView == null) {
- return INVALID_DISPLAY;
+ return INVALID_TASK_ID;
}
- final boolean defaultDisplay = context.getDisplay() != null
- && context.getDisplay().getDisplayId() == DEFAULT_DISPLAY;
final BubbleViewProvider expandedViewProvider = mStackView.getExpandedBubble();
- if (defaultDisplay && expandedViewProvider != null && isStackExpanded()
+ if (expandedViewProvider != null && isStackExpanded()
+ && !mStackView.isExpansionAnimating()
&& !mNotificationShadeWindowController.getPanelExpanded()) {
- return expandedViewProvider.getDisplayId();
+ return expandedViewProvider.getTaskId();
}
- return INVALID_DISPLAY;
+ return INVALID_TASK_ID;
}
@VisibleForTesting
@@ -1628,18 +1634,17 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
@Override
public void onTaskMovedToFront(RunningTaskInfo taskInfo) {
- if (mStackView != null && taskInfo.displayId == Display.DEFAULT_DISPLAY) {
- if (!mStackView.isExpansionAnimating()) {
- mBubbleData.setExpanded(false);
- }
+ int expandedId = getExpandedTaskId();
+ if (expandedId != INVALID_TASK_ID && expandedId != taskInfo.taskId) {
+ mBubbleData.setExpanded(false);
}
}
@Override
- public void onActivityRestartAttempt(RunningTaskInfo task, boolean homeTaskVisible,
+ public void onActivityRestartAttempt(RunningTaskInfo taskInfo, boolean homeTaskVisible,
boolean clearedTask, boolean wasVisible) {
for (Bubble b : mBubbleData.getBubbles()) {
- if (b.getDisplayId() == task.displayId) {
+ if (taskInfo.taskId == b.getTaskId()) {
mBubbleData.setSelectedBubble(b);
mBubbleData.setExpanded(true);
return;
@@ -1647,43 +1652,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
}
}
- @Override
- public void onActivityLaunchOnSecondaryDisplayRerouted() {
- if (mStackView != null) {
- mBubbleData.setExpanded(false);
- }
- }
-
- @Override
- public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {
- if (mStackView != null && taskInfo.displayId == getExpandedDisplayId(mContext)) {
- if (mImeVisible) {
- hideCurrentInputMethod();
- } else {
- mBubbleData.setExpanded(false);
- }
- }
- }
-
- @Override
- public void onSingleTaskDisplayDrawn(int displayId) {
- if (mStackView == null) {
- return;
- }
- mStackView.showExpandedViewContents(displayId);
- }
-
- @Override
- public void onSingleTaskDisplayEmpty(int displayId) {
- final BubbleViewProvider expandedBubble = mStackView != null
- ? mStackView.getExpandedBubble()
- : null;
- int expandedId = expandedBubble != null ? expandedBubble.getDisplayId() : -1;
- if (mStackView != null && mStackView.isExpanded() && expandedId == displayId) {
- mBubbleData.setExpanded(false);
- }
- mBubbleData.notifyDisplayEmpty(displayId);
- }
}
/**
@@ -1727,6 +1695,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
}
/** PinnedStackListener that dispatches IME visibility updates to the stack. */
+ //TODO(b/170442945): Better way to do this / insets listener?
private class BubblesImeListener extends PinnedStackListenerForwarder.PinnedStackListener {
@Override
public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
@@ -1735,4 +1704,10 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
}
}
}
+
+ static BubbleEntry notifToBubbleEntry(NotificationEntry e) {
+ return new BubbleEntry(e.getSbn(), e.getRanking(), e.isClearable(),
+ e.shouldSuppressNotificationDot(), e.shouldSuppressNotificationList(),
+ e.shouldSuppressPeek());
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index bab18ec053ee..b4626f27d370 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -32,12 +32,9 @@ import android.view.View;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.systemui.R;
import com.android.systemui.bubbles.BubbleController.DismissReason;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.shared.system.SysUiStatsLog;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -52,15 +49,12 @@ import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
-import javax.inject.Inject;
-
/**
* Keeps track of active bubbles.
*/
-@SysUISingleton
public class BubbleData {
- private BubbleLoggerImpl mLogger = new BubbleLoggerImpl();
+ private BubbleLogger mLogger;
private int mCurrentUserId;
@@ -154,14 +148,14 @@ public class BubbleData {
* associated with it). This list is used to check if the summary should be hidden from the
* shade.
*
- * Key: group key of the NotificationEntry
- * Value: key of the NotificationEntry
+ * Key: group key of the notification
+ * Value: key of the notification
*/
private HashMap<String, String> mSuppressedGroupKeys = new HashMap<>();
- @Inject
- public BubbleData(Context context) {
+ public BubbleData(Context context, BubbleLogger bubbleLogger) {
mContext = context;
+ mLogger = bubbleLogger;
mBubbles = new ArrayList<>();
mOverflowBubbles = new ArrayList<>();
mPendingBubbles = new HashMap<>();
@@ -205,6 +199,11 @@ public class BubbleData {
return mSelectedBubble;
}
+ /** Return a read-only current active bubble lists. */
+ public List<Bubble> getActiveBubbles() {
+ return Collections.unmodifiableList(mBubbles);
+ }
+
public void setExpanded(boolean expanded) {
if (DEBUG_BUBBLE_DATA) {
Log.d(TAG, "setExpanded: " + expanded);
@@ -235,8 +234,8 @@ public class BubbleData {
* @param persistedBubble The bubble to use, only non-null if it's a bubble being promoted from
* the overflow that was persisted over reboot.
*/
- Bubble getOrCreateBubble(NotificationEntry entry, Bubble persistedBubble) {
- String key = entry != null ? entry.getKey() : persistedBubble.getKey();
+ public Bubble getOrCreateBubble(BubbleEntry entry, Bubble persistedBubble) {
+ String key = persistedBubble != null ? persistedBubble.getKey() : entry.getKey();
Bubble bubbleToReturn = getBubbleInStackWithKey(key);
if (bubbleToReturn == null) {
@@ -266,7 +265,7 @@ public class BubbleData {
/**
* When this method is called it is expected that all info in the bubble has completed loading.
* @see Bubble#inflate(BubbleViewInfoTask.Callback, Context,
- * BubbleStackView, BubbleIconFactory).
+ * BubbleStackView, BubbleIconFactory, boolean).
*/
void notificationEntryUpdated(Bubble bubble, boolean suppressFlyout, boolean showInShade) {
if (DEBUG_BUBBLE_DATA) {
@@ -284,7 +283,8 @@ public class BubbleData {
} else {
// Updates an existing bubble
bubble.setSuppressFlyout(suppressFlyout);
- doUpdate(bubble);
+ // If there is no flyout, we probably shouldn't show the bubble at the top
+ doUpdate(bubble, !suppressFlyout /* reorder */);
}
if (bubble.shouldAutoExpand()) {
@@ -328,7 +328,7 @@ public class BubbleData {
* Retrieves the notif entry key of the summary associated with the provided group key.
*
* @param groupKey the group to look up
- * @return the key for the {@link NotificationEntry} that is the summary of this group.
+ * @return the key for the notification that is the summary of this group.
*/
String getSummaryKey(String groupKey) {
return mSuppressedGroupKeys.get(groupKey);
@@ -349,25 +349,6 @@ public class BubbleData {
}
/**
- * Retrieves any bubbles that are part of the notification group represented by the provided
- * group key.
- */
- ArrayList<Bubble> getBubblesInGroup(@Nullable String groupKey, @NonNull
- NotificationEntryManager nem) {
- ArrayList<Bubble> bubbleChildren = new ArrayList<>();
- if (groupKey == null) {
- return bubbleChildren;
- }
- for (Bubble b : mBubbles) {
- final NotificationEntry entry = nem.getPendingOrActiveNotif(b.getKey());
- if (entry != null && groupKey.equals(entry.getSbn().getGroupKey())) {
- bubbleChildren.add(b);
- }
- }
- return bubbleChildren;
- }
-
- /**
* Removes bubbles from the given package whose shortcut are not in the provided list of valid
* shortcuts.
*/
@@ -438,12 +419,12 @@ public class BubbleData {
}
}
- private void doUpdate(Bubble bubble) {
+ private void doUpdate(Bubble bubble, boolean reorder) {
if (DEBUG_BUBBLE_DATA) {
Log.d(TAG, "doUpdate: " + bubble);
}
mStateChange.updatedBubble = bubble;
- if (!isExpanded()) {
+ if (!isExpanded() && reorder) {
int prevPos = mBubbles.indexOf(bubble);
mBubbles.remove(bubble);
mBubbles.add(0, bubble);
@@ -570,22 +551,6 @@ public class BubbleData {
dispatchPendingChanges();
}
- /**
- * Indicates that the provided display is no longer in use and should be cleaned up.
- *
- * @param displayId the id of the display to clean up.
- */
- void notifyDisplayEmpty(int displayId) {
- for (Bubble b : mBubbles) {
- if (b.getDisplayId() == displayId) {
- if (b.getExpandedView() != null) {
- b.getExpandedView().notifyDisplayEmpty();
- }
- return;
- }
- }
- }
-
private void dispatchPendingChanges() {
if (mListener != null && mStateChange.anythingChanged()) {
mListener.applyUpdate(mStateChange);
@@ -636,12 +601,12 @@ public class BubbleData {
* @param normalX Normalized x position of the stack
* @param normalY Normalized y position of the stack
*/
- void logBubbleEvent(@Nullable BubbleViewProvider provider, int action, String packageName,
+ void logBubbleEvent(@Nullable BubbleViewProvider provider, int action, String packageName,
int bubbleCount, int bubbleIndex, float normalX, float normalY) {
if (provider == null) {
mLogger.logStackUiChanged(packageName, action, bubbleCount, normalX, normalY);
} else if (provider.getKey().equals(BubbleOverflow.KEY)) {
- if (action == SysUiStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED) {
+ if (action == FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED) {
mLogger.logShowOverflow(packageName, mCurrentUserId);
}
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt
index f129d3147032..2ab9e8734bef 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt
@@ -17,6 +17,7 @@ package com.android.systemui.bubbles
import android.annotation.SuppressLint
import android.annotation.UserIdInt
+import android.content.Context
import android.content.pm.LauncherApps
import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_CACHED
import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC
@@ -26,21 +27,16 @@ import android.util.Log
import com.android.systemui.bubbles.storage.BubbleEntity
import com.android.systemui.bubbles.storage.BubblePersistentRepository
import com.android.systemui.bubbles.storage.BubbleVolatileRepository
-import com.android.systemui.dagger.SysUISingleton
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.launch
import kotlinx.coroutines.yield
-import javax.inject.Inject
-@SysUISingleton
-internal class BubbleDataRepository @Inject constructor(
- private val volatileRepository: BubbleVolatileRepository,
- private val persistentRepository: BubblePersistentRepository,
- private val launcherApps: LauncherApps
-) {
+internal class BubbleDataRepository(context: Context, private val launcherApps: LauncherApps) {
+ private val volatileRepository = BubbleVolatileRepository(launcherApps)
+ private val persistentRepository = BubblePersistentRepository(context)
private val ioScope = CoroutineScope(Dispatchers.IO)
private val uiScope = CoroutineScope(Dispatchers.Main)
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleEntry.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleEntry.java
new file mode 100644
index 000000000000..6a1302518699
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleEntry.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bubbles;
+
+import android.app.Notification.BubbleMetadata;
+import android.app.NotificationManager.Policy;
+import android.service.notification.NotificationListenerService.Ranking;
+import android.service.notification.StatusBarNotification;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * Represents a notification with needed data and flag for bubbles.
+ *
+ * @see Bubble
+ */
+public class BubbleEntry {
+
+ private StatusBarNotification mSbn;
+ private Ranking mRanking;
+
+ private boolean mIsClearable;
+ private boolean mShouldSuppressNotificationDot;
+ private boolean mShouldSuppressNotificationList;
+ private boolean mShouldSuppressPeek;
+
+ public BubbleEntry(@NonNull StatusBarNotification sbn,
+ Ranking ranking, boolean isClearable, boolean shouldSuppressNotificationDot,
+ boolean shouldSuppressNotificationList, boolean shouldSuppressPeek) {
+ mSbn = sbn;
+ mRanking = ranking;
+
+ mIsClearable = isClearable;
+ mShouldSuppressNotificationDot = shouldSuppressNotificationDot;
+ mShouldSuppressNotificationList = shouldSuppressNotificationList;
+ mShouldSuppressPeek = shouldSuppressPeek;
+ }
+
+ /** @return the {@link StatusBarNotification} for this entry. */
+ @NonNull
+ public StatusBarNotification getStatusBarNotification() {
+ return mSbn;
+ }
+
+ /** @return the {@link Ranking} for this entry. */
+ public Ranking getRanking() {
+ return mRanking;
+ }
+
+ /** @return the key in the {@link StatusBarNotification}. */
+ public String getKey() {
+ return mSbn.getKey();
+ }
+
+ /** @return the {@link BubbleMetadata} in the {@link StatusBarNotification}. */
+ @Nullable
+ public BubbleMetadata getBubbleMetadata() {
+ return getStatusBarNotification().getNotification().getBubbleMetadata();
+ }
+
+ /** @return true if this notification is clearable. */
+ public boolean isClearable() {
+ return mIsClearable;
+ }
+
+ /** @return true if {@link Policy#SUPPRESSED_EFFECT_BADGE} set for this notification. */
+ public boolean shouldSuppressNotificationDot() {
+ return mShouldSuppressNotificationDot;
+ }
+
+ /**
+ * @return true if {@link Policy#SUPPRESSED_EFFECT_NOTIFICATION_LIST}
+ * set for this notification.
+ */
+ public boolean shouldSuppressNotificationList() {
+ return mShouldSuppressNotificationList;
+ }
+
+ /** @return true if {@link Policy#SUPPRESSED_EFFECT_PEEK} set for this notification. */
+ public boolean shouldSuppressPeek() {
+ return mShouldSuppressPeek;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index ec60cbd175d0..98a2257d2daa 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -16,27 +16,19 @@
package com.android.systemui.bubbles;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
-import static android.graphics.PixelFormat.TRANSPARENT;
-import static android.view.Display.INVALID_DISPLAY;
-import static android.view.InsetsState.ITYPE_IME;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
-import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_EXPANDED_VIEW;
import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.systemui.bubbles.BubbleOverflowActivity.EXTRA_BUBBLE_CONTROLLER;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
-import android.app.ActivityManager;
import android.app.ActivityOptions;
-import android.app.ActivityTaskManager;
-import android.app.ActivityView;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
@@ -49,18 +41,13 @@ import android.graphics.Outline;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.ShapeDrawable;
-import android.hardware.display.VirtualDisplay;
-import android.os.Binder;
-import android.os.RemoteException;
+import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;
-import android.view.Gravity;
import android.view.SurfaceControl;
-import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
-import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
@@ -82,18 +69,6 @@ import java.io.PrintWriter;
*/
public class BubbleExpandedView extends LinearLayout {
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleExpandedView" : TAG_BUBBLES;
- private static final String WINDOW_TITLE = "ImeInsetsWindowWithoutContent";
-
- private enum ActivityViewStatus {
- // ActivityView is being initialized, cannot start an activity yet.
- INITIALIZING,
- // ActivityView is initialized, and ready to start an activity.
- INITIALIZED,
- // Activity runs in the ActivityView.
- ACTIVITY_STARTED,
- // ActivityView is released, so activity launching will no longer be permitted.
- RELEASED,
- }
// The triangle pointing to the expanded view
private View mPointerView;
@@ -101,16 +76,11 @@ public class BubbleExpandedView extends LinearLayout {
@Nullable private int[] mExpandedViewContainerLocation;
private AlphaOptimizedButton mSettingsIcon;
+ private TaskView mTaskView;
- // Views for expanded state
- private ActivityView mActivityView;
-
- private ActivityViewStatus mActivityViewStatus = ActivityViewStatus.INITIALIZING;
- private int mTaskId = -1;
-
- private PendingIntent mPendingIntent;
+ private int mTaskId = INVALID_TASK_ID;
- private boolean mKeyboardVisible;
+ private boolean mImeVisible;
private boolean mNeedsNewHeight;
private Point mDisplaySize;
@@ -121,131 +91,119 @@ public class BubbleExpandedView extends LinearLayout {
private int mPointerHeight;
private ShapeDrawable mPointerDrawable;
private int mExpandedViewPadding;
-
+ private float mCornerRadius = 0f;
@Nullable private Bubble mBubble;
+ private PendingIntent mPendingIntent;
private boolean mIsOverflow;
- private BubbleController mBubbleController = Dependency.get(BubbleController.class);
+ private Bubbles mBubbles = Dependency.get(Bubbles.class);
private WindowManager mWindowManager;
- private ActivityManager mActivityManager;
-
private BubbleStackView mStackView;
- private View mVirtualImeView;
- private WindowManager mVirtualDisplayWindowManager;
- private boolean mImeShowing = false;
- private float mCornerRadius = 0f;
/**
* Container for the ActivityView that has a solid, round-rect background that shows if the
* ActivityView hasn't loaded.
*/
- private FrameLayout mActivityViewContainer = new FrameLayout(getContext());
+ private final FrameLayout mExpandedViewContainer = new FrameLayout(getContext());
- /** The SurfaceView that the ActivityView draws to. */
- @Nullable private SurfaceView mActivitySurface;
+ private final TaskView.Listener mTaskViewListener = new TaskView.Listener() {
+ private boolean mInitialized = false;
+ private boolean mDestroyed = false;
- private ActivityView.StateCallback mStateCallback = new ActivityView.StateCallback() {
@Override
- public void onActivityViewReady(ActivityView view) {
+ public void onInitialized() {
if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onActivityViewReady: mActivityViewStatus=" + mActivityViewStatus
+ Log.d(TAG, "onActivityViewReady: destroyed=" + mDestroyed
+ + " initialized=" + mInitialized
+ " bubble=" + getBubbleKey());
}
- switch (mActivityViewStatus) {
- case INITIALIZING:
- case INITIALIZED:
- // Custom options so there is no activity transition animation
- ActivityOptions options = ActivityOptions.makeCustomAnimation(getContext(),
- 0 /* enterResId */, 0 /* exitResId */);
- options.setTaskAlwaysOnTop(true);
- // Soptions.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
- // Post to keep the lifecycle normal
- post(() -> {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onActivityViewReady: calling startActivity, "
- + "bubble=" + getBubbleKey());
- }
- if (mActivityView == null) {
- mBubbleController.removeBubble(getBubbleKey(),
- BubbleController.DISMISS_INVALID_INTENT);
- return;
- }
- try {
- if (!mIsOverflow && mBubble.hasMetadataShortcutId()
- && mBubble.getShortcutInfo() != null) {
- options.setApplyActivityFlagsForBubbles(true);
- mActivityView.startShortcutActivity(mBubble.getShortcutInfo(),
- options, null /* sourceBounds */);
- } else {
- Intent fillInIntent = new Intent();
- // Apply flags to make behaviour match documentLaunchMode=always.
- fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
- fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
- if (mBubble != null) {
- mBubble.setIntentActive();
- }
- mActivityView.startActivity(mPendingIntent, fillInIntent, options);
- }
- } catch (RuntimeException e) {
- // If there's a runtime exception here then there's something
- // wrong with the intent, we can't really recover / try to populate
- // the bubble again so we'll just remove it.
- Log.w(TAG, "Exception while displaying bubble: " + getBubbleKey()
- + ", " + e.getMessage() + "; removing bubble");
- mBubbleController.removeBubble(getBubbleKey(),
- BubbleController.DISMISS_INVALID_INTENT);
- }
- });
- mActivityViewStatus = ActivityViewStatus.ACTIVITY_STARTED;
- break;
- case ACTIVITY_STARTED:
- post(() -> mActivityManager.moveTaskToFront(mTaskId, 0));
- break;
+
+ if (mDestroyed || mInitialized) {
+ return;
}
+ // Custom options so there is no activity transition animation
+ ActivityOptions options = ActivityOptions.makeCustomAnimation(getContext(),
+ 0 /* enterResId */, 0 /* exitResId */);
+
+ // TODO: I notice inconsistencies in lifecycle
+ // Post to keep the lifecycle normal
+ post(() -> {
+ if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+ Log.d(TAG, "onActivityViewReady: calling startActivity, bubble="
+ + getBubbleKey());
+ }
+ try {
+ if (!mIsOverflow && mBubble.hasMetadataShortcutId()) {
+ mTaskView.startShortcutActivity(mBubble.getShortcutInfo(),
+ options, null /* sourceBounds */);
+ } else {
+ Intent fillInIntent = new Intent();
+ // Apply flags to make behaviour match documentLaunchMode=always.
+ fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
+ fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ if (mBubble != null) {
+ mBubble.setIntentActive();
+ }
+ mTaskView.startActivity(mPendingIntent, fillInIntent, options);
+ }
+ } catch (RuntimeException e) {
+ // If there's a runtime exception here then there's something
+ // wrong with the intent, we can't really recover / try to populate
+ // the bubble again so we'll just remove it.
+ Log.w(TAG, "Exception while displaying bubble: " + getBubbleKey()
+ + ", " + e.getMessage() + "; removing bubble");
+ mBubbles.removeBubble(getBubbleKey(),
+ BubbleController.DISMISS_INVALID_INTENT);
+ }
+ });
+ mInitialized = true;
}
@Override
- public void onActivityViewDestroyed(ActivityView view) {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onActivityViewDestroyed: mActivityViewStatus=" + mActivityViewStatus
- + " bubble=" + getBubbleKey());
- }
- mActivityViewStatus = ActivityViewStatus.RELEASED;
+ public void onReleased() {
+ mDestroyed = true;
}
@Override
- public void onTaskCreated(int taskId, ComponentName componentName) {
+ public void onTaskCreated(int taskId, ComponentName name) {
if (DEBUG_BUBBLE_EXPANDED_VIEW) {
Log.d(TAG, "onTaskCreated: taskId=" + taskId
+ " bubble=" + getBubbleKey());
}
- // Since Bubble ActivityView applies singleTaskDisplay this is
- // guaranteed to only be called once per ActivityView. The taskId is
- // saved to use for removeTask, preventing appearance in recent tasks.
+ // The taskId is saved to use for removeTask, preventing appearance in recent tasks.
mTaskId = taskId;
+
+ // With the task org, the taskAppeared callback will only happen once the task has
+ // already drawn
+ setContentVisibility(true);
+ }
+
+ @Override
+ public void onTaskVisibilityChanged(int taskId, boolean visible) {
+ setContentVisibility(visible);
}
- /**
- * This is only called for tasks on this ActivityView, which is also set to
- * single-task mode -- meaning never more than one task on this display. If a task
- * is being removed, it's the top Activity finishing and this bubble should
- * be removed or collapsed.
- */
@Override
public void onTaskRemovalStarted(int taskId) {
if (DEBUG_BUBBLE_EXPANDED_VIEW) {
Log.d(TAG, "onTaskRemovalStarted: taskId=" + taskId
- + " mActivityViewStatus=" + mActivityViewStatus
+ " bubble=" + getBubbleKey());
}
if (mBubble != null) {
// Must post because this is called from a binder thread.
- post(() -> mBubbleController.removeBubble(mBubble.getKey(),
+ post(() -> mBubbles.removeBubble(mBubble.getKey(),
BubbleController.DISMISS_TASK_FINISHED));
}
}
+
+ @Override
+ public void onBackPressedOnTaskRoot(int taskId) {
+ if (mTaskId == taskId && mStackView.isExpanded()) {
+ mBubbles.collapseStack();
+ }
+ }
};
public BubbleExpandedView(Context context) {
@@ -264,7 +222,6 @@ public class BubbleExpandedView extends LinearLayout {
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
updateDimensions();
- mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
}
void updateDimensions() {
@@ -282,9 +239,6 @@ public class BubbleExpandedView extends LinearLayout {
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onFinishInflate: bubble=" + getBubbleKey());
- }
Resources res = getResources();
mPointerView = findViewById(R.id.pointer_view);
@@ -299,35 +253,21 @@ public class BubbleExpandedView extends LinearLayout {
R.dimen.bubble_manage_button_height);
mSettingsIcon = findViewById(R.id.settings_button);
- mActivityView = new ActivityView.Builder(mContext)
- .setSingleInstance(true)
- .setDisableSurfaceViewBackgroundLayer(true)
- .setUseTrustedDisplay(true)
- .build();
-
+ mTaskView = new TaskView(mContext, mBubbles.getTaskManager());
// Set ActivityView's alpha value as zero, since there is no view content to be shown.
setContentVisibility(false);
- mActivityViewContainer.setOutlineProvider(new ViewOutlineProvider() {
+ mExpandedViewContainer.setOutlineProvider(new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mCornerRadius);
}
});
- mActivityViewContainer.setClipToOutline(true);
- mActivityViewContainer.addView(mActivityView);
- mActivityViewContainer.setLayoutParams(
+ mExpandedViewContainer.setClipToOutline(true);
+ mExpandedViewContainer.addView(mTaskView);
+ mExpandedViewContainer.setLayoutParams(
new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
- addView(mActivityViewContainer);
-
- if (mActivityView != null
- && mActivityView.getChildCount() > 0
- && mActivityView.getChildAt(0) instanceof SurfaceView) {
- // Retrieve the surface from the ActivityView so we can screenshot it and change its
- // z-ordering. This should always be possible, since ActivityView's constructor adds the
- // SurfaceView as its first child.
- mActivitySurface = (SurfaceView) mActivityView.getChildAt(0);
- }
+ addView(mExpandedViewContainer);
// Expanded stack layout, top to bottom:
// Expanded view container
@@ -335,33 +275,22 @@ public class BubbleExpandedView extends LinearLayout {
// ==> expanded view
// ==> activity view
// ==> manage button
- bringChildToFront(mActivityView);
+ bringChildToFront(mTaskView);
bringChildToFront(mSettingsIcon);
+ mTaskView.setListener(mTaskViewListener);
applyThemeAttrs();
- setOnApplyWindowInsetsListener((View view, WindowInsets insets) -> {
- // Keep track of IME displaying because we should not make any adjustments that might
- // cause a config change while the IME is displayed otherwise it'll loose focus.
- final int keyboardHeight = insets.getSystemWindowInsetBottom()
- - insets.getStableInsetBottom();
- mKeyboardVisible = keyboardHeight != 0;
- if (!mKeyboardVisible && mNeedsNewHeight) {
- updateHeight();
- }
- return view.onApplyWindowInsets(insets);
- });
-
mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding);
setPadding(mExpandedViewPadding, mExpandedViewPadding, mExpandedViewPadding,
mExpandedViewPadding);
setOnTouchListener((view, motionEvent) -> {
- if (!usingActivityView()) {
+ if (mTaskView == null) {
return false;
}
final Rect avBounds = new Rect();
- mActivityView.getBoundsOnScreen(avBounds);
+ mTaskView.getBoundsOnScreen(avBounds);
// Consume and ignore events on the expanded view padding that are within the
// ActivityView's vertical bounds. These events are part of a back gesture, and so they
@@ -387,51 +316,58 @@ public class BubbleExpandedView extends LinearLayout {
}
/**
- * Asks the ActivityView's surface to draw on top of all other views in the window. This is
- * useful for ordering surfaces during animations, but should otherwise be set to false so that
- * bubbles and menus can draw over the ActivityView.
+ * Sets whether the surface displaying app content should sit on top. This is useful for
+ * ordering surfaces during animations. When content is drawn on top of the app (e.g. bubble
+ * being dragged out, the manage menu) this is set to false, otherwise it should be true.
*/
void setSurfaceZOrderedOnTop(boolean onTop) {
- if (mActivitySurface == null) {
+ if (mTaskView == null) {
return;
}
+ mTaskView.setZOrderedOnTop(onTop, true /* allowDynamicChange */);
+ }
- mActivitySurface.setZOrderedOnTop(onTop, true);
+ void setImeVisible(boolean visible) {
+ mImeVisible = visible;
+ if (!mImeVisible && mNeedsNewHeight) {
+ updateHeight();
+ }
}
- /** Return a GraphicBuffer with the contents of the ActivityView's underlying surface. */
+ /** Return a GraphicBuffer with the contents of the task view surface. */
@Nullable
SurfaceControl.ScreenshotHardwareBuffer snapshotActivitySurface() {
- if (mActivitySurface == null) {
+ if (mTaskView == null) {
return null;
}
-
return SurfaceControl.captureLayers(
- mActivitySurface.getSurfaceControl(),
- new Rect(0, 0, mActivityView.getWidth(), mActivityView.getHeight()),
+ mTaskView.getSurfaceControl(),
+ new Rect(0, 0, mTaskView.getWidth(), mTaskView.getHeight()),
1 /* scale */);
}
- int[] getActivityViewLocationOnScreen() {
- if (mActivityView != null) {
- return mActivityView.getLocationOnScreen();
+ int[] getTaskViewLocationOnScreen() {
+ if (mTaskView != null) {
+ return mTaskView.getLocationOnScreen();
} else {
return new int[]{0, 0};
}
}
+ // TODO: Could listener be passed when we pass StackView / can we avoid setting this like this
void setManageClickListener(OnClickListener manageClickListener) {
- findViewById(R.id.settings_button).setOnClickListener(manageClickListener);
+ mSettingsIcon.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.
+ * Updates the obscured touchable region for the task surface. This calls onLocationChanged,
+ * which results in a call to {@link BubbleStackView#subtractObscuredTouchableRegion}. This is
+ * useful if a view has been added or removed from on top of the ActivityView, such as the
+ * manage menu.
*/
void updateObscuredTouchableRegion() {
- if (mActivityView != null) {
- mActivityView.onLocationChanged();
+ if (mTaskView != null) {
+ mTaskView.onLocationChanged();
}
}
@@ -440,12 +376,12 @@ public class BubbleExpandedView extends LinearLayout {
android.R.attr.dialogCornerRadius,
android.R.attr.colorBackgroundFloating});
mCornerRadius = ta.getDimensionPixelSize(0, 0);
- mActivityViewContainer.setBackgroundColor(ta.getColor(1, Color.WHITE));
+ mExpandedViewContainer.setBackgroundColor(ta.getColor(1, Color.WHITE));
ta.recycle();
- if (mActivityView != null && ScreenDecorationsUtils.supportsRoundedCornersOnWindows(
+ if (mTaskView != null && ScreenDecorationsUtils.supportsRoundedCornersOnWindows(
mContext.getResources())) {
- mActivityView.setCornerRadius(mCornerRadius);
+ mTaskView.setCornerRadius(mCornerRadius);
}
final int mode =
@@ -464,11 +400,8 @@ public class BubbleExpandedView extends LinearLayout {
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
- mKeyboardVisible = false;
+ mImeVisible = false;
mNeedsNewHeight = false;
- if (mActivityView != null) {
- setImeWindowToDisplay(0, 0);
- }
if (DEBUG_BUBBLE_EXPANDED_VIEW) {
Log.d(TAG, "onDetachedFromWindow: bubble=" + getBubbleKey());
}
@@ -490,84 +423,23 @@ public class BubbleExpandedView extends LinearLayout {
final float alpha = visibility ? 1f : 0f;
mPointerView.setAlpha(alpha);
-
- if (mActivityView != null && alpha != mActivityView.getAlpha()) {
- mActivityView.setAlpha(alpha);
- mActivityView.bringToFront();
+ if (mTaskView == null) {
+ return;
+ }
+ if (alpha != mTaskView.getAlpha()) {
+ mTaskView.setAlpha(alpha);
}
}
- @Nullable ActivityView getActivityView() {
- return mActivityView;
+ @Nullable
+ View getTaskView() {
+ return mTaskView;
}
int getTaskId() {
return mTaskId;
}
- /**
- * Called by {@link BubbleStackView} when the insets for the expanded state should be updated.
- * This should be done post-move and post-animation.
- */
- void updateInsets(WindowInsets insets) {
- if (usingActivityView()) {
- int[] screenLoc = mActivityView.getLocationOnScreen();
- final int activityViewBottom = screenLoc[1] + mActivityView.getHeight();
- final int keyboardTop = mDisplaySize.y - Math.max(insets.getSystemWindowInsetBottom(),
- insets.getDisplayCutout() != null
- ? insets.getDisplayCutout().getSafeInsetBottom()
- : 0);
- setImeWindowToDisplay(getWidth(), Math.max(activityViewBottom - keyboardTop, 0));
- }
- }
-
- private void setImeWindowToDisplay(int w, int h) {
- if (getVirtualDisplayId() == INVALID_DISPLAY) {
- return;
- }
- if (h == 0 || w == 0) {
- if (mImeShowing) {
- mVirtualImeView.setVisibility(GONE);
- mImeShowing = false;
- }
- return;
- }
- final Context virtualDisplayContext = mContext.createDisplayContext(
- getVirtualDisplay().getDisplay());
-
- if (mVirtualDisplayWindowManager == null) {
- mVirtualDisplayWindowManager =
- (WindowManager) virtualDisplayContext.getSystemService(Context.WINDOW_SERVICE);
- }
- if (mVirtualImeView == null) {
- mVirtualImeView = new View(virtualDisplayContext);
- mVirtualImeView.setVisibility(VISIBLE);
- mVirtualDisplayWindowManager.addView(mVirtualImeView,
- getVirtualImeViewAttrs(w, h));
- } else {
- mVirtualDisplayWindowManager.updateViewLayout(mVirtualImeView,
- getVirtualImeViewAttrs(w, h));
- mVirtualImeView.setVisibility(VISIBLE);
- }
-
- mImeShowing = true;
- }
-
- private WindowManager.LayoutParams getVirtualImeViewAttrs(int w, int h) {
- // To use TYPE_NAVIGATION_BAR_PANEL instead of TYPE_IME_BAR to bypass the IME window type
- // token check when adding the window.
- final WindowManager.LayoutParams attrs =
- new WindowManager.LayoutParams(w, h, TYPE_NAVIGATION_BAR_PANEL,
- FLAG_LAYOUT_NO_LIMITS | FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCHABLE,
- TRANSPARENT);
- attrs.gravity = Gravity.BOTTOM;
- attrs.setTitle(WINDOW_TITLE);
- attrs.token = new Binder();
- attrs.providesInsetsTypes = new int[]{ITYPE_IME};
- attrs.alpha = 0.0f;
- return attrs;
- }
-
void setStackView(BubbleStackView stackView) {
mStackView = stackView;
}
@@ -576,7 +448,10 @@ public class BubbleExpandedView extends LinearLayout {
mIsOverflow = overflow;
Intent target = new Intent(mContext, BubbleOverflowActivity.class);
- mPendingIntent = PendingIntent.getActivity(mContext, /* requestCode */ 0,
+ Bundle extras = new Bundle();
+ extras.putBinder(EXTRA_BUBBLE_CONTROLLER, ObjectWrapper.wrap(mBubbles));
+ target.putExtras(extras);
+ mPendingIntent = PendingIntent.getActivity(mContext, 0 /* requestCode */,
target, PendingIntent.FLAG_UPDATE_CURRENT);
mSettingsIcon.setVisibility(GONE);
}
@@ -614,7 +489,7 @@ public class BubbleExpandedView extends LinearLayout {
mPendingIntent = mBubble.getBubbleIntent();
if (mPendingIntent != null || mBubble.hasMetadataShortcutId()) {
setContentVisibility(false);
- mActivityView.setVisibility(VISIBLE);
+ mTaskView.setVisibility(VISIBLE);
}
}
applyThemeAttrs();
@@ -624,59 +499,42 @@ public class BubbleExpandedView extends LinearLayout {
}
}
+ /**
+ * Bubbles are backed by a pending intent or a shortcut, once the activity is
+ * started we never change it / restart it on notification updates -- unless the bubbles'
+ * backing data switches.
+ *
+ * This indicates if the new bubble is backed by a different data source than what was
+ * previously shown here (e.g. previously a pending intent & now a shortcut).
+ *
+ * @param newBubble the bubble this view is being updated with.
+ * @return true if the backing content has changed.
+ */
private boolean didBackingContentChange(Bubble newBubble) {
boolean prevWasIntentBased = mBubble != null && mPendingIntent != null;
boolean newIsIntentBased = newBubble.getBubbleIntent() != null;
return prevWasIntentBased != newIsIntentBased;
}
- /**
- * Lets activity view know it should be shown / populated with activity content.
- */
- void populateExpandedView() {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "populateExpandedView: "
- + "bubble=" + getBubbleKey());
- }
-
- if (usingActivityView()) {
- mActivityView.setCallback(mStateCallback);
- } else {
- Log.e(TAG, "Cannot populate expanded view.");
- }
- }
-
- boolean performBackPressIfNeeded() {
- if (!usingActivityView()) {
- return false;
- }
- mActivityView.performBackPress();
- return true;
- }
-
void updateHeight() {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "updateHeight: bubble=" + getBubbleKey());
- }
-
if (mExpandedViewContainerLocation == null) {
return;
}
- if (usingActivityView()) {
+ if (mBubble != null || mIsOverflow) {
float desiredHeight = mOverflowHeight;
if (!mIsOverflow) {
desiredHeight = Math.max(mBubble.getDesiredHeight(mContext), mMinHeight);
}
float height = Math.min(desiredHeight, getMaxExpandedHeight());
- height = Math.max(height, mMinHeight);
- ViewGroup.LayoutParams lp = mActivityView.getLayoutParams();
+ height = Math.max(height, mIsOverflow ? mOverflowHeight : mMinHeight);
+ FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mTaskView.getLayoutParams();
mNeedsNewHeight = lp.height != height;
- if (!mKeyboardVisible) {
- // If the keyboard is visible... don't adjust the height because that will cause
- // a configuration change and the keyboard will be lost.
+ if (!mImeVisible) {
+ // If the ime is visible... don't adjust the height because that will cause
+ // a configuration change and the ime will be lost.
lp.height = (int) height;
- mActivityView.setLayoutParams(lp);
+ mTaskView.setLayoutParams(lp);
mNeedsNewHeight = false;
}
if (DEBUG_BUBBLE_EXPANDED_VIEW) {
@@ -689,12 +547,15 @@ public class BubbleExpandedView extends LinearLayout {
private int getMaxExpandedHeight() {
mWindowManager.getDefaultDisplay().getRealSize(mDisplaySize);
+ int expandedContainerY = mExpandedViewContainerLocation != null
+ ? mExpandedViewContainerLocation[1]
+ : 0;
int bottomInset = getRootWindowInsets() != null
? getRootWindowInsets().getStableInsetBottom()
: 0;
return mDisplaySize.y
- - mExpandedViewContainerLocation[1]
+ - expandedContainerY
- getPaddingTop()
- getPaddingBottom()
- mSettingsIconHeight
@@ -714,14 +575,12 @@ public class BubbleExpandedView extends LinearLayout {
Log.d(TAG, "updateView: bubble="
+ getBubbleKey());
}
-
mExpandedViewContainerLocation = containerLocationOnScreen;
-
- if (usingActivityView()
- && mActivityView.getVisibility() == VISIBLE
- && mActivityView.isAttachedToWindow()) {
- mActivityView.onLocationChanged();
+ if (mTaskView != null
+ && mTaskView.getVisibility() == VISIBLE
+ && mTaskView.isAttachedToWindow()) {
updateHeight();
+ mTaskView.onLocationChanged();
}
}
@@ -744,65 +603,19 @@ public class BubbleExpandedView extends LinearLayout {
}
/**
- * Removes and releases an ActivityView if one was previously created for this bubble.
+ * Cleans up anything related to the task and TaskView.
*/
public void cleanUpExpandedState() {
if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "cleanUpExpandedState: mActivityViewStatus=" + mActivityViewStatus
- + ", bubble=" + getBubbleKey());
- }
- if (mActivityView == null) {
- return;
- }
- mActivityView.release();
- if (mTaskId != -1) {
- try {
- ActivityTaskManager.getService().removeTask(mTaskId);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to remove taskId " + mTaskId);
- }
- mTaskId = -1;
- }
- removeView(mActivityView);
-
- mActivityView = null;
- }
-
- /**
- * Called when the last task is removed from a {@link android.hardware.display.VirtualDisplay}
- * which {@link ActivityView} uses.
- */
- void notifyDisplayEmpty() {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "notifyDisplayEmpty: bubble="
- + getBubbleKey()
- + " mActivityViewStatus=" + mActivityViewStatus);
+ Log.d(TAG, "cleanUpExpandedState: bubble=" + getBubbleKey() + " task=" + mTaskId);
}
- if (mActivityViewStatus == ActivityViewStatus.ACTIVITY_STARTED) {
- mActivityViewStatus = ActivityViewStatus.INITIALIZED;
+ if (mTaskView != null) {
+ mTaskView.release();
}
- }
-
- private boolean usingActivityView() {
- return (mPendingIntent != null || mBubble.hasMetadataShortcutId())
- && mActivityView != null;
- }
-
- /**
- * @return the display id of the virtual display.
- */
- public int getVirtualDisplayId() {
- if (usingActivityView()) {
- return mActivityView.getVirtualDisplayId();
- }
- return INVALID_DISPLAY;
- }
-
- private VirtualDisplay getVirtualDisplay() {
- if (usingActivityView()) {
- return mActivityView.getVirtualDisplay();
+ if (mTaskView != null) {
+ removeView(mTaskView);
+ mTaskView = null;
}
- return null;
}
/**
@@ -812,7 +625,6 @@ public class BubbleExpandedView extends LinearLayout {
@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
pw.print("BubbleExpandedView");
pw.print(" taskId: "); pw.println(mTaskId);
- pw.print(" activityViewStatus: "); pw.println(mActivityViewStatus);
pw.print(" stackView: "); pw.println(mStackView);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java
index 1fa3aaae5e61..009114ffa0be 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java
@@ -18,6 +18,8 @@ package com.android.systemui.bubbles;
import static android.graphics.Paint.ANTI_ALIAS_FLAG;
import static android.graphics.Paint.FILTER_BITMAP_FLAG;
+import static com.android.systemui.Interpolators.ALPHA_IN;
+import static com.android.systemui.Interpolators.ALPHA_OUT;
import android.animation.ArgbEvaluator;
import android.content.Context;
@@ -34,6 +36,7 @@ import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.ShapeDrawable;
import android.text.TextUtils;
+import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -55,6 +58,11 @@ public class BubbleFlyoutView extends FrameLayout {
/** Max width of the flyout, in terms of percent of the screen width. */
private static final float FLYOUT_MAX_WIDTH_PERCENT = .6f;
+ /** Translation Y of fade animation. */
+ private static final float FLYOUT_FADE_Y = 40f;
+
+ private static final long FLYOUT_FADE_DURATION = 200L;
+
private final int mFlyoutPadding;
private final int mFlyoutSpaceFromBubble;
private final int mPointerSize;
@@ -103,6 +111,9 @@ public class BubbleFlyoutView extends FrameLayout {
/** The bounds of the flyout background, kept up to date as it transitions to the 'new' dot. */
private final RectF mBgRect = new RectF();
+ /** The y position of the flyout, relative to the top of the screen. */
+ private float mFlyoutY = 0f;
+
/**
* Percent progress in the transition from flyout to 'new' dot. These two values are the inverse
* of each other (if we're 40% transitioned to the dot, we're 60% flyout), but it makes the code
@@ -212,18 +223,41 @@ public class BubbleFlyoutView extends FrameLayout {
super.onDraw(canvas);
}
- /** Configures the flyout, collapsed into to dot form. */
- void setupFlyoutStartingAsDot(
- Bubble.FlyoutMessage flyoutMessage,
- PointF stackPos,
- float parentWidth,
- boolean arrowPointingLeft,
- int dotColor,
- @Nullable Runnable onLayoutComplete,
- @Nullable Runnable onHide,
- float[] dotCenter,
- boolean hideDot) {
+ void updateFontSize(float fontScale) {
+ final float fontSize = mContext.getResources()
+ .getDimensionPixelSize(com.android.internal.R.dimen.text_size_body_2_material);
+ final float newFontSize = fontSize * fontScale;
+ mMessageText.setTextSize(TypedValue.COMPLEX_UNIT_PX, newFontSize);
+ mSenderText.setTextSize(TypedValue.COMPLEX_UNIT_PX, newFontSize);
+ }
+
+ /*
+ * Fade animation for consecutive flyouts.
+ */
+ void animateUpdate(Bubble.FlyoutMessage flyoutMessage, float parentWidth, float stackY) {
+ fade(false /* in */);
+ updateFlyoutMessage(flyoutMessage, parentWidth);
+ // Wait for TextViews to layout with updated height.
+ post(() -> {
+ mFlyoutY = stackY + (mBubbleSize - mFlyoutTextContainer.getHeight()) / 2f;
+ fade(true /* in */);
+ });
+ }
+ private void fade(boolean in) {
+ setAlpha(in ? 0f : 1f);
+ setTranslationY(in ? mFlyoutY : mFlyoutY + FLYOUT_FADE_Y);
+ animate()
+ .alpha(in ? 1f : 0f)
+ .setDuration(FLYOUT_FADE_DURATION)
+ .setInterpolator(in ? ALPHA_IN : ALPHA_OUT);
+ animate()
+ .translationY(in ? mFlyoutY : mFlyoutY - FLYOUT_FADE_Y)
+ .setDuration(FLYOUT_FADE_DURATION)
+ .setInterpolator(in ? ALPHA_IN : ALPHA_OUT);
+ }
+
+ private void updateFlyoutMessage(Bubble.FlyoutMessage flyoutMessage, float parentWidth) {
final Drawable senderAvatar = flyoutMessage.senderAvatar;
if (senderAvatar != null && flyoutMessage.isGroupChat) {
mSenderAvatar.setVisibility(VISIBLE);
@@ -247,6 +281,27 @@ public class BubbleFlyoutView extends FrameLayout {
mSenderText.setVisibility(GONE);
}
+ // Set the flyout TextView's max width in terms of percent, and then subtract out the
+ // padding so that the entire flyout view will be the desired width (rather than the
+ // TextView being the desired width + extra padding).
+ mMessageText.setMaxWidth(maxTextViewWidth);
+ mMessageText.setText(flyoutMessage.message);
+ }
+
+ /** Configures the flyout, collapsed into dot form. */
+ void setupFlyoutStartingAsDot(
+ Bubble.FlyoutMessage flyoutMessage,
+ PointF stackPos,
+ float parentWidth,
+ boolean arrowPointingLeft,
+ int dotColor,
+ @Nullable Runnable onLayoutComplete,
+ @Nullable Runnable onHide,
+ float[] dotCenter,
+ boolean hideDot) {
+
+ updateFlyoutMessage(flyoutMessage, parentWidth);
+
mArrowPointingLeft = arrowPointingLeft;
mDotColor = dotColor;
mOnHide = onHide;
@@ -254,24 +309,12 @@ public class BubbleFlyoutView extends FrameLayout {
setCollapsePercent(1f);
- // Set the flyout TextView's max width in terms of percent, and then subtract out the
- // padding so that the entire flyout view will be the desired width (rather than the
- // TextView being the desired width + extra padding).
- mMessageText.setMaxWidth(maxTextViewWidth);
- mMessageText.setText(flyoutMessage.message);
-
- // Wait for the TextView to lay out so we know its line count.
+ // Wait for TextViews to layout with updated height.
post(() -> {
- float restingTranslationY;
- // Multi line flyouts get top-aligned to the bubble.
- if (mMessageText.getLineCount() > 1) {
- restingTranslationY = stackPos.y + mBubbleIconTopPadding;
- } else {
- // Single line flyouts are vertically centered with respect to the bubble.
- restingTranslationY =
- stackPos.y + (mBubbleSize - mFlyoutTextContainer.getHeight()) / 2f;
- }
- setTranslationY(restingTranslationY);
+ // Flyout is vertically centered with respect to the bubble.
+ mFlyoutY =
+ stackPos.y + (mBubbleSize - mFlyoutTextContainer.getHeight()) / 2f;
+ setTranslationY(mFlyoutY);
// Calculate the translation required to position the flyout next to the bubble stack,
// with the desired padding.
@@ -291,7 +334,7 @@ public class BubbleFlyoutView extends FrameLayout {
final float dotPositionY = stackPos.y + mDotCenter[1] - adjustmentForScaleAway;
final float distanceFromFlyoutLeftToDotCenterX = mRestingTranslationX - dotPositionX;
- final float distanceFromLayoutTopToDotCenterY = restingTranslationY - dotPositionY;
+ final float distanceFromLayoutTopToDotCenterY = mFlyoutY - dotPositionY;
mTranslationXWhenDot = -distanceFromFlyoutLeftToDotCenterX;
mTranslationYWhenDot = -distanceFromLayoutTopToDotCenterY;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLogger.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLogger.java
index 86ba8c5c7192..48c809d1b0a7 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLogger.java
@@ -19,17 +19,22 @@ package com.android.systemui.bubbles;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
+import com.android.internal.util.FrameworkStatsLog;
/**
- * Interface for handling bubble-specific logging.
+ * Implementation of UiEventLogger for logging bubble UI events.
+ *
+ * See UiEventReported atom in atoms.proto for more context.
*/
-public interface BubbleLogger extends UiEventLogger {
+public class BubbleLogger {
+
+ private final UiEventLogger mUiEventLogger;
/**
* Bubble UI event.
*/
@VisibleForTesting
- enum Event implements UiEventLogger.UiEventEnum {
+ public enum Event implements UiEventLogger.UiEventEnum {
@UiEvent(doc = "User dismissed the bubble via gesture, add bubble to overflow.")
BUBBLE_OVERFLOW_ADD_USER_GESTURE(483),
@@ -70,23 +75,80 @@ public interface BubbleLogger extends UiEventLogger {
}
}
+ public BubbleLogger(UiEventLogger uiEventLogger) {
+ mUiEventLogger = uiEventLogger;
+ }
+
/**
* @param b Bubble involved in this UI event
* @param e UI event
*/
- void log(Bubble b, UiEventEnum e);
+ public void log(Bubble b, UiEventLogger.UiEventEnum e) {
+ mUiEventLogger.logWithInstanceId(e, b.getAppUid(), b.getPackageName(), b.getInstanceId());
+ }
/**
- *
* @param b Bubble removed from overflow
- * @param r Reason that bubble was removed from overflow
+ * @param r Reason that bubble was removed
*/
- void logOverflowRemove(Bubble b, @BubbleController.DismissReason int r);
+ public void logOverflowRemove(Bubble b, @BubbleController.DismissReason int r) {
+ if (r == BubbleController.DISMISS_NOTIF_CANCEL) {
+ log(b, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_CANCEL);
+ } else if (r == BubbleController.DISMISS_GROUP_CANCELLED) {
+ log(b, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_GROUP_CANCEL);
+ } else if (r == BubbleController.DISMISS_NO_LONGER_BUBBLE) {
+ log(b, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_NO_LONGER_BUBBLE);
+ } else if (r == BubbleController.DISMISS_BLOCKED) {
+ log(b, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_BLOCKED);
+ }
+ }
/**
- *
* @param b Bubble added to overflow
* @param r Reason that bubble was added to overflow
*/
- void logOverflowAdd(Bubble b, @BubbleController.DismissReason int r);
-}
+ public void logOverflowAdd(Bubble b, @BubbleController.DismissReason int r) {
+ if (r == BubbleController.DISMISS_AGED) {
+ log(b, Event.BUBBLE_OVERFLOW_ADD_AGED);
+ } else if (r == BubbleController.DISMISS_USER_GESTURE) {
+ log(b, Event.BUBBLE_OVERFLOW_ADD_USER_GESTURE);
+ }
+ }
+
+ void logStackUiChanged(String packageName, int action, int bubbleCount, float normalX,
+ float normalY) {
+ FrameworkStatsLog.write(FrameworkStatsLog.BUBBLE_UI_CHANGED,
+ packageName,
+ null /* notification channel */,
+ 0 /* notification ID */,
+ 0 /* bubble position */,
+ bubbleCount,
+ action,
+ normalX,
+ normalY,
+ false /* unread bubble */,
+ false /* on-going bubble */,
+ false /* isAppForeground (unused) */);
+ }
+
+ void logShowOverflow(String packageName, int currentUserId) {
+ mUiEventLogger.log(BubbleLogger.Event.BUBBLE_OVERFLOW_SELECTED, currentUserId,
+ packageName);
+ }
+
+ void logBubbleUiChanged(Bubble bubble, String packageName, int action, int bubbleCount,
+ float normalX, float normalY, int index) {
+ FrameworkStatsLog.write(FrameworkStatsLog.BUBBLE_UI_CHANGED,
+ packageName,
+ bubble.getChannelId() /* notification channel */,
+ bubble.getNotificationId() /* notification ID */,
+ index,
+ bubbleCount,
+ action,
+ normalX,
+ normalY,
+ bubble.showInShade() /* isUnread */,
+ false /* isOngoing (unused) */,
+ false /* isAppForeground (unused) */);
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLoggerImpl.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLoggerImpl.java
deleted file mode 100644
index ea612af3d4a4..000000000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLoggerImpl.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.bubbles;
-
-import android.os.UserHandle;
-
-import com.android.internal.logging.UiEventLoggerImpl;
-import com.android.systemui.shared.system.SysUiStatsLog;
-
-/**
- * Implementation of UiEventLogger for logging bubble UI events.
- *
- * See UiEventReported atom in atoms.proto for more context.
- */
-public class BubbleLoggerImpl extends UiEventLoggerImpl implements BubbleLogger {
-
- /**
- * @param b Bubble involved in this UI event
- * @param e UI event
- */
- public void log(Bubble b, UiEventEnum e) {
- logWithInstanceId(e, b.getAppUid(), b.getPackageName(), b.getInstanceId());
- }
-
- /**
- * @param b Bubble removed from overflow
- * @param r Reason that bubble was removed
- */
- public void logOverflowRemove(Bubble b, @BubbleController.DismissReason int r) {
- if (r == BubbleController.DISMISS_NOTIF_CANCEL) {
- log(b, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_CANCEL);
- } else if (r == BubbleController.DISMISS_GROUP_CANCELLED) {
- log(b, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_GROUP_CANCEL);
- } else if (r == BubbleController.DISMISS_NO_LONGER_BUBBLE) {
- log(b, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_NO_LONGER_BUBBLE);
- } else if (r == BubbleController.DISMISS_BLOCKED) {
- log(b, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_BLOCKED);
- }
- }
-
- /**
- * @param b Bubble added to overflow
- * @param r Reason that bubble was added to overflow
- */
- public void logOverflowAdd(Bubble b, @BubbleController.DismissReason int r) {
- if (r == BubbleController.DISMISS_AGED) {
- log(b, Event.BUBBLE_OVERFLOW_ADD_AGED);
- } else if (r == BubbleController.DISMISS_USER_GESTURE) {
- log(b, Event.BUBBLE_OVERFLOW_ADD_USER_GESTURE);
- }
- }
-
- void logStackUiChanged(String packageName, int action, int bubbleCount, float normalX,
- float normalY) {
- SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED,
- packageName,
- null /* notification channel */,
- 0 /* notification ID */,
- 0 /* bubble position */,
- bubbleCount,
- action,
- normalX,
- normalY,
- false /* unread bubble */,
- false /* on-going bubble */,
- false /* isAppForeground (unused) */);
- }
-
- void logShowOverflow(String packageName, int currentUserId) {
- super.log(BubbleLogger.Event.BUBBLE_OVERFLOW_SELECTED, currentUserId,
- packageName);
- }
-
- void logBubbleUiChanged(Bubble bubble, String packageName, int action, int bubbleCount,
- float normalX, float normalY, int index) {
- SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED,
- packageName,
- bubble.getChannelId() /* notification channel */,
- bubble.getNotificationId() /* notification ID */,
- index,
- bubbleCount,
- action,
- normalX,
- normalY,
- bubble.showInShade() /* isUnread */,
- false /* isOngoing (unused) */,
- false /* isAppForeground (unused) */);
- }
-} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.kt b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.kt
index bf7c860132bf..102055de2bea 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.kt
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.kt
@@ -16,6 +16,7 @@
package com.android.systemui.bubbles
+import android.app.ActivityTaskManager.INVALID_TASK_ID
import android.content.Context
import android.content.res.Configuration
import android.graphics.Bitmap
@@ -37,8 +38,8 @@ class BubbleOverflow(
private val stack: BubbleStackView
) : BubbleViewProvider {
- private lateinit var bitmap : Bitmap
- private lateinit var dotPath : Path
+ private lateinit var bitmap: Bitmap
+ private lateinit var dotPath: Path
private var bitmapSize = 0
private var iconBitmapSize = 0
@@ -167,8 +168,8 @@ class BubbleOverflow(
return KEY
}
- override fun getDisplayId(): Int {
- return expandedView.virtualDisplayId
+ override fun getTaskId(): Int {
+ return if (expandedView != null) expandedView.getTaskId() else INVALID_TASK_ID
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
index 160addc405fa..fc3f5b6cbf5e 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
@@ -22,11 +22,13 @@ import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME
import android.app.Activity;
import android.content.Context;
+import android.content.Intent;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.os.Bundle;
+import android.os.IBinder;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.LayoutInflater;
@@ -47,20 +49,21 @@ import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
-import javax.inject.Inject;
/**
* Activity for showing aged out bubbles.
* Must be public to be accessible to androidx...AppComponentFactory
*/
public class BubbleOverflowActivity extends Activity {
+ static final String EXTRA_BUBBLE_CONTROLLER = "bubble_controller";
+
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleOverflowActivity" : TAG_BUBBLES;
private LinearLayout mEmptyState;
private TextView mEmptyStateTitle;
private TextView mEmptyStateSubtitle;
private ImageView mEmptyStateImage;
- private BubbleController mBubbleController;
+ private Bubbles mBubbles;
private BubbleOverflowAdapter mAdapter;
private RecyclerView mRecyclerView;
private List<Bubble> mOverflowBubbles = new ArrayList<>();
@@ -71,7 +74,8 @@ public class BubbleOverflowActivity extends Activity {
}
@Override
public boolean canScrollVertically() {
- if (mBubbleController.inLandscape()) {
+ if (getResources().getConfiguration().orientation
+ == Configuration.ORIENTATION_LANDSCAPE) {
return super.canScrollVertically();
}
return false;
@@ -92,11 +96,6 @@ public class BubbleOverflowActivity extends Activity {
}
}
- @Inject
- public BubbleOverflowActivity(BubbleController controller) {
- mBubbleController = controller;
- }
-
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -108,6 +107,15 @@ public class BubbleOverflowActivity extends Activity {
mEmptyStateSubtitle = findViewById(R.id.bubble_overflow_empty_subtitle);
mEmptyStateImage = findViewById(R.id.bubble_overflow_empty_state_image);
+ Intent intent = getIntent();
+ if (intent != null && intent.getExtras() != null) {
+ IBinder binder = intent.getExtras().getBinder(EXTRA_BUBBLE_CONTROLLER);
+ if (binder instanceof ObjectWrapper) {
+ mBubbles = ((ObjectWrapper<Bubbles>) binder).get();
+ }
+ } else {
+ Log.w(TAG, "Bubble overflow activity created without bubble controller!");
+ }
updateOverflow();
}
@@ -131,15 +139,15 @@ public class BubbleOverflowActivity extends Activity {
final int viewHeight = recyclerViewHeight / rows;
mAdapter = new BubbleOverflowAdapter(getApplicationContext(), mOverflowBubbles,
- mBubbleController::promoteBubbleFromOverflow, viewWidth, viewHeight);
+ mBubbles::promoteBubbleFromOverflow, viewWidth, viewHeight);
mRecyclerView.setAdapter(mAdapter);
mOverflowBubbles.clear();
- mOverflowBubbles.addAll(mBubbleController.getOverflowBubbles());
+ mOverflowBubbles.addAll(mBubbles.getOverflowBubbles());
mAdapter.notifyDataSetChanged();
updateEmptyStateVisibility();
- mBubbleController.setOverflowListener(mDataListener);
+ mBubbles.setOverflowListener(mDataListener);
updateTheme();
}
@@ -209,8 +217,7 @@ public class BubbleOverflowActivity extends Activity {
if (DEBUG_OVERFLOW) {
Log.d(TAG, BubbleDebugConfig.formatBubblesString(
- mBubbleController.getOverflowBubbles(),
- null));
+ mBubbles.getOverflowBubbles(), null));
}
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index f2fba23564da..431719f98ad9 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -16,11 +16,17 @@
package com.android.systemui.bubbles;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
+import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_STACK_VIEW;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
-import android.app.ActivityView;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -67,21 +73,16 @@ import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.systemui.Interpolators;
-import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.bubbles.animation.AnimatableScaleMatrix;
import com.android.systemui.bubbles.animation.ExpandedAnimationController;
import com.android.systemui.bubbles.animation.PhysicsAnimationLayout;
import com.android.systemui.bubbles.animation.StackAnimationController;
-import com.android.systemui.model.SysUiState;
-import com.android.systemui.shared.system.QuickStepContract;
-import com.android.systemui.shared.system.SysUiStatsLog;
-import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
-import com.android.systemui.util.FloatingContentCoordinator;
-import com.android.systemui.util.RelativeTouchListener;
-import com.android.systemui.util.animation.PhysicsAnimator;
-import com.android.systemui.util.magnetictarget.MagnetizedObject;
+import com.android.wm.shell.animation.PhysicsAnimator;
+import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -92,12 +93,6 @@ import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_STACK_VIEW;
-import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
-import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
-
/**
* Renders bubbles in a stack and handles animating expanded and collapsed states.
*/
@@ -120,6 +115,8 @@ public class BubbleStackView extends FrameLayout
/** Duration of the flyout alpha animations. */
private static final int FLYOUT_ALPHA_ANIMATION_DURATION = 100;
+ private static final int FADE_IN_DURATION = 320;
+
/** Percent to darken the bubbles when they're in the dismiss target. */
private static final float DARKEN_PERCENT = 0.3f;
@@ -301,7 +298,7 @@ public class BubbleStackView extends FrameLayout
pw.println(" expandedViewAlpha: " + expandedView.getAlpha());
pw.println(" expandedViewTaskId: " + expandedView.getTaskId());
- final ActivityView av = expandedView.getActivityView();
+ final View av = expandedView.getTaskView();
if (av != null) {
pw.println(" activityViewVis: " + av.getVisibility());
@@ -322,8 +319,6 @@ public class BubbleStackView extends FrameLayout
/** Callback to run when we want to unbubble the given notification's conversation. */
private Consumer<String> mUnbubbleConversationCallback;
- private SysUiState mSysUiState;
-
private boolean mViewUpdatedRequested = false;
private boolean mIsExpansionAnimating = false;
private boolean mIsBubbleSwitchAnimating = false;
@@ -331,8 +326,6 @@ public class BubbleStackView extends FrameLayout
/** The view to desaturate/darken when magneted to the dismiss target. */
@Nullable private View mDesaturateAndDarkenTargetView;
- private LayoutInflater mInflater;
-
private Rect mTempRect = new Rect();
private final List<Rect> mSystemGestureExclusionRects = Collections.singletonList(new Rect());
@@ -400,6 +393,11 @@ public class BubbleStackView extends FrameLayout
public final Consumer<Boolean> mOnImeVisibilityChanged;
/**
+ * Callback to run when the bubble expand status changes.
+ */
+ private final Consumer<Boolean> mOnBubbleExpandChanged;
+
+ /**
* Callback to run to ask BubbleController to hide the current IME.
*/
private final Runnable mHideCurrentInputMethodCallback;
@@ -659,7 +657,7 @@ public class BubbleStackView extends FrameLayout
viewInitialX + dx, velX, velY) <= 0;
updateBubbleIcons();
logBubbleEvent(null /* no bubble associated with bubble stack move */,
- SysUiStatsLog.BUBBLE_UICHANGED__ACTION__STACK_MOVED);
+ FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__STACK_MOVED);
}
mDismissView.hide();
}
@@ -741,16 +739,13 @@ public class BubbleStackView extends FrameLayout
public BubbleStackView(Context context, BubbleData data,
@Nullable SurfaceSynchronizer synchronizer,
FloatingContentCoordinator floatingContentCoordinator,
- SysUiState sysUiState,
Runnable allBubblesAnimatedOutAction,
Consumer<Boolean> onImeVisibilityChanged,
- Runnable hideCurrentInputMethodCallback) {
+ Runnable hideCurrentInputMethodCallback,
+ Consumer<Boolean> onBubbleExpandChanged) {
super(context);
mBubbleData = data;
- mInflater = LayoutInflater.from(context);
-
- mSysUiState = sysUiState;
Resources res = getResources();
mMaxBubbles = res.getInteger(R.integer.bubbles_max_rendered);
@@ -864,21 +859,13 @@ public class BubbleStackView extends FrameLayout
mOnImeVisibilityChanged = onImeVisibilityChanged;
mHideCurrentInputMethodCallback = hideCurrentInputMethodCallback;
+ mOnBubbleExpandChanged = onBubbleExpandChanged;
setOnApplyWindowInsetsListener((View view, WindowInsets insets) -> {
onImeVisibilityChanged.accept(insets.getInsets(WindowInsets.Type.ime()).bottom > 0);
-
if (!mIsExpanded || mIsExpansionAnimating) {
return view.onApplyWindowInsets(insets);
}
- mExpandedAnimationController.updateYPosition(
- // Update the insets after we're done translating otherwise position
- // calculation for them won't be correct.
- () -> {
- if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
- mExpandedBubble.getExpandedView().updateInsets(insets);
- }
- });
return view.onApplyWindowInsets(insets);
});
@@ -973,7 +960,7 @@ public class BubbleStackView extends FrameLayout
animate()
.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED)
- .setDuration(CollapsedStatusBarFragment.FADE_IN_DURATION);
+ .setDuration(FADE_IN_DURATION);
}
/**
@@ -1065,7 +1052,7 @@ public class BubbleStackView extends FrameLayout
mBubbleData.setExpanded(false);
mContext.startActivityAsUser(intent, bubble.getUser());
logBubbleEvent(bubble,
- SysUiStatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_SETTINGS);
+ FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_SETTINGS);
}
});
@@ -1081,8 +1068,7 @@ public class BubbleStackView extends FrameLayout
* Whether the educational view should show for the expanded view "manage" menu.
*/
private boolean shouldShowManageEdu() {
- final boolean seen = Prefs.getBoolean(mContext,
- Prefs.Key.HAS_SEEN_BUBBLES_MANAGE_EDUCATION, false /* default */);
+ final boolean seen = getPrefBoolean(ManageEducationViewKt.PREF_MANAGED_EDUCATION);
final boolean shouldShow = (!seen || BubbleDebugConfig.forceShowUserEducation(mContext))
&& mExpandedBubble != null;
if (BubbleDebugConfig.DEBUG_USER_EDUCATION) {
@@ -1106,8 +1092,7 @@ public class BubbleStackView extends FrameLayout
* Whether education view should show for the collapsed stack.
*/
private boolean shouldShowStackEdu() {
- final boolean seen = Prefs.getBoolean(getContext(),
- Prefs.Key.HAS_SEEN_BUBBLES_EDUCATION, false /* default */);
+ final boolean seen = getPrefBoolean(StackEducationViewKt.PREF_STACK_EDUCATION);
final boolean shouldShow = !seen || BubbleDebugConfig.forceShowUserEducation(mContext);
if (BubbleDebugConfig.DEBUG_USER_EDUCATION) {
Log.d(TAG, "Show stack edu: " + shouldShow);
@@ -1115,6 +1100,11 @@ public class BubbleStackView extends FrameLayout
return shouldShow;
}
+ private boolean getPrefBoolean(String key) {
+ return mContext.getSharedPreferences(mContext.getPackageName(), Context.MODE_PRIVATE)
+ .getBoolean(key, false /* default */);
+ }
+
/**
* @return true if education view for collapsed stack should show and was not showing before.
*/
@@ -1155,6 +1145,10 @@ public class BubbleStackView extends FrameLayout
addView(mFlyout, new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
}
+ void updateFlyout(float fontScale) {
+ mFlyout.updateFontSize(fontScale);
+ }
+
private void updateOverflow() {
mBubbleOverflow.update();
mBubbleContainer.reorderView(mBubbleOverflow.getIconView(),
@@ -1249,7 +1243,15 @@ public class BubbleStackView extends FrameLayout
mTempRect.setEmpty();
getTouchableRegion(mTempRect);
- inoutInfo.touchableRegion.set(mTempRect);
+ if (mIsExpanded && mExpandedBubble != null
+ && mExpandedBubble.getExpandedView() != null
+ && mExpandedBubble.getExpandedView().getTaskView() != null) {
+ inoutInfo.touchableRegion.set(mTempRect);
+ mExpandedBubble.getExpandedView().getTaskView().getBoundsOnScreen(mTempRect);
+ inoutInfo.touchableRegion.op(mTempRect, Region.Op.DIFFERENCE);
+ } else {
+ inoutInfo.touchableRegion.set(mTempRect);
+ }
}
@Override
@@ -1437,13 +1439,6 @@ public class BubbleStackView extends FrameLayout
}
/**
- * The {@link BadgedImageView} that is expanded, null if one does not exist.
- */
- View getExpandedBubbleView() {
- return mExpandedBubble != null ? mExpandedBubble.getIconView() : null;
- }
-
- /**
* The {@link Bubble} that is expanded, null if one does not exist.
*/
@Nullable
@@ -1483,7 +1478,7 @@ public class BubbleStackView extends FrameLayout
new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
animateInFlyoutForBubble(bubble);
requestUpdate();
- logBubbleEvent(bubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__POSTED);
+ logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__POSTED);
}
// via BubbleData.Listener
@@ -1503,7 +1498,7 @@ public class BubbleStackView extends FrameLayout
bubble.cleanupViews();
}
updatePointerPosition();
- logBubbleEvent(bubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED);
+ logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED);
return;
}
}
@@ -1521,7 +1516,7 @@ public class BubbleStackView extends FrameLayout
void updateBubble(Bubble bubble) {
animateInFlyoutForBubble(bubble);
requestUpdate();
- logBubbleEvent(bubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__UPDATED);
+ logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__UPDATED);
}
public void updateBubbleOrder(List<Bubble> bubbles) {
@@ -1558,7 +1553,7 @@ public class BubbleStackView extends FrameLayout
return;
}
- if (bubbleToSelect.getKey() == BubbleOverflow.KEY) {
+ if (bubbleToSelect.getKey().equals(BubbleOverflow.KEY)) {
mBubbleData.setShowingOverflow(true);
} else {
mBubbleData.setShowingOverflow(false);
@@ -1617,8 +1612,9 @@ public class BubbleStackView extends FrameLayout
requestUpdate();
logBubbleEvent(previouslySelected,
- SysUiStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
- logBubbleEvent(bubbleToSelect, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
+ FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
+ logBubbleEvent(bubbleToSelect,
+ FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
notifyExpansionChanged(previouslySelected, false /* expanded */);
notifyExpansionChanged(bubbleToSelect, true /* expanded */);
});
@@ -1648,30 +1644,21 @@ public class BubbleStackView extends FrameLayout
hideCurrentInputMethod();
- mSysUiState
- .setFlag(QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED, shouldExpand)
- .commitUpdate(mContext.getDisplayId());
+ mOnBubbleExpandChanged.accept(shouldExpand);
if (mIsExpanded) {
animateCollapse();
- logBubbleEvent(mExpandedBubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
+ logBubbleEvent(mExpandedBubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
} else {
animateExpansion();
// TODO: move next line to BubbleData
- logBubbleEvent(mExpandedBubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
- logBubbleEvent(mExpandedBubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__STACK_EXPANDED);
+ logBubbleEvent(mExpandedBubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
+ logBubbleEvent(mExpandedBubble,
+ FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__STACK_EXPANDED);
}
notifyExpansionChanged(mExpandedBubble, mIsExpanded);
}
- void showExpandedViewContents(int displayId) {
- if (mExpandedBubble != null
- && mExpandedBubble.getExpandedView() != null
- && mExpandedBubble.getExpandedView().getVirtualDisplayId() == displayId) {
- mExpandedBubble.setContentVisibility(true);
- }
- }
-
/**
* Asks the BubbleController to hide the IME from anywhere, whether it's focused on Bubbles or
* not.
@@ -1706,7 +1693,6 @@ public class BubbleStackView extends FrameLayout
updateOverflowVisibility();
updatePointerPosition();
mExpandedAnimationController.expandFromStack(() -> {
- afterExpandedViewAnimation();
if (mIsExpanded && mExpandedBubble.getExpandedView() != null) {
maybeShowManageEdu();
}
@@ -1769,11 +1755,10 @@ public class BubbleStackView extends FrameLayout
mExpandedViewContainerMatrix);
})
.withEndActions(() -> {
+ afterExpandedViewAnimation();
if (mExpandedBubble != null
&& mExpandedBubble.getExpandedView() != null) {
mExpandedBubble.getExpandedView()
- .setContentVisibility(true);
- mExpandedBubble.getExpandedView()
.setSurfaceZOrderedOnTop(false);
}
})
@@ -1917,7 +1902,6 @@ public class BubbleStackView extends FrameLayout
})
.withEndActions(() -> {
if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
- mExpandedBubble.getExpandedView().setContentVisibility(true);
mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false);
}
@@ -1953,13 +1937,6 @@ public class BubbleStackView extends FrameLayout
}
}
- /** Return the BubbleView at the given index from the bubble container. */
- public BadgedImageView getBubbleAt(int i) {
- return getBubbleCount() > i
- ? (BadgedImageView) mBubbleContainer.getChildAt(i)
- : null;
- }
-
/** Moves the bubbles out of the way if they're going to be over the keyboard. */
public void onImeVisibilityChanged(boolean visible, int height) {
mStackAnimationController.setImeHeight(visible ? height + mImeOffset : 0);
@@ -1981,6 +1958,9 @@ public class BubbleStackView extends FrameLayout
FLYOUT_IME_ANIMATION_SPRING_CONFIG)
.start();
}
+ } else if (mIsExpanded && mExpandedBubble != null
+ && mExpandedBubble.getExpandedView() != null) {
+ mExpandedBubble.getExpandedView().setImeVisible(visible);
}
}
@@ -2191,11 +2171,7 @@ public class BubbleStackView extends FrameLayout
return getStatusBarHeight() + mBubbleSize + mBubblePaddingTop;
}
- /**
- * Animates in the flyout for the given bubble, if available, and then hides it after some time.
- */
- @VisibleForTesting
- void animateInFlyoutForBubble(Bubble bubble) {
+ private boolean shouldShowFlyout(Bubble bubble) {
Bubble.FlyoutMessage flyoutMessage = bubble.getFlyoutMessage();
final BadgedImageView bubbleView = bubble.getIconView();
if (flyoutMessage == null
@@ -2207,11 +2183,22 @@ public class BubbleStackView extends FrameLayout
|| mIsGestureInProgress
|| mBubbleToExpandAfterFlyoutCollapse != null
|| bubbleView == null) {
- if (bubbleView != null) {
+ if (bubbleView != null && mFlyout.getVisibility() != VISIBLE) {
bubbleView.removeDotSuppressionFlag(BadgedImageView.SuppressionFlag.FLYOUT_VISIBLE);
}
// Skip the message if none exists, we're expanded or animating expansion, or we're
// about to expand a bubble from the previous tapped flyout, or if bubble view is null.
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Animates in the flyout for the given bubble, if available, and then hides it after some time.
+ */
+ @VisibleForTesting
+ void animateInFlyoutForBubble(Bubble bubble) {
+ if (!shouldShowFlyout(bubble)) {
return;
}
@@ -2229,25 +2216,22 @@ public class BubbleStackView extends FrameLayout
}
// Stop suppressing the dot now that the flyout has morphed into the dot.
- bubbleView.removeDotSuppressionFlag(
+ bubble.getIconView().removeDotSuppressionFlag(
BadgedImageView.SuppressionFlag.FLYOUT_VISIBLE);
- mFlyout.setVisibility(INVISIBLE);
-
// Hide the stack after a delay, if needed.
updateTemporarilyInvisibleAnimation(false /* hideImmediately */);
};
- mFlyout.setVisibility(INVISIBLE);
// Suppress the dot when we are animating the flyout.
- bubbleView.addDotSuppressionFlag(
+ bubble.getIconView().addDotSuppressionFlag(
BadgedImageView.SuppressionFlag.FLYOUT_VISIBLE);
// Start flyout expansion. Post in case layout isn't complete and getWidth returns 0.
post(() -> {
// An auto-expanding bubble could have been posted during the time it takes to
// layout.
- if (isExpanded()) {
+ if (isExpanded() || bubble.getIconView() == null) {
return;
}
final Runnable expandFlyoutAfterDelay = () -> {
@@ -2264,23 +2248,26 @@ public class BubbleStackView extends FrameLayout
mFlyout.postDelayed(mAnimateInFlyout, 200);
};
- if (bubble.getIconView() == null) {
- return;
- }
- mFlyout.setupFlyoutStartingAsDot(flyoutMessage,
- mStackAnimationController.getStackPosition(), getWidth(),
- mStackAnimationController.isStackOnLeftSide(),
- bubble.getIconView().getDotColor() /* dotColor */,
- expandFlyoutAfterDelay /* onLayoutComplete */,
- mAfterFlyoutHidden,
- bubble.getIconView().getDotCenter(),
- !bubble.showDot());
+ if (mFlyout.getVisibility() == View.VISIBLE) {
+ mFlyout.animateUpdate(bubble.getFlyoutMessage(), getWidth(),
+ mStackAnimationController.getStackPosition().y);
+ } else {
+ mFlyout.setVisibility(INVISIBLE);
+ mFlyout.setupFlyoutStartingAsDot(bubble.getFlyoutMessage(),
+ mStackAnimationController.getStackPosition(), getWidth(),
+ mStackAnimationController.isStackOnLeftSide(),
+ bubble.getIconView().getDotColor() /* dotColor */,
+ expandFlyoutAfterDelay /* onLayoutComplete */,
+ mAfterFlyoutHidden,
+ bubble.getIconView().getDotCenter(),
+ !bubble.showDot());
+ }
mFlyout.bringToFront();
});
mFlyout.removeCallbacks(mHideFlyout);
mFlyout.postDelayed(mHideFlyout, FLYOUT_HIDE_AFTER);
- logBubbleEvent(bubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__FLYOUT);
+ logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__FLYOUT);
}
/** Hide the flyout immediately and cancel any pending hide runnables. */
@@ -2388,7 +2375,6 @@ public class BubbleStackView extends FrameLayout
final float targetY = mTempRect.bottom - mManageMenu.getHeight();
final float xOffsetForAnimation = (isLtr ? 1 : -1) * mManageMenu.getWidth() / 4f;
-
if (show) {
mManageMenu.setScaleX(0.5f);
mManageMenu.setScaleY(0.5f);
@@ -2405,6 +2391,8 @@ public class BubbleStackView extends FrameLayout
.withEndActions(() -> {
View child = mManageMenu.getChildAt(0);
child.requestAccessibilityFocus();
+ // Update the AV's obscured touchable region for the new visibility state.
+ mExpandedBubble.getExpandedView().updateObscuredTouchableRegion();
})
.start();
@@ -2416,12 +2404,15 @@ public class BubbleStackView extends FrameLayout
.spring(DynamicAnimation.SCALE_Y, 0.5f)
.spring(DynamicAnimation.TRANSLATION_X, targetX - xOffsetForAnimation)
.spring(DynamicAnimation.TRANSLATION_Y, targetY + mManageMenu.getHeight() / 4f)
- .withEndActions(() -> mManageMenu.setVisibility(View.INVISIBLE))
+ .withEndActions(() -> {
+ mManageMenu.setVisibility(View.INVISIBLE);
+ if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
+ // Update the AV's obscured touchable region for the new state.
+ mExpandedBubble.getExpandedView().updateObscuredTouchableRegion();
+ }
+ })
.start();
}
-
- // Update the AV's obscured touchable region for the new menu visibility state.
- mExpandedBubble.getExpandedView().updateObscuredTouchableRegion();
}
private void updateExpandedBubble() {
@@ -2441,7 +2432,6 @@ public class BubbleStackView extends FrameLayout
mExpandedViewContainer.setAlpha(0f);
mExpandedViewContainer.addView(bev);
bev.setManageClickListener((view) -> showManageMenu(!mShowingManage));
- bev.populateExpandedView();
if (!mIsExpansionAnimating) {
mSurfaceSynchronizer.syncSurfaceAndRun(() -> {
@@ -2498,7 +2488,7 @@ public class BubbleStackView extends FrameLayout
mAnimatingOutSurfaceContainer.setTranslationY(0);
final int[] activityViewLocation =
- mExpandedBubble.getExpandedView().getActivityViewLocationOnScreen();
+ mExpandedBubble.getExpandedView().getTaskViewLocationOnScreen();
final int[] surfaceViewLocation = mAnimatingOutSurfaceView.getLocationOnScreen();
// Translate the surface to overlap the real ActivityView.
@@ -2674,17 +2664,6 @@ public class BubbleStackView extends FrameLayout
getNormalizedYPosition());
}
- /**
- * Called when a back gesture should be directed to the Bubbles stack. When expanded,
- * a back key down/up event pair is forwarded to the bubble Activity.
- */
- boolean performBackPressIfNeeded() {
- if (!isExpanded() || mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) {
- return false;
- }
- return mExpandedBubble.getExpandedView().performBackPressIfNeeded();
- }
-
/** For debugging only */
List<Bubble> getBubblesOnScreen() {
List<Bubble> bubbles = new ArrayList<>();
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTaskView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTaskView.java
deleted file mode 100644
index 06205c5c1c41..000000000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTaskView.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.bubbles;
-
-import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
-import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.ActivityOptions;
-import android.app.PendingIntent;
-import android.window.TaskEmbedder;
-import android.window.TaskOrganizerTaskEmbedder;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ShortcutInfo;
-import android.graphics.Matrix;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.view.IWindow;
-import android.view.SurfaceControl;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
-
-import dalvik.system.CloseGuard;
-
-
-public class BubbleTaskView extends SurfaceView implements SurfaceHolder.Callback,
- TaskEmbedder.Host {
- private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleTaskView" : TAG_BUBBLES;
-
- private final CloseGuard mGuard = CloseGuard.get();
- private boolean mOpened; // Protected by mGuard.
-
- private TaskEmbedder mTaskEmbedder;
- private final SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction();
- private final Rect mTmpRect = new Rect();
-
- public BubbleTaskView(Context context) {
- super(context);
-
- mTaskEmbedder = new TaskOrganizerTaskEmbedder(context, this);
- setUseAlpha();
- getHolder().addCallback(this);
-
- mOpened = true;
- mGuard.open("release");
- }
-
- public void setCallback(TaskEmbedder.Listener callback) {
- if (callback == null) {
- mTaskEmbedder.setListener(null);
- return;
- }
- mTaskEmbedder.setListener(callback);
- }
-
- public void startShortcutActivity(@NonNull ShortcutInfo shortcut,
- @NonNull ActivityOptions options, @Nullable Rect sourceBounds) {
- mTaskEmbedder.startShortcutActivity(shortcut, options, sourceBounds);
- }
-
- public void startActivity(@NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent,
- @NonNull ActivityOptions options) {
- mTaskEmbedder.startActivity(pendingIntent, fillInIntent, options);
- }
-
- public void onLocationChanged() {
- mTaskEmbedder.notifyBoundsChanged();
- }
-
- @Override
- public Rect getScreenBounds() {
- getBoundsOnScreen(mTmpRect);
- return mTmpRect;
- }
-
- @Override
- public void onTaskBackgroundColorChanged(TaskEmbedder ts, int bgColor) {
- setResizeBackgroundColor(bgColor);
- }
-
- @Override
- public Region getTapExcludeRegion() {
- // Not used
- return null;
- }
-
- @Override
- public Matrix getScreenToTaskMatrix() {
- // Not used
- return null;
- }
-
- @Override
- public IWindow getWindow() {
- // Not used
- return null;
- }
-
- @Override
- public Point getPositionInWindow() {
- // Not used
- return null;
- }
-
- @Override
- public boolean canReceivePointerEvents() {
- // Not used
- return false;
- }
-
- public void release() {
- if (!mTaskEmbedder.isInitialized()) {
- throw new IllegalStateException(
- "Trying to release container that is not initialized.");
- }
- performRelease();
- }
-
- @Override
- protected void finalize() throws Throwable {
- try {
- if (mGuard != null) {
- mGuard.warnIfOpen();
- performRelease();
- }
- } finally {
- super.finalize();
- }
- }
-
- private void performRelease() {
- if (!mOpened) {
- return;
- }
- getHolder().removeCallback(this);
- mTaskEmbedder.release();
- mTaskEmbedder.setListener(null);
-
- mGuard.close();
- mOpened = false;
- }
-
- @Override
- public void surfaceCreated(SurfaceHolder holder) {
- if (!mTaskEmbedder.isInitialized()) {
- mTaskEmbedder.initialize(getSurfaceControl());
- } else {
- mTmpTransaction.reparent(mTaskEmbedder.getSurfaceControl(),
- getSurfaceControl()).apply();
- }
- mTaskEmbedder.start();
- }
-
- @Override
- public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
- mTaskEmbedder.resizeTask(width, height);
- mTaskEmbedder.notifyBoundsChanged();
- }
-
- @Override
- public void surfaceDestroyed(SurfaceHolder holder) {
- mTaskEmbedder.stop();
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
index 28757facc220..010a29e3560a 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
@@ -22,8 +22,6 @@ import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
import android.annotation.NonNull;
-import android.app.Notification;
-import android.app.Person;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -36,8 +34,6 @@ import android.graphics.Path;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.AsyncTask;
-import android.os.Parcelable;
-import android.text.TextUtils;
import android.util.Log;
import android.util.PathParser;
import android.view.LayoutInflater;
@@ -47,10 +43,8 @@ import androidx.annotation.Nullable;
import com.android.internal.graphics.ColorUtils;
import com.android.launcher3.icons.BitmapInfo;
import com.android.systemui.R;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import java.lang.ref.WeakReference;
-import java.util.List;
import java.util.Objects;
/**
@@ -208,73 +202,6 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
}
}
-
- /**
- * Returns our best guess for the most relevant text summary of the latest update to this
- * notification, based on its type. Returns null if there should not be an update message.
- */
- @NonNull
- static Bubble.FlyoutMessage extractFlyoutMessage(NotificationEntry entry) {
- Objects.requireNonNull(entry);
- final Notification underlyingNotif = entry.getSbn().getNotification();
- final Class<? extends Notification.Style> style = underlyingNotif.getNotificationStyle();
-
- Bubble.FlyoutMessage bubbleMessage = new Bubble.FlyoutMessage();
- bubbleMessage.isGroupChat = underlyingNotif.extras.getBoolean(
- Notification.EXTRA_IS_GROUP_CONVERSATION);
- try {
- if (Notification.BigTextStyle.class.equals(style)) {
- // Return the big text, it is big so probably important. If it's not there use the
- // normal text.
- CharSequence bigText =
- underlyingNotif.extras.getCharSequence(Notification.EXTRA_BIG_TEXT);
- bubbleMessage.message = !TextUtils.isEmpty(bigText)
- ? bigText
- : underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT);
- return bubbleMessage;
- } else if (Notification.MessagingStyle.class.equals(style)) {
- final List<Notification.MessagingStyle.Message> messages =
- Notification.MessagingStyle.Message.getMessagesFromBundleArray(
- (Parcelable[]) underlyingNotif.extras.get(
- Notification.EXTRA_MESSAGES));
-
- final Notification.MessagingStyle.Message latestMessage =
- Notification.MessagingStyle.findLatestIncomingMessage(messages);
- if (latestMessage != null) {
- bubbleMessage.message = latestMessage.getText();
- Person sender = latestMessage.getSenderPerson();
- bubbleMessage.senderName = sender != null ? sender.getName() : null;
- bubbleMessage.senderAvatar = null;
- bubbleMessage.senderIcon = sender != null ? sender.getIcon() : null;
- return bubbleMessage;
- }
- } else if (Notification.InboxStyle.class.equals(style)) {
- CharSequence[] lines =
- underlyingNotif.extras.getCharSequenceArray(Notification.EXTRA_TEXT_LINES);
-
- // Return the last line since it should be the most recent.
- if (lines != null && lines.length > 0) {
- bubbleMessage.message = lines[lines.length - 1];
- return bubbleMessage;
- }
- } else if (Notification.MediaStyle.class.equals(style)) {
- // Return nothing, media updates aren't typically useful as a text update.
- return bubbleMessage;
- } else {
- // Default to text extra.
- bubbleMessage.message =
- underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT);
- return bubbleMessage;
- }
- } catch (ClassCastException | NullPointerException | ArrayIndexOutOfBoundsException e) {
- // No use crashing, we'll just return null and the caller will assume there's no update
- // message.
- e.printStackTrace();
- }
-
- return bubbleMessage;
- }
-
@Nullable
static Drawable loadSenderAvatar(@NonNull final Context context, @Nullable final Icon icon) {
Objects.requireNonNull(context);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java
index 916ad18b2812..5cc24ce5a775 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java
@@ -48,5 +48,5 @@ interface BubbleViewProvider {
boolean showDot();
- int getDisplayId();
+ int getTaskId();
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubbles.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubbles.java
new file mode 100644
index 000000000000..39c750de28ac
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubbles.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bubbles;
+
+import android.annotation.NonNull;
+
+import androidx.annotation.MainThread;
+
+import com.android.systemui.statusbar.ScrimView;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.phone.ScrimController;
+
+import java.util.List;
+
+/**
+ * Interface to engage bubbles feature.
+ */
+public interface Bubbles {
+
+ /**
+ * @return {@code true} if there is a bubble associated with the provided key and if its
+ * notification is hidden from the shade or there is a group summary associated with the
+ * provided key that is hidden from the shade because it has been dismissed but still has child
+ * bubbles active.
+ */
+ boolean isBubbleNotificationSuppressedFromShade(NotificationEntry entry);
+
+ /**
+ * @return {@code true} if the current notification entry same as selected bubble
+ * notification entry and the stack is currently expanded.
+ */
+ boolean isBubbleExpanded(NotificationEntry entry);
+
+ /** @return {@code true} if stack of bubbles is expanded or not. */
+ boolean isStackExpanded();
+
+ /**
+ * @return the {@link ScrimView} drawn behind the bubble stack. This is managed by
+ * {@link ScrimController} since we want the scrim's appearance and behavior to be identical to
+ * that of the notification shade scrim.
+ */
+ ScrimView getScrimForBubble();
+
+ /** @return Bubbles for updating overflow. */
+ List<Bubble> getOverflowBubbles();
+
+ /** Tell the stack of bubbles to collapse. */
+ void collapseStack();
+
+ /**
+ * Request the stack expand if needed, then select the specified Bubble as current.
+ * If no bubble exists for this entry, one is created.
+ *
+ * @param entry the notification for the bubble to be selected
+ */
+ void expandStackAndSelectBubble(NotificationEntry entry);
+
+ /** Promote the provided bubbles when overflow view. */
+ void promoteBubbleFromOverflow(Bubble bubble);
+
+ /**
+ * We intercept notification entries (including group summaries) dismissed by the user when
+ * there is an active bubble associated with it. We do this so that developers can still
+ * cancel it (and hence the bubbles associated with it). However, these intercepted
+ * notifications should then be hidden from the shade since the user has cancelled them, so we
+ * {@link Bubble#setSuppressNotification}. For the case of suppressed summaries, we also add
+ * {@link BubbleData#addSummaryToSuppress}.
+ *
+ * @return true if we want to intercept the dismissal of the entry, else false.
+ */
+ boolean handleDismissalInterception(NotificationEntry entry);
+
+ /**
+ * Removes the bubble with the given key.
+ * <p>
+ * Must be called from the main thread.
+ */
+ @MainThread
+ void removeBubble(String key, int reason);
+
+
+ /**
+ * When a notification is marked Priority, expand the stack if needed,
+ * then (maybe create and) select the given bubble.
+ *
+ * @param entry the notification for the bubble to show
+ */
+ void onUserChangedImportance(NotificationEntry entry);
+
+ /**
+ * Called when the status bar has become visible or invisible (either permanently or
+ * temporarily).
+ */
+ void onStatusBarVisibilityChanged(boolean visible);
+
+ /**
+ * Called when a user has indicated that an active notification should be shown as a bubble.
+ * <p>
+ * This method will collapse the shade, create the bubble without a flyout or dot, and suppress
+ * the notification from appearing in the shade.
+ *
+ * @param entry the notification to change bubble state for.
+ * @param shouldBubble whether the notification should show as a bubble or not.
+ */
+ void onUserChangedBubble(@NonNull NotificationEntry entry, boolean shouldBubble);
+
+
+ /** See {@link BubbleController.NotifCallback}. */
+ void addNotifCallback(BubbleController.NotifCallback callback);
+
+ /** Set a listener to be notified of bubble expand events. */
+ void setExpandListener(BubbleController.BubbleExpandListener listener);
+
+ /** Set a listener to be notified of when overflow view update. */
+ void setOverflowListener(BubbleData.Listener listener);
+
+ /** The task listener for events in bubble tasks. **/
+ MultiWindowTaskListener getTaskManager();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/DismissView.kt b/packages/SystemUI/src/com/android/systemui/bubbles/DismissView.kt
index 71faf4a2eeb7..b3c552d24dcd 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/DismissView.kt
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/DismissView.kt
@@ -10,8 +10,8 @@ import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY
import androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW
import com.android.systemui.R
-import com.android.systemui.util.DismissCircleView
-import com.android.systemui.util.animation.PhysicsAnimator
+import com.android.wm.shell.common.DismissCircleView
+import com.android.wm.shell.animation.PhysicsAnimator
/*
* View that handles interactions between DismissCircleView and BubbleStackView.
@@ -29,7 +29,7 @@ class DismissView(context: Context) : FrameLayout(context) {
var isShowing = false
private val animator = PhysicsAnimator.getInstance(circle)
- private val spring = PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY);
+ private val spring = PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY)
private val DISMISS_SCRIM_FADE_MS = 200
init {
setLayoutParams(LayoutParams(
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/ManageEducationView.kt b/packages/SystemUI/src/com/android/systemui/bubbles/ManageEducationView.kt
index 26a9773f9bb8..3db07c227d02 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/ManageEducationView.kt
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/ManageEducationView.kt
@@ -25,8 +25,6 @@ import android.widget.LinearLayout
import android.widget.TextView
import com.android.internal.util.ContrastColorUtil
import com.android.systemui.Interpolators
-import com.android.systemui.Prefs
-import com.android.systemui.Prefs.Key.HAS_SEEN_BUBBLES_MANAGE_EDUCATION
import com.android.systemui.R
/**
@@ -38,8 +36,8 @@ class ManageEducationView constructor(context: Context) : LinearLayout(context)
private val TAG = if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "BubbleManageEducationView"
else BubbleDebugConfig.TAG_BUBBLES
- private val ANIMATE_DURATION : Long = 200
- private val ANIMATE_DURATION_SHORT : Long = 40
+ private val ANIMATE_DURATION: Long = 200
+ private val ANIMATE_DURATION_SHORT: Long = 40
private val manageView by lazy { findViewById<View>(R.id.manage_education_view) }
private val manageButton by lazy { findViewById<Button>(R.id.manage) }
@@ -50,7 +48,7 @@ class ManageEducationView constructor(context: Context) : LinearLayout(context)
private var isHiding = false
init {
- LayoutInflater.from(context).inflate(R.layout.bubbles_manage_button_education, this);
+ LayoutInflater.from(context).inflate(R.layout.bubbles_manage_button_education, this)
visibility = View.GONE
elevation = resources.getDimensionPixelSize(R.dimen.bubble_elevation).toFloat()
@@ -95,7 +93,7 @@ class ManageEducationView constructor(context: Context) : LinearLayout(context)
*
* @param show whether the user education view should show or not.
*/
- fun show(expandedView: BubbleExpandedView, rect : Rect) {
+ fun show(expandedView: BubbleExpandedView, rect: Rect) {
if (visibility == VISIBLE) return
alpha = 0f
@@ -136,10 +134,13 @@ class ManageEducationView constructor(context: Context) : LinearLayout(context)
.withEndAction {
isHiding = false
visibility = GONE
- };
+ }
}
private fun setShouldShow(shouldShow: Boolean) {
- Prefs.putBoolean(context, HAS_SEEN_BUBBLES_MANAGE_EDUCATION, !shouldShow)
+ context.getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
+ .edit().putBoolean(PREF_MANAGED_EDUCATION, !shouldShow).apply()
}
-} \ No newline at end of file
+}
+
+const val PREF_MANAGED_EDUCATION: String = "HasSeenBubblesManageOnboarding" \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/MultiWindowTaskListener.java b/packages/SystemUI/src/com/android/systemui/bubbles/MultiWindowTaskListener.java
new file mode 100644
index 000000000000..dff8becccb86
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/MultiWindowTaskListener.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bubbles;
+
+import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_MULTI_WINDOW;
+
+import android.app.ActivityManager.RunningTaskInfo;
+import android.app.ActivityTaskManager;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.view.SurfaceControl;
+import android.window.TaskOrganizer;
+import android.window.WindowContainerToken;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+
+/**
+ * Manages tasks that are displayed in multi-window (e.g. bubbles). These are displayed in a
+ * {@link TaskView}.
+ *
+ * This class listens on {@link TaskOrganizer} callbacks for events. Once visible, these tasks will
+ * intercept back press events.
+ *
+ * @see android.app.WindowConfiguration#WINDOWING_MODE_MULTI_WINDOW
+ * @see TaskView
+ */
+// TODO: Place in com.android.wm.shell vs. com.android.wm.shell.bubbles on shell migration.
+public class MultiWindowTaskListener implements ShellTaskOrganizer.TaskListener {
+ private static final String TAG = MultiWindowTaskListener.class.getSimpleName();
+
+ private static final boolean DEBUG = false;
+
+ //TODO(b/170153209): Have shell listener allow per task registration and remove this.
+ public interface Listener {
+ void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash);
+ void onTaskVanished(RunningTaskInfo taskInfo);
+ void onTaskInfoChanged(RunningTaskInfo taskInfo);
+ void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo);
+ }
+
+ private static class TaskData {
+ final RunningTaskInfo taskInfo;
+ final Listener listener;
+
+ TaskData(RunningTaskInfo info, Listener l) {
+ taskInfo = info;
+ listener = l;
+ }
+ }
+
+ private final Handler mHandler;
+ private final ShellTaskOrganizer mTaskOrganizer;
+ private final ArrayMap<WindowContainerToken, TaskData> mTasks = new ArrayMap<>();
+
+ private MultiWindowTaskListener.Listener mPendingListener;
+
+ /**
+ * Create a listener for tasks in multi-window mode.
+ */
+ public MultiWindowTaskListener(Handler handler, ShellTaskOrganizer organizer) {
+ mHandler = handler;
+ mTaskOrganizer = organizer;
+ mTaskOrganizer.addListener(this, TASK_LISTENER_TYPE_MULTI_WINDOW);
+ }
+
+ /**
+ * @return the task organizer that is listened to.
+ */
+ public TaskOrganizer getTaskOrganizer() {
+ return mTaskOrganizer;
+ }
+
+ // TODO(b/129067201): track launches for bubbles
+ // Once we have key in ActivityOptions, match listeners via that key
+ public void setPendingListener(Listener listener) {
+ mPendingListener = listener;
+ }
+
+ /**
+ * Removes a task listener previously registered when starting a new activity.
+ */
+ public void removeListener(Listener listener) {
+ if (DEBUG) {
+ Log.d(TAG, "removeListener: listener=" + listener);
+ }
+ if (mPendingListener == listener) {
+ mPendingListener = null;
+ }
+ for (int i = 0; i < mTasks.size(); i++) {
+ if (mTasks.valueAt(i).listener == listener) {
+ mTasks.removeAt(i);
+ }
+ }
+ }
+
+ @Override
+ public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
+ if (DEBUG) {
+ Log.d(TAG, "onTaskAppeared: taskInfo=" + taskInfo
+ + " mPendingListener=" + mPendingListener);
+ }
+ if (mPendingListener == null) {
+ // If there is no pending listener, then we are either receiving this task as a part of
+ // registering the task org again (ie. after SysUI dies) or the previously started
+ // task is no longer needed (ie. bubble is closed soon after), for now, just finish the
+ // associated task
+ try {
+ ActivityTaskManager.getService().removeTask(taskInfo.taskId);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to remove taskId " + taskInfo.taskId);
+ }
+ return;
+ }
+
+ mTaskOrganizer.setInterceptBackPressedOnTaskRoot(taskInfo.token, true);
+
+ final TaskData data = new TaskData(taskInfo, mPendingListener);
+ mTasks.put(taskInfo.token, data);
+ mHandler.post(() -> data.listener.onTaskAppeared(taskInfo, leash));
+ mPendingListener = null;
+ }
+
+ @Override
+ public void onTaskVanished(RunningTaskInfo taskInfo) {
+ final TaskData data = mTasks.remove(taskInfo.token);
+ if (data == null) {
+ return;
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "onTaskVanished: taskInfo=" + taskInfo + " listener=" + data.listener);
+ }
+ mHandler.post(() -> data.listener.onTaskVanished(taskInfo));
+ }
+
+ @Override
+ public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
+ final TaskData data = mTasks.get(taskInfo.token);
+ if (data == null) {
+ return;
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "onTaskInfoChanged: taskInfo=" + taskInfo + " listener=" + data.listener);
+ }
+ mHandler.post(() -> data.listener.onTaskInfoChanged(taskInfo));
+ }
+
+ @Override
+ public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {
+ final TaskData data = mTasks.get(taskInfo.token);
+ if (data == null) {
+ return;
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "onTaskInfoChanged: taskInfo=" + taskInfo + " listener=" + data.listener);
+ }
+ mHandler.post(() -> data.listener.onBackPressedOnTaskRoot(taskInfo));
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/ObjectWrapper.java b/packages/SystemUI/src/com/android/systemui/bubbles/ObjectWrapper.java
new file mode 100644
index 000000000000..f054122eaa47
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/ObjectWrapper.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.bubbles;
+
+import android.os.Binder;
+import android.os.IBinder;
+
+// Copied from Launcher3
+/**
+ * Utility class to pass non-parcealable objects within same process using parcealable payload.
+ *
+ * It wraps the object in a binder as binders are singleton within a process
+ */
+public class ObjectWrapper<T> extends Binder {
+
+ private T mObject;
+
+ public ObjectWrapper(T object) {
+ mObject = object;
+ }
+
+ public T get() {
+ return mObject;
+ }
+
+ public void clear() {
+ mObject = null;
+ }
+
+ public static IBinder wrap(Object obj) {
+ return new ObjectWrapper<>(obj);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt b/packages/SystemUI/src/com/android/systemui/bubbles/RelativeTouchListener.kt
index 8880df9959c1..b1291a507b57 100644
--- a/packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/RelativeTouchListener.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.util
+package com.android.systemui.bubbles
import android.graphics.PointF
import android.os.Handler
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/StackEducationView.kt b/packages/SystemUI/src/com/android/systemui/bubbles/StackEducationView.kt
index 3e4c729d8315..216df2e1f402 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/StackEducationView.kt
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/StackEducationView.kt
@@ -24,21 +24,19 @@ import android.widget.LinearLayout
import android.widget.TextView
import com.android.internal.util.ContrastColorUtil
import com.android.systemui.Interpolators
-import com.android.systemui.Prefs
-import com.android.systemui.Prefs.Key.HAS_SEEN_BUBBLES_EDUCATION
import com.android.systemui.R
/**
* User education view to highlight the collapsed stack of bubbles.
* Shown only the first time a user taps the stack.
*/
-class StackEducationView constructor(context: Context) : LinearLayout(context){
+class StackEducationView constructor(context: Context) : LinearLayout(context) {
private val TAG = if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "BubbleStackEducationView"
else BubbleDebugConfig.TAG_BUBBLES
- private val ANIMATE_DURATION : Long = 200
- private val ANIMATE_DURATION_SHORT : Long = 40
+ private val ANIMATE_DURATION: Long = 200
+ private val ANIMATE_DURATION_SHORT: Long = 40
private val view by lazy { findViewById<View>(R.id.stack_education_layout) }
private val titleTextView by lazy { findViewById<TextView>(R.id.stack_education_title) }
@@ -47,7 +45,7 @@ class StackEducationView constructor(context: Context) : LinearLayout(context){
private var isHiding = false
init {
- LayoutInflater.from(context).inflate(R.layout.bubble_stack_user_education, this);
+ LayoutInflater.from(context).inflate(R.layout.bubble_stack_user_education, this)
visibility = View.GONE
elevation = resources.getDimensionPixelSize(R.dimen.bubble_elevation).toFloat()
@@ -93,7 +91,7 @@ class StackEducationView constructor(context: Context) : LinearLayout(context){
*
* @return true if user education was shown, false otherwise.
*/
- fun show(stackPosition: PointF) : Boolean{
+ fun show(stackPosition: PointF): Boolean {
if (visibility == VISIBLE) return false
setAlpha(0f)
@@ -129,6 +127,9 @@ class StackEducationView constructor(context: Context) : LinearLayout(context){
}
private fun setShouldShow(shouldShow: Boolean) {
- Prefs.putBoolean(context, HAS_SEEN_BUBBLES_EDUCATION, !shouldShow)
+ context.getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
+ .edit().putBoolean(PREF_STACK_EDUCATION, !shouldShow).apply()
}
-} \ No newline at end of file
+}
+
+const val PREF_STACK_EDUCATION: String = "HasSeenBubblesOnboarding" \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/TaskView.java b/packages/SystemUI/src/com/android/systemui/bubbles/TaskView.java
new file mode 100644
index 000000000000..524fa42af7d5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/TaskView.java
@@ -0,0 +1,308 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bubbles;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
+import android.graphics.Rect;
+import android.view.SurfaceControl;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import dalvik.system.CloseGuard;
+
+/**
+ * View that can display a task.
+ */
+// TODO: Place in com.android.wm.shell vs. com.android.wm.shell.bubbles on shell migration.
+public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
+ MultiWindowTaskListener.Listener {
+
+ public interface Listener {
+ /** Called when the container is ready for launching activities. */
+ default void onInitialized() {}
+
+ /** Called when the container can no longer launch activities. */
+ default void onReleased() {}
+
+ /** Called when a task is created inside the container. */
+ default void onTaskCreated(int taskId, ComponentName name) {}
+
+ /** Called when a task visibility changes. */
+ default void onTaskVisibilityChanged(int taskId, boolean visible) {}
+
+ /** Called when a task is about to be removed from the stack inside the container. */
+ default void onTaskRemovalStarted(int taskId) {}
+
+ /** Called when a task is created inside the container. */
+ default void onBackPressedOnTaskRoot(int taskId) {}
+ }
+
+ private final CloseGuard mGuard = CloseGuard.get();
+
+ private final MultiWindowTaskListener mMultiWindowTaskListener;
+
+ private ActivityManager.RunningTaskInfo mTaskInfo;
+ private WindowContainerToken mTaskToken;
+ private SurfaceControl mTaskLeash;
+ private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+ private boolean mSurfaceCreated;
+ private boolean mIsInitialized;
+ private Listener mListener;
+
+ private final Rect mTmpRect = new Rect();
+ private final Rect mTmpRootRect = new Rect();
+
+ public TaskView(Context context, MultiWindowTaskListener taskListener) {
+ super(context, null, 0, 0, true /* disableBackgroundLayer */);
+
+ mMultiWindowTaskListener = taskListener;
+ setUseAlpha();
+ getHolder().addCallback(this);
+ mGuard.open("release");
+ }
+
+ /**
+ * Only one listener may be set on the view, throws an exception otherwise.
+ */
+ public void setListener(Listener listener) {
+ if (mListener != null) {
+ throw new IllegalStateException(
+ "Trying to set a listener when one has already been set");
+ }
+ mListener = listener;
+ }
+
+ /**
+ * Launch an activity represented by {@link ShortcutInfo}.
+ * <p>The owner of this container must be allowed to access the shortcut information,
+ * as defined in {@link LauncherApps#hasShortcutHostPermission()} to use this method.
+ *
+ * @param shortcut the shortcut used to launch the activity.
+ * @param options options for the activity.
+ * @param sourceBounds the rect containing the source bounds of the clicked icon to open
+ * this shortcut.
+ */
+ public void startShortcutActivity(@NonNull ShortcutInfo shortcut,
+ @NonNull ActivityOptions options, @Nullable Rect sourceBounds) {
+ mMultiWindowTaskListener.setPendingListener(this);
+ prepareActivityOptions(options);
+ LauncherApps service = mContext.getSystemService(LauncherApps.class);
+ try {
+ service.startShortcut(shortcut, sourceBounds, options.toBundle());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Launch a new activity.
+ *
+ * @param pendingIntent Intent used to launch an activity.
+ * @param fillInIntent Additional Intent data, see {@link Intent#fillIn Intent.fillIn()}
+ * @param options options for the activity.
+ */
+ public void startActivity(@NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent,
+ @NonNull ActivityOptions options) {
+ mMultiWindowTaskListener.setPendingListener(this);
+ prepareActivityOptions(options);
+ try {
+ pendingIntent.send(mContext, 0 /* code */, fillInIntent,
+ null /* onFinished */, null /* handler */, null /* requiredPermission */,
+ options.toBundle());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void prepareActivityOptions(ActivityOptions options) {
+ options.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ options.setTaskAlwaysOnTop(true);
+ }
+
+ /**
+ * Call when view position or size has changed. Do not call when animating.
+ */
+ public void onLocationChanged() {
+ if (mTaskToken == null) {
+ return;
+ }
+ // Update based on the screen bounds
+ getBoundsOnScreen(mTmpRect);
+ getRootView().getBoundsOnScreen(mTmpRootRect);
+ if (!mTmpRootRect.contains(mTmpRect)) {
+ mTmpRect.offsetTo(0, 0);
+ }
+
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setBounds(mTaskToken, mTmpRect);
+ // TODO(b/151449487): Enable synchronization
+ mMultiWindowTaskListener.getTaskOrganizer().applyTransaction(wct);
+ }
+
+ /**
+ * Release this container if it is initialized.
+ */
+ public void release() {
+ performRelease();
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (mGuard != null) {
+ mGuard.warnIfOpen();
+ performRelease();
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+
+ private void performRelease() {
+ getHolder().removeCallback(this);
+ mMultiWindowTaskListener.removeListener(this);
+ resetTaskInfo();
+ mGuard.close();
+ if (mListener != null && mIsInitialized) {
+ mListener.onReleased();
+ mIsInitialized = false;
+ }
+ }
+
+ private void resetTaskInfo() {
+ mTaskInfo = null;
+ mTaskToken = null;
+ mTaskLeash = null;
+ }
+
+ private void updateTaskVisibility() {
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setHidden(mTaskToken, !mSurfaceCreated /* hidden */);
+ mMultiWindowTaskListener.getTaskOrganizer().applyTransaction(wct);
+ // TODO(b/151449487): Only call callback once we enable synchronization
+ if (mListener != null) {
+ mListener.onTaskVisibilityChanged(mTaskInfo.taskId, mSurfaceCreated);
+ }
+ }
+
+ @Override
+ public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo,
+ SurfaceControl leash) {
+ mTaskInfo = taskInfo;
+ mTaskToken = taskInfo.token;
+ mTaskLeash = leash;
+
+ if (mSurfaceCreated) {
+ // Surface is ready, so just reparent the task to this surface control
+ mTransaction.reparent(mTaskLeash, getSurfaceControl())
+ .show(mTaskLeash)
+ .apply();
+ } else {
+ // The surface has already been destroyed before the task has appeared, so go ahead and
+ // hide the task entirely
+ updateTaskVisibility();
+ }
+
+ // TODO: Synchronize show with the resize
+ onLocationChanged();
+ setResizeBackgroundColor(taskInfo.taskDescription.getBackgroundColor());
+
+ if (mListener != null) {
+ mListener.onTaskCreated(taskInfo.taskId, taskInfo.baseActivity);
+ }
+ }
+
+ @Override
+ public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
+ if (mTaskToken != null && mTaskToken.equals(taskInfo.token)) {
+ if (mListener != null) {
+ mListener.onTaskRemovalStarted(taskInfo.taskId);
+ }
+
+ // Unparent the task when this surface is destroyed
+ mTransaction.reparent(mTaskLeash, null).apply();
+ resetTaskInfo();
+ }
+ }
+
+ @Override
+ public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ mTaskInfo.taskDescription = taskInfo.taskDescription;
+ setResizeBackgroundColor(taskInfo.taskDescription.getBackgroundColor());
+ }
+
+ @Override
+ public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) {
+ if (mTaskToken != null && mTaskToken.equals(taskInfo.token)) {
+ if (mListener != null) {
+ mListener.onBackPressedOnTaskRoot(taskInfo.taskId);
+ }
+ }
+ }
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ mSurfaceCreated = true;
+ if (mListener != null && !mIsInitialized) {
+ mIsInitialized = true;
+ mListener.onInitialized();
+ }
+ if (mTaskToken == null) {
+ // Nothing to update, task is not yet available
+ return;
+ }
+ // Reparent the task when this surface is created
+ mTransaction.reparent(mTaskLeash, getSurfaceControl())
+ .show(mTaskLeash)
+ .apply();
+ updateTaskVisibility();
+ }
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+ if (mTaskToken == null) {
+ return;
+ }
+ onLocationChanged();
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ mSurfaceCreated = false;
+ if (mTaskToken == null) {
+ // Nothing to update, task is not yet available
+ return;
+ }
+
+ // Unparent the task when this surface is destroyed
+ mTransaction.reparent(mTaskLeash, null).apply();
+ updateTaskVisibility();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
index f2a4f159f959..7fdc01961aa5 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
@@ -32,8 +32,8 @@ import androidx.dynamicanimation.animation.SpringForce;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.util.animation.PhysicsAnimator;
-import com.android.systemui.util.magnetictarget.MagnetizedObject;
+import com.android.wm.shell.animation.PhysicsAnimator;
+import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
import com.google.android.collect.Sets;
@@ -58,6 +58,9 @@ public class ExpandedAnimationController
/** Duration of the expand/collapse target path animation. */
public static final int EXPAND_COLLAPSE_TARGET_ANIM_DURATION = 175;
+ /** Damping ratio for expand/collapse spring. */
+ private static final float DAMPING_RATIO_MEDIUM_LOW_BOUNCY = 0.65f;
+
/** Stiffness for the expand/collapse path-following animation. */
private static final int EXPAND_COLLAPSE_ANIM_STIFFNESS = 1000;
@@ -271,16 +274,14 @@ public class ExpandedAnimationController
// Then, draw a line across the screen to the bubble's resting position.
path.lineTo(getBubbleLeft(index), expandedY);
} else {
- final float sideMultiplier =
- mLayout.isFirstChildXLeftOfCenter(mCollapsePoint.x) ? -1 : 1;
- final float stackedX = mCollapsePoint.x + (sideMultiplier * index * mStackOffsetPx);
+ final float stackedX = mCollapsePoint.x;
// If we're collapsing, draw a line from the bubble's current position to the side
// of the screen where the bubble will be stacked.
path.lineTo(stackedX, expandedY);
// Then, draw a line down to the stack position.
- path.lineTo(stackedX, mCollapsePoint.y);
+ path.lineTo(stackedX, mCollapsePoint.y + index * mStackOffsetPx);
}
// The lead bubble should be the bubble with the longest distance to travel when we're
@@ -510,7 +511,7 @@ public class ExpandedAnimationController
@Override
SpringForce getSpringForce(DynamicAnimation.ViewProperty property, View view) {
return new SpringForce()
- .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
+ .setDampingRatio(DAMPING_RATIO_MEDIUM_LOW_BOUNCY)
.setStiffness(SpringForce.STIFFNESS_LOW);
}
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 e835ea206e59..12051241f049 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
@@ -36,9 +36,9 @@ import androidx.dynamicanimation.animation.SpringForce;
import com.android.systemui.R;
import com.android.systemui.bubbles.BubbleStackView;
-import com.android.systemui.util.FloatingContentCoordinator;
-import com.android.systemui.util.animation.PhysicsAnimator;
-import com.android.systemui.util.magnetictarget.MagnetizedObject;
+import com.android.wm.shell.animation.PhysicsAnimator;
+import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
import com.google.android.collect.Sets;
@@ -72,9 +72,9 @@ public class StackAnimationController extends
/**
* Values to use for the default {@link SpringForce} provided to the physics animation layout.
*/
- public static final int DEFAULT_STIFFNESS = 12000;
+ public static final int SPRING_TO_TOUCH_STIFFNESS = 12000;
public static final float IME_ANIMATION_STIFFNESS = SpringForce.STIFFNESS_LOW;
- private static final int FLING_FOLLOW_STIFFNESS = 20000;
+ private static final int CHAIN_STIFFNESS = 600;
public static final float DEFAULT_BOUNCINESS = 0.9f;
private final PhysicsAnimator.SpringConfig mAnimateOutSpringConfig =
@@ -629,7 +629,7 @@ public class StackAnimationController extends
public void moveStackFromTouch(float x, float y) {
// Begin the spring-to-touch catch up animation if needed.
if (mSpringToTouchOnNextMotionEvent) {
- springStack(x, y, DEFAULT_STIFFNESS);
+ springStack(x, y, SPRING_TO_TOUCH_STIFFNESS);
mSpringToTouchOnNextMotionEvent = false;
mFirstBubbleSpringingToTouch = true;
} else if (mFirstBubbleSpringingToTouch) {
@@ -744,15 +744,13 @@ public class StackAnimationController extends
@Override
float getOffsetForChainedPropertyAnimation(DynamicAnimation.ViewProperty property) {
- if (property.equals(DynamicAnimation.TRANSLATION_X)) {
+ if (property.equals(DynamicAnimation.TRANSLATION_Y)) {
// If we're in the dismiss target, have the bubbles pile on top of each other with no
// offset.
if (isStackStuckToTarget()) {
return 0f;
} else {
- // Offset to the left if we're on the left, or the right otherwise.
- return mLayout.isFirstChildXLeftOfCenter(mStackPosition.x)
- ? -mStackOffset : mStackOffset;
+ return mStackOffset;
}
} else {
return 0f;
@@ -762,14 +760,12 @@ public class StackAnimationController extends
@Override
SpringForce getSpringForce(DynamicAnimation.ViewProperty property, View view) {
final ContentResolver contentResolver = mLayout.getContext().getContentResolver();
- final float stiffness = Settings.Secure.getFloat(contentResolver, "bubble_stiffness",
- mIsMovingFromFlinging ? FLING_FOLLOW_STIFFNESS : DEFAULT_STIFFNESS /* default */);
final float dampingRatio = Settings.Secure.getFloat(contentResolver, "bubble_damping",
DEFAULT_BOUNCINESS);
return new SpringForce()
.setDampingRatio(dampingRatio)
- .setStiffness(stiffness);
+ .setStiffness(CHAIN_STIFFNESS);
}
@Override
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 9efc3c20f55a..6b5f237ac76f 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
@@ -19,13 +19,15 @@ package com.android.systemui.bubbles.dagger;
import android.app.INotificationManager;
import android.content.Context;
import android.content.pm.LauncherApps;
+import android.os.Handler;
import android.view.WindowManager;
+import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.bubbles.BubbleController;
-import com.android.systemui.bubbles.BubbleData;
-import com.android.systemui.bubbles.BubbleDataRepository;
+import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -39,7 +41,9 @@ import com.android.systemui.statusbar.notification.interruption.NotificationInte
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ZenModeController;
-import com.android.systemui.util.FloatingContentCoordinator;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.WindowManagerShellWrapper;
+import com.android.wm.shell.common.FloatingContentCoordinator;
import dagger.Module;
import dagger.Provides;
@@ -52,12 +56,11 @@ public interface BubbleModule {
*/
@SysUISingleton
@Provides
- static BubbleController newBubbleController(
+ static Bubbles newBubbleController(
Context context,
NotificationShadeWindowController notificationShadeWindowController,
StatusBarStateController statusBarStateController,
ShadeController shadeController,
- BubbleData data,
ConfigurationController configurationController,
NotificationInterruptStateProvider interruptionStateProvider,
ZenModeController zenModeController,
@@ -68,18 +71,20 @@ public interface BubbleModule {
FeatureFlags featureFlags,
DumpManager dumpManager,
FloatingContentCoordinator floatingContentCoordinator,
- BubbleDataRepository bubbleDataRepository,
SysUiState sysUiState,
INotificationManager notifManager,
IStatusBarService statusBarService,
WindowManager windowManager,
- LauncherApps launcherApps) {
- return new BubbleController(
+ WindowManagerShellWrapper windowManagerShellWrapper,
+ LauncherApps launcherApps,
+ UiEventLogger uiEventLogger,
+ @Main Handler mainHandler,
+ ShellTaskOrganizer organizer) {
+ return BubbleController.create(
context,
notificationShadeWindowController,
statusBarStateController,
shadeController,
- data,
null /* synchronizer */,
configurationController,
interruptionStateProvider,
@@ -91,11 +96,14 @@ public interface BubbleModule {
featureFlags,
dumpManager,
floatingContentCoordinator,
- bubbleDataRepository,
sysUiState,
notifManager,
statusBarService,
windowManager,
- launcherApps);
+ windowManagerShellWrapper,
+ launcherApps,
+ uiEventLogger,
+ mainHandler,
+ organizer);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubblePersistentRepository.kt b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubblePersistentRepository.kt
index f4479653d12e..ce0786d86460 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubblePersistentRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubblePersistentRepository.kt
@@ -18,16 +18,11 @@ package com.android.systemui.bubbles.storage
import android.content.Context
import android.util.AtomicFile
import android.util.Log
-import com.android.systemui.dagger.SysUISingleton
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
-import javax.inject.Inject
-@SysUISingleton
-class BubblePersistentRepository @Inject constructor(
- context: Context
-) {
+class BubblePersistentRepository(context: Context) {
private val bubbleFile: AtomicFile = AtomicFile(File(context.filesDir,
"overflow_bubbles.xml"), "overflow-bubbles")
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleVolatileRepository.kt b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleVolatileRepository.kt
index c6d57326357c..e0a7c7879f43 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleVolatileRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleVolatileRepository.kt
@@ -19,8 +19,6 @@ import android.content.pm.LauncherApps
import android.os.UserHandle
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.bubbles.ShortcutKey
-import com.android.systemui.dagger.SysUISingleton
-import javax.inject.Inject
private const val CAPACITY = 16
@@ -28,10 +26,7 @@ private const val CAPACITY = 16
* BubbleVolatileRepository holds the most updated snapshot of list of bubbles for in-memory
* manipulation.
*/
-@SysUISingleton
-class BubbleVolatileRepository @Inject constructor(
- private val launcherApps: LauncherApps
-) {
+class BubbleVolatileRepository(private val launcherApps: LauncherApps) {
/**
* An ordered set of bubbles based on their natural ordering.
*/
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 658f46e3bb96..2f0fd99337e5 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
@@ -16,7 +16,6 @@
package com.android.systemui.controls.controller
-import android.app.ActivityManager
import android.content.ComponentName
import android.content.Context
import android.os.IBinder
@@ -30,6 +29,7 @@ import android.util.Log
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.concurrency.DelayableExecutor
import dagger.Lazy
import java.util.concurrent.atomic.AtomicBoolean
@@ -40,7 +40,8 @@ import javax.inject.Inject
open class ControlsBindingControllerImpl @Inject constructor(
private val context: Context,
@Background private val backgroundExecutor: DelayableExecutor,
- private val lazyController: Lazy<ControlsController>
+ private val lazyController: Lazy<ControlsController>,
+ userTracker: UserTracker
) : ControlsBindingController {
companion object {
@@ -56,7 +57,7 @@ open class ControlsBindingControllerImpl @Inject constructor(
}
}
- private var currentUser = UserHandle.of(ActivityManager.getCurrentUser())
+ private var currentUser = userTracker.userHandle
override val currentUserId: Int
get() = currentUser.identifier
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 495872f3433d..d3d24be0ad9d 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -16,7 +16,6 @@
package com.android.systemui.controls.controller
-import android.app.ActivityManager
import android.app.PendingIntent
import android.app.backup.BackupManager
import android.content.BroadcastReceiver
@@ -46,6 +45,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dump.DumpManager
import com.android.systemui.globalactions.GlobalActionsDialog
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.concurrency.DelayableExecutor
import java.io.FileDescriptor
import java.io.PrintWriter
@@ -56,14 +56,15 @@ import javax.inject.Inject
@SysUISingleton
class ControlsControllerImpl @Inject constructor (
- private val context: Context,
- @Background private val executor: DelayableExecutor,
- private val uiController: ControlsUiController,
- private val bindingController: ControlsBindingController,
- private val listingController: ControlsListingController,
- private val broadcastDispatcher: BroadcastDispatcher,
- optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>,
- dumpManager: DumpManager
+ private val context: Context,
+ @Background private val executor: DelayableExecutor,
+ private val uiController: ControlsUiController,
+ private val bindingController: ControlsBindingController,
+ private val listingController: ControlsListingController,
+ private val broadcastDispatcher: BroadcastDispatcher,
+ optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>,
+ dumpManager: DumpManager,
+ userTracker: UserTracker
) : Dumpable, ControlsController {
companion object {
@@ -85,7 +86,7 @@ class ControlsControllerImpl @Inject constructor (
private var seedingInProgress = false
private val seedingCallbacks = mutableListOf<Consumer<Boolean>>()
- private var currentUser = UserHandle.of(ActivityManager.getCurrentUser())
+ private var currentUser = userTracker.userHandle
override val currentUserId
get() = currentUser.identifier
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
index 0d4439fe8ccb..2d76ff2774d6 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
@@ -16,7 +16,6 @@
package com.android.systemui.controls.management
-import android.app.ActivityManager
import android.content.ComponentName
import android.content.Context
import android.content.pm.ServiceInfo
@@ -29,6 +28,7 @@ import com.android.settingslib.widget.CandidateInfo
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.settings.UserTracker
import java.util.concurrent.Executor
import java.util.concurrent.atomic.AtomicInteger
import javax.inject.Inject
@@ -56,14 +56,16 @@ private fun createServiceListing(context: Context): ServiceListing {
class ControlsListingControllerImpl @VisibleForTesting constructor(
private val context: Context,
@Background private val backgroundExecutor: Executor,
- private val serviceListingBuilder: (Context) -> ServiceListing
+ private val serviceListingBuilder: (Context) -> ServiceListing,
+ userTracker: UserTracker
) : ControlsListingController {
@Inject
- constructor(context: Context, executor: Executor): this(
+ constructor(context: Context, executor: Executor, userTracker: UserTracker): this(
context,
executor,
- ::createServiceListing
+ ::createServiceListing,
+ userTracker
)
private var serviceListing = serviceListingBuilder(context)
@@ -78,7 +80,7 @@ class ControlsListingControllerImpl @VisibleForTesting constructor(
private var availableServices = emptyList<ServiceInfo>()
private var userChangeInProgress = AtomicInteger(0)
- override var currentUserId = ActivityManager.getCurrentUser()
+ override var currentUserId = userTracker.userId
private set
private val serviceListingCallback = ServiceListing.Callback {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
index 28bcf3a35117..d13e194a0d44 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
@@ -19,13 +19,13 @@ package com.android.systemui.dagger;
import android.app.Activity;
import com.android.systemui.ForegroundServicesDialog;
-import com.android.systemui.bubbles.BubbleOverflowActivity;
import com.android.systemui.keyguard.WorkLockActivity;
import com.android.systemui.screenrecord.ScreenRecordDialog;
import com.android.systemui.settings.BrightnessDialog;
import com.android.systemui.tuner.TunerActivity;
import com.android.systemui.usb.UsbDebuggingActivity;
import com.android.systemui.usb.UsbDebuggingSecondaryUserActivity;
+import com.android.systemui.user.CreateUserActivity;
import dagger.Binds;
import dagger.Module;
@@ -67,12 +67,6 @@ public abstract class DefaultActivityBinder {
@ClassKey(ScreenRecordDialog.class)
public abstract Activity bindScreenRecordDialog(ScreenRecordDialog activity);
- /** Inject into BubbleOverflowActivity. */
- @Binds
- @IntoMap
- @ClassKey(BubbleOverflowActivity.class)
- public abstract Activity bindBubbleOverflowActivity(BubbleOverflowActivity activity);
-
/** Inject into UsbDebuggingActivity. */
@Binds
@IntoMap
@@ -85,4 +79,10 @@ public abstract class DefaultActivityBinder {
@ClassKey(UsbDebuggingSecondaryUserActivity.class)
public abstract Activity bindUsbDebuggingSecondaryUserActivity(
UsbDebuggingSecondaryUserActivity activity);
+
+ /** Inject into CreateUserActivity. */
+ @Binds
+ @IntoMap
+ @ClassKey(CreateUserActivity.class)
+ public abstract Activity bindCreateUserActivity(CreateUserActivity activity);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
index eb431274b8a3..cb90b6114396 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
@@ -66,6 +66,8 @@ import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.shared.plugins.PluginManagerImpl;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
+import com.android.systemui.shared.system.WindowManagerWrapper;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.phone.AutoHideController;
@@ -262,6 +264,13 @@ public class DependencyProvider {
return ActivityManagerWrapper.getInstance();
}
+ /** */
+ @Provides
+ @SysUISingleton
+ public TaskStackChangeListeners provideTaskStackChangeListeners() {
+ return TaskStackChangeListeners.getInstance();
+ }
+
/** Provides and initializes the {#link BroadcastDispatcher} for SystemUI */
@Provides
@SysUISingleton
@@ -313,6 +322,13 @@ public class DependencyProvider {
/** */
@Provides
+ public WindowManagerWrapper providesWindowManagerWrapper() {
+ return WindowManagerWrapper.getInstance();
+ }
+
+ /** */
+ @Provides
+ @SysUISingleton
public SystemActions providesSystemActions(Context context) {
return new SystemActions(context);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 8f4e738e5a5f..63d9a831b33f 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -41,8 +41,10 @@ import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfC
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.policy.dagger.SmartRepliesInflationModule;
import com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule;
import com.android.systemui.tuner.dagger.TunerModule;
+import com.android.systemui.user.UserModule;
import com.android.systemui.util.concurrency.SysUIConcurrencyModule;
import com.android.systemui.util.dagger.UtilModule;
import com.android.systemui.util.sensors.SensorModule;
@@ -73,19 +75,23 @@ import dagger.Provides;
SensorModule.class,
SettingsModule.class,
SettingsUtilModule.class,
+ SmartRepliesInflationModule.class,
StatusBarPolicyModule.class,
SysUIConcurrencyModule.class,
TunerModule.class,
+ UserModule.class,
UtilModule.class,
VolumeModule.class
},
- subcomponents = {StatusBarComponent.class,
- NotificationRowComponent.class,
- DozeComponent.class,
- ExpandableNotificationRowComponent.class,
- KeyguardBouncerComponent.class,
- NotificationShelfComponent.class,
- FragmentService.FragmentCreator.class})
+ subcomponents = {
+ StatusBarComponent.class,
+ NotificationRowComponent.class,
+ DozeComponent.class,
+ ExpandableNotificationRowComponent.class,
+ KeyguardBouncerComponent.class,
+ NotificationShelfComponent.class,
+ FragmentService.FragmentCreator.class
+ })
public abstract class SystemUIModule {
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index 424a8246b278..f470a6b55b76 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -123,6 +123,14 @@ public class DozeLog implements Dumpable {
}
/**
+ * Appends dozing event to the logs
+ * @param suppressed true if dozing is suppressed
+ */
+ public void traceDozingSuppressed(boolean suppressed) {
+ mLogger.logDozingSuppressed(suppressed);
+ }
+
+ /**
* Appends fling event to the logs
*/
public void traceFling(boolean expand, boolean aboveThreshold, boolean thresholdNeeded,
@@ -198,6 +206,22 @@ public class DozeLog implements Dumpable {
}
/**
+ * Appends doze state changed sent to all DozeMachine parts event to the logs
+ * @param state new DozeMachine state
+ */
+ public void traceDozeStateSendComplete(DozeMachine.State state) {
+ mLogger.logStateChangedSent(state);
+ }
+
+ /**
+ * Appends display state changed event to the logs
+ * @param displayState new DozeMachine state
+ */
+ public void traceDisplayState(int displayState) {
+ mLogger.logDisplayStateChanged(displayState);
+ }
+
+ /**
* Appends wake-display event to the logs.
* @param wake if we're waking up or sleeping.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
index 732745a1158b..0c9e14352946 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
@@ -64,6 +64,14 @@ class DozeLogger @Inject constructor(
})
}
+ fun logDozingSuppressed(isDozingSuppressed: Boolean) {
+ buffer.log(TAG, INFO, {
+ bool1 = isDozingSuppressed
+ }, {
+ "DozingSuppressed=$bool1"
+ })
+ }
+
fun logFling(
expand: Boolean,
aboveThreshold: Boolean,
@@ -143,6 +151,22 @@ class DozeLogger @Inject constructor(
})
}
+ fun logStateChangedSent(state: DozeMachine.State) {
+ buffer.log(TAG, INFO, {
+ str1 = state.name
+ }, {
+ "Doze state sent to all DozeMachineParts stateSent=$str1"
+ })
+ }
+
+ fun logDisplayStateChanged(displayState: Int) {
+ buffer.log(TAG, INFO, {
+ int1 = displayState
+ }, {
+ "Display state changed to $int1"
+ })
+ }
+
fun logWakeDisplay(isAwake: Boolean) {
buffer.log(TAG, DEBUG, {
bool1 = isAwake
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
index d5820f3e05e4..befb648152d4 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
@@ -205,6 +205,7 @@ public class DozeMachine {
}
void onScreenState(int state) {
+ mDozeLog.traceDisplayState(state);
for (Part part : mParts) {
part.onScreenState(state);
}
@@ -308,6 +309,7 @@ public class DozeMachine {
for (Part p : mParts) {
p.transitionTo(oldState, newState);
}
+ mDozeLog.traceDozeStateSendComplete(newState);
switch (newState) {
case FINISH:
@@ -411,6 +413,7 @@ public class DozeMachine {
pw.print(" state="); pw.println(mState);
pw.print(" wakeLockHeldForCurrentState="); pw.println(mWakeLockHeldForCurrentState);
pw.print(" wakeLock="); pw.println(mWakeLock);
+ pw.print(" isDozeSuppressed="); pw.println(mDozeHost.isDozeSuppressed());
pw.println("Parts:");
for (Part p : mParts) {
p.dump(pw);
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
index 5aeb8df2028d..92494cf5b546 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
@@ -35,6 +35,7 @@ import com.android.systemui.doze.dagger.DozeScope;
import com.android.systemui.doze.dagger.WrappedService;
import com.android.systemui.util.sensors.AsyncSensorManager;
+import java.io.PrintWriter;
import java.util.Optional;
import javax.inject.Inject;
@@ -221,4 +222,9 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi
mDebugBrightnessBucket = intent.getIntExtra(BRIGHTNESS_BUCKET, -1);
updateBrightnessAndReady(false /* force */);
}
+
+ /** Dump current state */
+ public void dump(PrintWriter pw) {
+ pw.println("DozeScreenBrightnessSensorRegistered=" + mRegistered);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeScope.java b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeScope.java
index 7a8b8166a969..435859afc03f 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeScope.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeScope.java
@@ -24,7 +24,7 @@ import java.lang.annotation.Retention;
import javax.inject.Scope;
/**
- * Scope annotation for singleton items within the StatusBarComponent.
+ * Scope annotation for singleton items within the DozeComponent.
*/
@Documented
@Retention(RUNTIME)
diff --git a/packages/SystemUI/src/com/android/systemui/emergency/EmergencyGesture.java b/packages/SystemUI/src/com/android/systemui/emergency/EmergencyGesture.java
new file mode 100644
index 000000000000..dccb24dfe21e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/emergency/EmergencyGesture.java
@@ -0,0 +1,40 @@
+/*
+ * 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.emergency;
+
+/**
+ * Constants for the Emergency gesture.
+ *
+ * TODO (b/169175022) Update classname and docs when feature name is locked
+ */
+public final class EmergencyGesture {
+
+ /**
+ * Launches the emergency flow.
+ *
+ * <p>The emergency flow is triggered by the Emergency gesture. By default the flow will call
+ * local emergency services, though OEMs can customize the flow.
+ *
+ * <p>This action can only be triggered by System UI through the emergency gesture.
+ *
+ * <p>TODO (b/169175022) Update action name and docs when feature name is locked
+ */
+ public static final String ACTION_LAUNCH_EMERGENCY =
+ "com.android.systemui.action.LAUNCH_EMERGENCY";
+
+ private EmergencyGesture() {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index daef2c506ad3..5f726cd1e1f9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -38,6 +38,7 @@ import android.provider.Settings;
import android.service.notification.ZenModeConfig;
import android.text.TextUtils;
import android.text.style.StyleSpan;
+import android.util.Log;
import androidx.core.graphics.drawable.IconCompat;
import androidx.slice.Slice;
@@ -52,6 +53,8 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.SystemUIAppComponentFactory;
+import com.android.systemui.SystemUIFactory;
+import com.android.systemui.dagger.SysUIComponent;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.StatusBarState;
@@ -62,6 +65,8 @@ import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.util.wakelock.SettableWakeLock;
import com.android.systemui.util.wakelock.WakeLock;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
@@ -80,6 +85,8 @@ public class KeyguardSliceProvider extends SliceProvider implements
NotificationMediaManager.MediaListener, StatusBarStateController.StateListener,
SystemUIAppComponentFactory.ContextInitializer {
+ private static final String TAG = "KgdSliceProvider";
+
private static final StyleSpan BOLD_STYLE = new StyleSpan(Typeface.BOLD);
public static final String KEYGUARD_SLICE_URI = "content://com.android.systemui.keyguard/main";
private static final String KEYGUARD_HEADER_URI =
@@ -310,7 +317,25 @@ public class KeyguardSliceProvider extends SliceProvider implements
mDatePattern = getContext().getString(R.string.system_ui_aod_date_pattern);
mPendingIntent = PendingIntent.getActivity(getContext(), 0,
new Intent(getContext(), KeyguardSliceProvider.class), 0);
- mMediaManager.addCallback(this);
+ try {
+ //TODO(b/168778439): Remove this whole try catch. This is for debugging in dogfood.
+ mMediaManager.addCallback(this);
+ } catch (NullPointerException e) {
+ // We are sometimes failing to set the media manager. Why?
+ Log.w(TAG, "Failed to setup mMediaManager. Trying again.");
+ SysUIComponent rootComponent = SystemUIFactory.getInstance().getSysUIComponent();
+ try {
+ Method injectMethod = rootComponent.getClass()
+ .getMethod("inject", getClass());
+ injectMethod.invoke(rootComponent, this);
+ Log.w("TAG", "mMediaManager is now: " + mMediaManager);
+ } catch (NoSuchMethodException ex) {
+ Log.e(TAG, "Failed to find inject method for KeyguardSliceProvider", ex);
+ } catch (IllegalAccessException | InvocationTargetException ex) {
+ Log.e(TAG, "Failed to call inject", ex);
+ }
+ throw e;
+ }
mStatusBarStateController.addCallback(this);
mNextAlarmController.addCallback(this);
mZenModeController.addCallback(this);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/Lifecycle.java b/packages/SystemUI/src/com/android/systemui/keyguard/Lifecycle.java
index 1b20cfbc4e55..3da6caf31968 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/Lifecycle.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/Lifecycle.java
@@ -16,7 +16,10 @@
package com.android.systemui.keyguard;
+import androidx.annotation.NonNull;
+
import java.util.ArrayList;
+import java.util.Objects;
import java.util.function.Consumer;
/**
@@ -26,8 +29,8 @@ public class Lifecycle<T> {
private ArrayList<T> mObservers = new ArrayList<>();
- public void addObserver(T observer) {
- mObservers.add(observer);
+ public void addObserver(@NonNull T observer) {
+ mObservers.add(Objects.requireNonNull(observer));
}
public void removeObserver(T observer) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
index c281ece59c5a..75851102bc4a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
@@ -30,8 +30,8 @@ import android.os.UserHandle;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
public class WorkLockActivityController {
private static final String TAG = WorkLockActivityController.class.getSimpleName();
@@ -40,16 +40,16 @@ public class WorkLockActivityController {
private final IActivityTaskManager mIatm;
public WorkLockActivityController(Context context) {
- this(context, ActivityManagerWrapper.getInstance(), ActivityTaskManager.getService());
+ this(context, TaskStackChangeListeners.getInstance(), ActivityTaskManager.getService());
}
@VisibleForTesting
WorkLockActivityController(
- Context context, ActivityManagerWrapper am, IActivityTaskManager iAtm) {
+ Context context, TaskStackChangeListeners tscl, IActivityTaskManager iAtm) {
mContext = context;
mIatm = iAtm;
- am.registerTaskStackListener(mLockListener);
+ tscl.registerTaskStackListener(mLockListener);
}
private void startWorkChallengeInTask(int taskId, int userId) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 9d8e73a0ff47..e50fd6a96b5c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -134,7 +134,7 @@ public class KeyguardModule {
// Cameras that support "self illumination," via IR for example, don't need low light
// environment mitigation.
- boolean needsLowLightMitigation = faceManager.getSensorProperties().stream()
+ boolean needsLowLightMitigation = faceManager.getSensorPropertiesInternal().stream()
.anyMatch((properties) -> !properties.supportsSelfIllumination);
if (!needsLowLightMitigation) {
return Optional.empty();
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 6db408659c96..e3ee2a10821b 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -108,6 +108,18 @@ public class LogModule {
return buffer;
}
+ /** Provides a logging buffer for all logs related to Toasts shown by SystemUI. */
+ @Provides
+ @SysUISingleton
+ @ToastLog
+ public static LogBuffer provideToastLogBuffer(
+ LogcatEchoTracker bufferFilter,
+ DumpManager dumpManager) {
+ LogBuffer buffer = new LogBuffer("ToastLog", 50, 10, bufferFilter);
+ buffer.attach(dumpManager);
+ return buffer;
+ }
+
/** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/ToastLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/ToastLog.java
new file mode 100644
index 000000000000..8671dbfdf1fe
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/ToastLog.java
@@ -0,0 +1,33 @@
+/*
+ * 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 ToastLog-related messages. */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface ToastLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaBrowserFactory.java b/packages/SystemUI/src/com/android/systemui/media/MediaBrowserFactory.java
new file mode 100644
index 000000000000..aca033e99623
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaBrowserFactory.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.media.browse.MediaBrowser;
+import android.os.Bundle;
+
+import javax.inject.Inject;
+
+/**
+ * Testable wrapper around {@link MediaBrowser} constructor
+ */
+public class MediaBrowserFactory {
+ private final Context mContext;
+
+ @Inject
+ public MediaBrowserFactory(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Creates a new MediaBrowser
+ *
+ * @param serviceComponent
+ * @param callback
+ * @param rootHints
+ * @return
+ */
+ public MediaBrowser create(ComponentName serviceComponent,
+ MediaBrowser.ConnectionCallback callback, Bundle rootHints) {
+ return new MediaBrowser(mContext, serviceComponent, callback, rootHints);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index f150381f4070..1beb875af70c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -174,7 +174,7 @@ class MediaCarouselController @Inject constructor(
mediaManager.addListener(object : MediaDataManager.Listener {
override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
addOrUpdatePlayer(key, oldKey, data)
- val canRemove = data.isPlaying?.let { !it } ?: data.isClearable
+ val canRemove = data.isPlaying?.let { !it } ?: data.isClearable && !data.active
if (canRemove && !Utils.useMediaResumption(context)) {
// This view isn't playing, let's remove this! This happens e.g when
// dismissing/timing out a view. We still have the data around because
@@ -250,13 +250,13 @@ class MediaCarouselController @Inject constructor(
val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT)
newPlayer.view?.player?.setLayoutParams(lp)
- newPlayer.bind(data)
+ newPlayer.bind(data, key)
newPlayer.setListening(currentlyExpanded)
MediaPlayerData.addMediaPlayer(key, data, newPlayer)
updatePlayerToState(newPlayer, noAnimation = true)
reorderAllPlayers()
} else {
- existingPlayer.bind(data)
+ existingPlayer.bind(data, key)
MediaPlayerData.addMediaPlayer(key, data, existingPlayer)
if (visualStabilityManager.isReorderingAllowed) {
reorderAllPlayers()
@@ -274,7 +274,7 @@ class MediaCarouselController @Inject constructor(
}
}
- private fun removePlayer(key: String) {
+ private fun removePlayer(key: String, dismissMediaData: Boolean = true) {
val removed = MediaPlayerData.removeMediaPlayer(key)
removed?.apply {
mediaCarouselScrollHandler.onPrePlayerRemoved(removed)
@@ -283,13 +283,16 @@ class MediaCarouselController @Inject constructor(
mediaCarouselScrollHandler.onPlayersChanged()
updatePageIndicator()
- // Inform the media manager of a potentially late dismissal
- mediaManager.dismissMediaData(key, 0L)
+ if (dismissMediaData) {
+ // Inform the media manager of a potentially late dismissal
+ mediaManager.dismissMediaData(key, 0L)
+ }
}
}
private fun recreatePlayers() {
MediaPlayerData.mediaData().forEach { (key, data) ->
+ removePlayer(key, dismissMediaData = false)
addOrUpdatePlayer(key = key, oldKey = null, data = data)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
index 486399979db7..d80aafb714d3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
@@ -32,7 +32,7 @@ import com.android.systemui.qs.PageIndicator
import com.android.systemui.R
import com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS
import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.util.animation.PhysicsAnimator
+import com.android.wm.shell.animation.PhysicsAnimator
import com.android.systemui.util.concurrency.DelayableExecutor
private const val FLING_SLOP = 1000000
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index e55678dc986b..5b096ea363b6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -42,10 +42,10 @@ import androidx.annotation.UiThread;
import androidx.constraintlayout.widget.ConstraintSet;
import com.android.settingslib.Utils;
-import com.android.settingslib.media.MediaOutputSliceConstants;
import com.android.settingslib.widget.AdaptiveIcon;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
import com.android.systemui.util.animation.TransitionLayout;
@@ -82,6 +82,7 @@ public class MediaControlPanel {
private Context mContext;
private PlayerViewHolder mViewHolder;
+ private String mKey;
private MediaViewController mMediaViewController;
private MediaSession.Token mToken;
private MediaController mController;
@@ -92,7 +93,7 @@ public class MediaControlPanel {
private int mAlbumArtRadius;
// This will provide the corners for the album art.
private final ViewOutlineProvider mViewOutlineProvider;
-
+ private final MediaOutputDialogFactory mMediaOutputDialogFactory;
/**
* Initialize a new control panel
* @param context
@@ -103,7 +104,8 @@ public class MediaControlPanel {
public MediaControlPanel(Context context, @Background Executor backgroundExecutor,
ActivityStarter activityStarter, MediaViewController mediaViewController,
SeekBarViewModel seekBarViewModel, Lazy<MediaDataManager> lazyMediaDataManager,
- KeyguardDismissUtil keyguardDismissUtil) {
+ KeyguardDismissUtil keyguardDismissUtil, MediaOutputDialogFactory
+ mediaOutputDialogFactory) {
mContext = context;
mBackgroundExecutor = backgroundExecutor;
mActivityStarter = activityStarter;
@@ -111,6 +113,7 @@ public class MediaControlPanel {
mMediaViewController = mediaViewController;
mMediaDataManagerLazy = lazyMediaDataManager;
mKeyguardDismissUtil = keyguardDismissUtil;
+ mMediaOutputDialogFactory = mediaOutputDialogFactory;
loadDimens();
mViewOutlineProvider = new ViewOutlineProvider() {
@@ -206,10 +209,11 @@ public class MediaControlPanel {
/**
* Bind this view based on the data given
*/
- public void bind(@NonNull MediaData data) {
+ public void bind(@NonNull MediaData data, String key) {
if (mViewHolder == null) {
return;
}
+ mKey = key;
MediaSession.Token token = data.getToken();
mBackgroundColor = data.getBackgroundColor();
if (mToken == null || !mToken.equals(token)) {
@@ -249,10 +253,9 @@ public class MediaControlPanel {
// App icon
ImageView appIcon = mViewHolder.getAppIcon();
if (data.getAppIcon() != null) {
- appIcon.setImageDrawable(data.getAppIcon());
+ appIcon.setImageIcon(data.getAppIcon());
} else {
- Drawable iconDrawable = mContext.getDrawable(R.drawable.ic_music_note);
- appIcon.setImageDrawable(iconDrawable);
+ appIcon.setImageResource(R.drawable.ic_music_note);
}
// Song name
@@ -272,13 +275,7 @@ public class MediaControlPanel {
setVisibleAndAlpha(collapsedSet, R.id.media_seamless, true /*visible */);
setVisibleAndAlpha(expandedSet, R.id.media_seamless, true /*visible */);
mViewHolder.getSeamless().setOnClickListener(v -> {
- final Intent intent = new Intent()
- .setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT)
- .putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME,
- data.getPackageName())
- .putExtra(MediaOutputSliceConstants.KEY_MEDIA_SESSION_TOKEN, mToken);
- mActivityStarter.startActivity(intent, false, true /* dismissShade */,
- Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ mMediaOutputDialogFactory.create(data.getPackageName(), true);
});
ImageView iconView = mViewHolder.getSeamlessIcon();
@@ -330,7 +327,7 @@ public class MediaControlPanel {
int actionId = ACTION_IDS[i];
final ImageButton button = mViewHolder.getAction(actionId);
MediaAction mediaAction = actionIcons.get(i);
- button.setImageDrawable(mediaAction.getDrawable());
+ button.setImageIcon(mediaAction.getIcon());
button.setContentDescription(mediaAction.getContentDescription());
Runnable action = mediaAction.getAction();
@@ -359,10 +356,10 @@ public class MediaControlPanel {
// Dismiss
mViewHolder.getDismiss().setOnClickListener(v -> {
- if (data.getNotificationKey() != null) {
+ if (mKey != null) {
closeGuts();
mKeyguardDismissUtil.executeWhenUnlocked(() -> {
- mMediaDataManagerLazy.get().dismissMediaData(data.getNotificationKey(),
+ mMediaDataManagerLazy.get().dismissMediaData(mKey,
MediaViewController.GUTS_ANIMATION_DURATION + 100);
return true;
}, /* requiresShadeOpen */ true);
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
index 40a879abde34..0ed96eeac402 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
@@ -33,7 +33,7 @@ data class MediaData(
/**
* Icon shown on player, close to app name.
*/
- val appIcon: Drawable?,
+ val appIcon: Icon?,
/**
* Artist name.
*/
@@ -109,7 +109,7 @@ data class MediaData(
/** State of a media action. */
data class MediaAction(
- val drawable: Drawable?,
+ val icon: Icon?,
val action: Runnable?,
val contentDescription: CharSequence?
)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index cb6b22c2321f..6f6ee4c8091d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -455,7 +455,7 @@ class MediaDataManager(
val app = builder.loadHeaderAppName()
// App Icon
- val smallIconDrawable: Drawable = sbn.notification.smallIcon.loadDrawable(context)
+ val smallIcon = sbn.notification.smallIcon
// Song name
var song: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE)
@@ -501,8 +501,13 @@ class MediaDataManager(
} else {
null
}
+ val mediaActionIcon = if (action.getIcon()?.getType() == Icon.TYPE_RESOURCE) {
+ Icon.createWithResource(packageContext, action.getIcon()!!.getResId())
+ } else {
+ action.getIcon()
+ }
val mediaAction = MediaAction(
- action.getIcon().loadDrawable(packageContext),
+ mediaActionIcon,
runnable,
action.title)
actionIcons.add(mediaAction)
@@ -518,7 +523,7 @@ class MediaDataManager(
val hasCheckedForResume = mediaEntries[key]?.hasCheckedForResume == true
val active = mediaEntries[key]?.active ?: true
onMediaDataLoaded(key, oldKey, MediaData(sbn.normalizedUserId, true, bgColor, app,
- smallIconDrawable, artist, song, artWorkIcon, actionIcons,
+ smallIcon, artist, song, artWorkIcon, actionIcons,
actionsToShowCollapsed, sbn.packageName, token, notif.contentIntent, null,
active, resumeAction = resumeAction, isLocalSession = isLocalSession,
notificationKey = key, hasCheckedForResume = hasCheckedForResume,
@@ -572,7 +577,7 @@ class MediaDataManager(
val source = ImageDecoder.createSource(context.getContentResolver(), uri)
return try {
ImageDecoder.decodeBitmap(source) {
- decoder, info, source -> decoder.isMutableRequired = true
+ decoder, info, source -> decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE
}
} catch (e: IOException) {
Log.e(TAG, "Unable to load bitmap", e)
@@ -612,7 +617,7 @@ class MediaDataManager(
private fun getResumeMediaAction(action: Runnable): MediaAction {
return MediaAction(
- context.getDrawable(R.drawable.lb_ic_play),
+ Icon.createWithResource(context, R.drawable.lb_ic_play),
action,
context.getString(R.string.controls_media_resume)
)
@@ -631,13 +636,13 @@ class MediaDataManager(
Assert.isMainThread()
val removed = mediaEntries.remove(key)
if (useMediaResumption && removed?.resumeAction != null &&
- !isBlockedFromResume(removed?.packageName)) {
+ !isBlockedFromResume(removed.packageName)) {
Log.d(TAG, "Not removing $key because resumable")
// Move to resume key (aka package name) if that key doesn't already exist.
val resumeAction = getResumeMediaAction(removed.resumeAction!!)
val updated = removed.copy(token = null, actions = listOf(resumeAction),
actionsToShowInCompact = listOf(0), active = false, resumption = true)
- val pkg = removed?.packageName
+ val pkg = removed.packageName
val migrate = mediaEntries.put(pkg, updated) == null
// Notify listeners of "new" controls when migrating or removed and update when not
if (migrate) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
index 2bc908be055c..a993d00df01e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
@@ -16,7 +16,6 @@
package com.android.systemui.media
-import android.content.Context
import android.media.MediaRouter2Manager
import android.media.session.MediaController
import androidx.annotation.AnyThread
@@ -25,7 +24,6 @@ import androidx.annotation.WorkerThread
import com.android.settingslib.media.LocalMediaManager
import com.android.settingslib.media.MediaDevice
import com.android.systemui.Dumpable
-import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
@@ -34,11 +32,13 @@ import java.io.PrintWriter
import java.util.concurrent.Executor
import javax.inject.Inject
+private const val PLAYBACK_TYPE_UNKNOWN = 0
+
/**
* Provides information about the route (ie. device) where playback is occurring.
*/
class MediaDeviceManager @Inject constructor(
- private val context: Context,
+ private val controllerFactory: MediaControllerFactory,
private val localMediaManagerFactory: LocalMediaManagerFactory,
private val mr2manager: MediaRouter2Manager,
@Main private val fgExecutor: Executor,
@@ -72,7 +72,7 @@ class MediaDeviceManager @Inject constructor(
if (entry == null || entry?.token != data.token) {
entry?.stop()
val controller = data.token?.let {
- MediaController(context, it)
+ controllerFactory.create(it)
}
entry = Entry(key, oldKey, controller,
localMediaManagerFactory.create(data.packageName))
@@ -123,11 +123,12 @@ class MediaDeviceManager @Inject constructor(
val oldKey: String?,
val controller: MediaController?,
val localMediaManager: LocalMediaManager
- ) : LocalMediaManager.DeviceCallback {
+ ) : LocalMediaManager.DeviceCallback, MediaController.Callback() {
val token
get() = controller?.sessionToken
private var started = false
+ private var playbackType = PLAYBACK_TYPE_UNKNOWN
private var current: MediaDevice? = null
set(value) {
if (!started || value != field) {
@@ -142,6 +143,8 @@ class MediaDeviceManager @Inject constructor(
fun start() = bgExecutor.execute {
localMediaManager.registerCallback(this)
localMediaManager.startScan()
+ playbackType = controller?.playbackInfo?.playbackType ?: PLAYBACK_TYPE_UNKNOWN
+ controller?.registerCallback(this)
updateCurrent()
started = true
}
@@ -149,22 +152,37 @@ class MediaDeviceManager @Inject constructor(
@AnyThread
fun stop() = bgExecutor.execute {
started = false
+ controller?.unregisterCallback(this)
localMediaManager.stopScan()
localMediaManager.unregisterCallback(this)
}
fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) {
- val route = controller?.let {
+ val routingSession = controller?.let {
mr2manager.getRoutingSessionForMediaController(it)
}
+ val selectedRoutes = routingSession?.let {
+ mr2manager.getSelectedRoutes(it)
+ }
with(pw) {
println(" current device is ${current?.name}")
val type = controller?.playbackInfo?.playbackType
- println(" PlaybackType=$type (1 for local, 2 for remote)")
- println(" route=$route")
+ println(" PlaybackType=$type (1 for local, 2 for remote) cached=$playbackType")
+ println(" routingSession=$routingSession")
+ println(" selectedRoutes=$selectedRoutes")
}
}
+ @WorkerThread
+ override fun onAudioInfoChanged(info: MediaController.PlaybackInfo?) {
+ val newPlaybackType = info?.playbackType ?: PLAYBACK_TYPE_UNKNOWN
+ if (newPlaybackType == playbackType) {
+ return
+ }
+ playbackType = newPlaybackType
+ updateCurrent()
+ }
+
override fun onDeviceListUpdate(devices: List<MediaDevice>?) = bgExecutor.execute {
updateCurrent()
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
index 5b59214afdc9..5c1c60c5b07e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
@@ -28,6 +28,7 @@ import android.os.UserHandle
import android.provider.Settings
import android.service.media.MediaBrowserService
import android.util.Log
+import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
@@ -47,7 +48,8 @@ class MediaResumeListener @Inject constructor(
private val context: Context,
private val broadcastDispatcher: BroadcastDispatcher,
@Background private val backgroundExecutor: Executor,
- private val tunerService: TunerService
+ private val tunerService: TunerService,
+ private val mediaBrowserFactory: ResumeMediaBrowserFactory
) : MediaDataManager.Listener {
private var useMediaResumption: Boolean = Utils.useMediaResumption(context)
@@ -59,7 +61,8 @@ class MediaResumeListener @Inject constructor(
private var mediaBrowser: ResumeMediaBrowser? = null
private var currentUserId: Int = context.userId
- private val userChangeReceiver = object : BroadcastReceiver() {
+ @VisibleForTesting
+ val userChangeReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (Intent.ACTION_USER_UNLOCKED == intent.action) {
loadMediaResumptionControls()
@@ -152,7 +155,7 @@ class MediaResumeListener @Inject constructor(
resumeComponents.forEach {
if (!blockedApps.contains(it.packageName)) {
- val browser = ResumeMediaBrowser(context, mediaBrowserCallback, it)
+ val browser = mediaBrowserFactory.create(mediaBrowserCallback, it)
browser.findRecentMedia()
}
}
@@ -193,14 +196,10 @@ class MediaResumeListener @Inject constructor(
private fun tryUpdateResumptionList(key: String, componentName: ComponentName) {
Log.d(TAG, "Testing if we can connect to $componentName")
mediaBrowser?.disconnect()
- mediaBrowser = ResumeMediaBrowser(context,
+ mediaBrowser = mediaBrowserFactory.create(
object : ResumeMediaBrowser.Callback() {
override fun onConnected() {
- Log.d(TAG, "yes we can resume with $componentName")
- mediaDataManager.setResumeAction(key, getResumeAction(componentName))
- updateResumptionList(componentName)
- mediaBrowser?.disconnect()
- mediaBrowser = null
+ Log.d(TAG, "Connected to $componentName")
}
override fun onError() {
@@ -209,6 +208,19 @@ class MediaResumeListener @Inject constructor(
mediaBrowser?.disconnect()
mediaBrowser = null
}
+
+ override fun addTrack(
+ desc: MediaDescription,
+ component: ComponentName,
+ browser: ResumeMediaBrowser
+ ) {
+ // Since this is a test, just save the component for later
+ Log.d(TAG, "Can get resumable media from $componentName")
+ mediaDataManager.setResumeAction(key, getResumeAction(componentName))
+ updateResumptionList(componentName)
+ mediaBrowser?.disconnect()
+ mediaBrowser = null
+ }
},
componentName)
mediaBrowser?.testConnection()
@@ -245,7 +257,7 @@ class MediaResumeListener @Inject constructor(
private fun getResumeAction(componentName: ComponentName): Runnable {
return Runnable {
mediaBrowser?.disconnect()
- mediaBrowser = ResumeMediaBrowser(context,
+ mediaBrowser = mediaBrowserFactory.create(
object : ResumeMediaBrowser.Callback() {
override fun onConnected() {
if (mediaBrowser?.token == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaScrollView.kt b/packages/SystemUI/src/com/android/systemui/media/MediaScrollView.kt
index b8872250bb6c..00273bc34552 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaScrollView.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaScrollView.kt
@@ -8,7 +8,7 @@ import android.view.MotionEvent
import android.view.ViewGroup
import android.widget.HorizontalScrollView
import com.android.systemui.Gefingerpoken
-import com.android.systemui.util.animation.physicsAnimator
+import com.android.wm.shell.animation.physicsAnimator
/**
* A ScrollView used in Media that doesn't limit itself to the childs bounds. This is useful
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
index 6bd5274fa331..51dbfa733541 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
@@ -126,6 +126,7 @@ class MediaTimeoutListener @Inject constructor(
fun destroy() {
mediaController?.unregisterCallback(this)
+ cancellation?.run()
}
override fun onPlaybackStateChanged(state: PlaybackState?) {
@@ -182,4 +183,4 @@ class MediaTimeoutListener @Inject constructor(
cancellation = null
}
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
index 11551aca80f2..666a6038a8b6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
@@ -23,7 +23,6 @@ import android.widget.ImageButton
import android.widget.ImageView
import android.widget.SeekBar
import android.widget.TextView
-import androidx.constraintlayout.widget.ConstraintSet
import com.android.systemui.R
import com.android.systemui.util.animation.TransitionLayout
diff --git a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java
index 68b6785849aa..a4d44367be73 100644
--- a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java
+++ b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java
@@ -30,6 +30,8 @@ import android.service.media.MediaBrowserService;
import android.text.TextUtils;
import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.util.List;
/**
@@ -46,6 +48,7 @@ public class ResumeMediaBrowser {
private static final String TAG = "ResumeMediaBrowser";
private final Context mContext;
private final Callback mCallback;
+ private MediaBrowserFactory mBrowserFactory;
private MediaBrowser mMediaBrowser;
private ComponentName mComponentName;
@@ -55,10 +58,12 @@ public class ResumeMediaBrowser {
* @param callback used to report media items found
* @param componentName Component name of the MediaBrowserService this browser will connect to
*/
- public ResumeMediaBrowser(Context context, Callback callback, ComponentName componentName) {
+ public ResumeMediaBrowser(Context context, Callback callback, ComponentName componentName,
+ MediaBrowserFactory browserFactory) {
mContext = context;
mCallback = callback;
mComponentName = componentName;
+ mBrowserFactory = browserFactory;
}
/**
@@ -74,7 +79,7 @@ public class ResumeMediaBrowser {
disconnect();
Bundle rootHints = new Bundle();
rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true);
- mMediaBrowser = new MediaBrowser(mContext,
+ mMediaBrowser = mBrowserFactory.create(
mComponentName,
mConnectionCallback,
rootHints);
@@ -88,17 +93,19 @@ public class ResumeMediaBrowser {
List<MediaBrowser.MediaItem> children) {
if (children.size() == 0) {
Log.d(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() && mMediaBrowser != null) {
- mCallback.addTrack(desc, mMediaBrowser.getServiceComponent(),
- ResumeMediaBrowser.this);
+ mCallback.onError();
} else {
- Log.d(TAG, "Child found but not playable for " + mComponentName);
+ // 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() && mMediaBrowser != null) {
+ mCallback.addTrack(desc, mMediaBrowser.getServiceComponent(),
+ ResumeMediaBrowser.this);
+ } else {
+ Log.d(TAG, "Child found but not playable for " + mComponentName);
+ mCallback.onError();
+ }
}
disconnect();
}
@@ -131,7 +138,7 @@ public class ResumeMediaBrowser {
Log.d(TAG, "Service connected for " + mComponentName);
if (mMediaBrowser != null && mMediaBrowser.isConnected()) {
String root = mMediaBrowser.getRoot();
- if (!TextUtils.isEmpty(root)) {
+ if (!TextUtils.isEmpty(root) && mMediaBrowser != null) {
mCallback.onConnected();
mMediaBrowser.subscribe(root, mSubscriptionCallback);
return;
@@ -182,7 +189,7 @@ public class ResumeMediaBrowser {
disconnect();
Bundle rootHints = new Bundle();
rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true);
- mMediaBrowser = new MediaBrowser(mContext, mComponentName,
+ mMediaBrowser = mBrowserFactory.create(mComponentName,
new MediaBrowser.ConnectionCallback() {
@Override
public void onConnected() {
@@ -192,7 +199,7 @@ public class ResumeMediaBrowser {
return;
}
MediaSession.Token token = mMediaBrowser.getSessionToken();
- MediaController controller = new MediaController(mContext, token);
+ MediaController controller = createMediaController(token);
controller.getTransportControls();
controller.getTransportControls().prepare();
controller.getTransportControls().play();
@@ -212,6 +219,11 @@ public class ResumeMediaBrowser {
mMediaBrowser.connect();
}
+ @VisibleForTesting
+ protected MediaController createMediaController(MediaSession.Token token) {
+ return new MediaController(mContext, token);
+ }
+
/**
* Get the media session token
* @return the token, or null if the MediaBrowser is null or disconnected
@@ -235,42 +247,19 @@ public class ResumeMediaBrowser {
/**
* Used to test if SystemUI is allowed to connect to the given component as a MediaBrowser.
- * ResumeMediaBrowser.Callback#onError or ResumeMediaBrowser.Callback#onConnected will be called
- * depending on whether it was successful.
+ * If it can connect, ResumeMediaBrowser.Callback#onConnected will be called. If valid media is
+ * found, then ResumeMediaBrowser.Callback#addTrack will also be called. This allows for more
+ * detailed logging if the service has issues. If it cannot connect, or cannot find valid media,
+ * then ResumeMediaBrowser.Callback#onError will be called.
* ResumeMediaBrowser#disconnect should be called after this to ensure the connection is closed.
*/
public void testConnection() {
disconnect();
- final MediaBrowser.ConnectionCallback connectionCallback =
- new MediaBrowser.ConnectionCallback() {
- @Override
- public void onConnected() {
- Log.d(TAG, "connected");
- if (mMediaBrowser == null || !mMediaBrowser.isConnected()
- || TextUtils.isEmpty(mMediaBrowser.getRoot())) {
- mCallback.onError();
- } else {
- mCallback.onConnected();
- }
- }
-
- @Override
- public void onConnectionSuspended() {
- Log.d(TAG, "suspended");
- mCallback.onError();
- }
-
- @Override
- public void onConnectionFailed() {
- Log.d(TAG, "failed");
- mCallback.onError();
- }
- };
Bundle rootHints = new Bundle();
rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true);
- mMediaBrowser = new MediaBrowser(mContext,
+ mMediaBrowser = mBrowserFactory.create(
mComponentName,
- connectionCallback,
+ mConnectionCallback,
rootHints);
mMediaBrowser.connect();
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserFactory.java b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserFactory.java
new file mode 100644
index 000000000000..2261aa5ac265
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserFactory.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.media;
+
+import android.content.ComponentName;
+import android.content.Context;
+
+import javax.inject.Inject;
+
+/**
+ * Testable wrapper around {@link ResumeMediaBrowser} constructor
+ */
+public class ResumeMediaBrowserFactory {
+ private final Context mContext;
+ private final MediaBrowserFactory mBrowserFactory;
+
+ @Inject
+ public ResumeMediaBrowserFactory(Context context, MediaBrowserFactory browserFactory) {
+ mContext = context;
+ mBrowserFactory = browserFactory;
+ }
+
+ /**
+ * Creates a new ResumeMediaBrowser.
+ *
+ * @param callback will be called on connection or error, and addTrack when media item found
+ * @param componentName component to browse
+ * @return
+ */
+ public ResumeMediaBrowser create(ResumeMediaBrowser.Callback callback,
+ ComponentName componentName) {
+ return new ResumeMediaBrowser(mContext, callback, componentName, mBrowserFactory);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index 9fc64d51cdf7..d1630ebe8dc8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -42,7 +42,9 @@ import java.util.List;
public class MediaOutputAdapter extends MediaOutputBaseAdapter {
private static final String TAG = "MediaOutputAdapter";
- private static final int PAIR_NEW = 1;
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private ViewGroup mConnectedItem;
public MediaOutputAdapter(MediaOutputController controller) {
super(controller);
@@ -58,11 +60,14 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
@Override
public void onBindViewHolder(@NonNull MediaDeviceBaseViewHolder viewHolder, int position) {
- if (mController.isZeroMode() && position == (mController.getMediaDevices().size())) {
- viewHolder.onBind(PAIR_NEW);
- } else if (position < (mController.getMediaDevices().size())) {
- viewHolder.onBind(((List<MediaDevice>) (mController.getMediaDevices())).get(position));
- } else {
+ final int size = mController.getMediaDevices().size();
+ if (mController.isZeroMode() && position == size) {
+ viewHolder.onBind(CUSTOMIZED_ITEM_PAIR_NEW, false /* topMargin */,
+ true /* bottomMargin */);
+ } else if (position < size) {
+ viewHolder.onBind(((List<MediaDevice>) (mController.getMediaDevices())).get(position),
+ position == 0 /* topMargin */, position == (size - 1) /* bottomMargin */);
+ } else if (DEBUG) {
Log.d(TAG, "Incorrect position: " + position);
}
}
@@ -76,18 +81,6 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
return mController.getMediaDevices().size();
}
- void onItemClick(MediaDevice device) {
- mController.connectDevice(device);
- device.setState(MediaDeviceState.STATE_CONNECTING);
- notifyDataSetChanged();
- }
-
- void onItemClick(int customizedItem) {
- if (customizedItem == PAIR_NEW) {
- mController.launchBluetoothPairing();
- }
- }
-
@Override
CharSequence getItemTitle(MediaDevice device) {
if (device.getDeviceType() == MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE
@@ -112,51 +105,72 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
}
@Override
- void onBind(MediaDevice device) {
- super.onBind(device);
+ void onBind(MediaDevice device, boolean topMargin, boolean bottomMargin) {
+ super.onBind(device, topMargin, bottomMargin);
+ final boolean currentlyConnected = isCurrentlyConnected(device);
+ if (currentlyConnected) {
+ mConnectedItem = mFrameLayout;
+ }
if (mController.isTransferring()) {
if (device.getState() == MediaDeviceState.STATE_CONNECTING
&& !mController.hasAdjustVolumeUserRestriction()) {
- setTwoLineLayout(device, true);
- mProgressBar.setVisibility(View.VISIBLE);
- mSeekBar.setVisibility(View.GONE);
- mSubTitleText.setVisibility(View.GONE);
+ setTwoLineLayout(device, null /* title */, true /* bFocused */,
+ false /* showSeekBar*/, true /* showProgressBar */,
+ false /* showSubtitle */);
} else {
- setSingleLineLayout(getItemTitle(device), false);
+ setSingleLineLayout(getItemTitle(device), false /* bFocused */);
}
} else {
// Set different layout for each device
if (device.getState() == MediaDeviceState.STATE_CONNECTING_FAILED) {
- setTwoLineLayout(device, false);
- mSubTitleText.setVisibility(View.VISIBLE);
- mSeekBar.setVisibility(View.GONE);
- mProgressBar.setVisibility(View.GONE);
+ setTwoLineLayout(device, null /* title */, false /* bFocused */,
+ false /* showSeekBar*/, false /* showProgressBar */,
+ true /* showSubtitle */);
mSubTitleText.setText(R.string.media_output_dialog_connect_failed);
- mFrameLayout.setOnClickListener(v -> onItemClick(device));
+ mFrameLayout.setOnClickListener(v -> onItemClick(v, device));
} else if (!mController.hasAdjustVolumeUserRestriction()
- && isCurrentConnected(device)) {
- setTwoLineLayout(device, true);
- mSeekBar.setVisibility(View.VISIBLE);
- mProgressBar.setVisibility(View.GONE);
- mSubTitleText.setVisibility(View.GONE);
+ && currentlyConnected) {
+ setTwoLineLayout(device, null /* title */, true /* bFocused */,
+ true /* showSeekBar*/, false /* showProgressBar */,
+ false /* showSubtitle */);
initSeekbar(device);
} else {
- setSingleLineLayout(getItemTitle(device), false);
- mFrameLayout.setOnClickListener(v -> onItemClick(device));
+ setSingleLineLayout(getItemTitle(device), false /* bFocused */);
+ mFrameLayout.setOnClickListener(v -> onItemClick(v, device));
}
}
}
@Override
- void onBind(int customizedItem) {
- if (customizedItem == PAIR_NEW) {
+ void onBind(int customizedItem, boolean topMargin, boolean bottomMargin) {
+ super.onBind(customizedItem, topMargin, bottomMargin);
+ if (customizedItem == CUSTOMIZED_ITEM_PAIR_NEW) {
setSingleLineLayout(mContext.getText(R.string.media_output_dialog_pairing_new),
- false);
+ false /* bFocused */);
final Drawable d = mContext.getDrawable(R.drawable.ic_add);
d.setColorFilter(new PorterDuffColorFilter(
Utils.getColorAccentDefaultColor(mContext), PorterDuff.Mode.SRC_IN));
mTitleIcon.setImageDrawable(d);
- mFrameLayout.setOnClickListener(v -> onItemClick(PAIR_NEW));
+ mFrameLayout.setOnClickListener(v -> onItemClick(CUSTOMIZED_ITEM_PAIR_NEW));
+ }
+ }
+
+ private void onItemClick(View view, MediaDevice device) {
+ if (mController.isTransferring()) {
+ return;
+ }
+
+ playSwitchingAnim(mConnectedItem, view);
+ mController.connectDevice(device);
+ device.setState(MediaDeviceState.STATE_CONNECTING);
+ if (!isAnimating()) {
+ notifyDataSetChanged();
+ }
+ }
+
+ private void onItemClick(int customizedItem) {
+ if (customizedItem == CUSTOMIZED_ITEM_PAIR_NEW) {
+ mController.launchBluetoothPairing();
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index 7579c25b030a..2d3e77db1ea3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -16,6 +16,8 @@
package com.android.systemui.media.dialog;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.graphics.Typeface;
import android.text.TextUtils;
@@ -33,6 +35,7 @@ import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.android.settingslib.media.MediaDevice;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
/**
@@ -44,9 +47,13 @@ public abstract class MediaOutputBaseAdapter extends
private static final String FONT_SELECTED_TITLE = "sans-serif-medium";
private static final String FONT_TITLE = "sans-serif";
+ static final int CUSTOMIZED_ITEM_PAIR_NEW = 1;
+
final MediaOutputController mController;
private boolean mIsDragging;
+ private int mMargin;
+ private boolean mIsAnimating;
Context mContext;
View mHolderView;
@@ -60,6 +67,8 @@ public abstract class MediaOutputBaseAdapter extends
public MediaDeviceBaseViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup,
int viewType) {
mContext = viewGroup.getContext();
+ mMargin = mContext.getResources().getDimensionPixelSize(
+ R.dimen.media_output_dialog_list_margin);
mHolderView = LayoutInflater.from(mContext).inflate(R.layout.media_output_list_item,
viewGroup, false);
@@ -70,7 +79,7 @@ public abstract class MediaOutputBaseAdapter extends
return device.getName();
}
- boolean isCurrentConnected(MediaDevice device) {
+ boolean isCurrentlyConnected(MediaDevice device) {
return TextUtils.equals(device.getId(),
mController.getCurrentConnectedMediaDevice().getId());
}
@@ -79,10 +88,17 @@ public abstract class MediaOutputBaseAdapter extends
return mIsDragging;
}
+ boolean isAnimating() {
+ return mIsAnimating;
+ }
+
/**
* ViewHolder for binding device view.
*/
abstract class MediaDeviceBaseViewHolder extends RecyclerView.ViewHolder {
+
+ private static final int ANIM_DURATION = 200;
+
final FrameLayout mFrameLayout;
final TextView mTitleText;
final TextView mTwoLineTitleText;
@@ -106,15 +122,28 @@ public abstract class MediaOutputBaseAdapter extends
mSeekBar = view.requireViewById(R.id.volume_seekbar);
}
- void onBind(MediaDevice device) {
+ void onBind(MediaDevice device, boolean topMargin, boolean bottomMargin) {
mTitleIcon.setImageIcon(mController.getDeviceIconCompat(device).toIcon(mContext));
+ setMargin(topMargin, bottomMargin);
+ }
+
+ void onBind(int customizedItem, boolean topMargin, boolean bottomMargin) {
+ setMargin(topMargin, bottomMargin);
}
- void onBind(int customizedItem) { }
+ private void setMargin(boolean topMargin, boolean bottomMargin) {
+ ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) mFrameLayout
+ .getLayoutParams();
+ params.topMargin = topMargin ? mMargin : 0;
+ params.bottomMargin = bottomMargin ? mMargin : 0;
+ mFrameLayout.setLayoutParams(params);
+ }
void setSingleLineLayout(CharSequence title, boolean bFocused) {
- mTitleText.setVisibility(View.VISIBLE);
mTwoLineLayout.setVisibility(View.GONE);
+ mProgressBar.setVisibility(View.GONE);
+ mTitleText.setVisibility(View.VISIBLE);
+ mTitleText.setTranslationY(0);
mTitleText.setText(title);
if (bFocused) {
mTitleText.setTypeface(Typeface.create(FONT_SELECTED_TITLE, Typeface.NORMAL));
@@ -123,10 +152,21 @@ public abstract class MediaOutputBaseAdapter extends
}
}
- void setTwoLineLayout(MediaDevice device, boolean bFocused) {
+ void setTwoLineLayout(MediaDevice device, CharSequence title, boolean bFocused,
+ boolean showSeekBar, boolean showProgressBar, boolean showSubtitle) {
mTitleText.setVisibility(View.GONE);
mTwoLineLayout.setVisibility(View.VISIBLE);
- mTwoLineTitleText.setText(getItemTitle(device));
+ mSeekBar.setAlpha(1);
+ mSeekBar.setVisibility(showSeekBar ? View.VISIBLE : View.GONE);
+ mProgressBar.setVisibility(showProgressBar ? View.VISIBLE : View.GONE);
+ mSubTitleText.setVisibility(showSubtitle ? View.VISIBLE : View.GONE);
+ mTwoLineTitleText.setTranslationY(0);
+ if (device == null) {
+ mTwoLineTitleText.setText(title);
+ } else {
+ mTwoLineTitleText.setText(getItemTitle(device));
+ }
+
if (bFocused) {
mTwoLineTitleText.setTypeface(Typeface.create(FONT_SELECTED_TITLE,
Typeface.NORMAL));
@@ -161,5 +201,53 @@ public abstract class MediaOutputBaseAdapter extends
}
});
}
+
+ void playSwitchingAnim(@NonNull View from, @NonNull View to) {
+ final float delta = (float) (mContext.getResources().getDimensionPixelSize(
+ R.dimen.media_output_dialog_title_anim_y_delta));
+ final SeekBar fromSeekBar = from.requireViewById(R.id.volume_seekbar);
+ final TextView toTitleText = to.requireViewById(R.id.title);
+ if (fromSeekBar.getVisibility() != View.VISIBLE || toTitleText.getVisibility()
+ != View.VISIBLE) {
+ return;
+ }
+ mIsAnimating = true;
+ // Animation for title text
+ toTitleText.setTypeface(Typeface.create(FONT_SELECTED_TITLE, Typeface.NORMAL));
+ toTitleText.animate()
+ .setDuration(ANIM_DURATION)
+ .translationY(-delta)
+ .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ to.requireViewById(R.id.volume_indeterminate_progress).setVisibility(
+ View.VISIBLE);
+ }
+ });
+ // Animation for seek bar
+ fromSeekBar.animate()
+ .alpha(0)
+ .setDuration(ANIM_DURATION)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ final TextView fromTitleText = from.requireViewById(
+ R.id.two_line_title);
+ fromTitleText.setTypeface(Typeface.create(FONT_TITLE, Typeface.NORMAL));
+ fromTitleText.animate()
+ .setDuration(ANIM_DURATION)
+ .translationY(delta)
+ .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mIsAnimating = false;
+ notifyDataSetChanged();
+ }
+ });
+ }
+ });
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index f8f4f4df58bc..caef536961f1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -33,7 +33,6 @@ import android.view.Window;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.widget.Button;
-import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
@@ -50,7 +49,7 @@ import com.android.systemui.statusbar.phone.SystemUIDialog;
* Base dialog for media output UI
*/
public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
- MediaOutputController.Callback {
+ MediaOutputController.Callback, Window.Callback {
private static final String TAG = "MediaOutputDialog";
@@ -69,12 +68,9 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
private LinearLayout mDeviceListLayout;
private Button mDoneButton;
private Button mStopButton;
- private View mListBottomPadding;
private int mListMaxHeight;
MediaOutputBaseAdapter mAdapter;
- FrameLayout mGroupItemController;
- View mGroupDivider;
private final ViewTreeObserver.OnGlobalLayoutListener mDeviceListLayoutListener = () -> {
// Set max height for list
@@ -114,12 +110,9 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
mHeaderSubtitle = mDialogView.requireViewById(R.id.header_subtitle);
mHeaderIcon = mDialogView.requireViewById(R.id.header_icon);
mDevicesRecyclerView = mDialogView.requireViewById(R.id.list_result);
- mGroupItemController = mDialogView.requireViewById(R.id.group_item_controller);
- mGroupDivider = mDialogView.requireViewById(R.id.group_item_divider);
mDeviceListLayout = mDialogView.requireViewById(R.id.device_list);
mDoneButton = mDialogView.requireViewById(R.id.done);
mStopButton = mDialogView.requireViewById(R.id.stop);
- mListBottomPadding = mDialogView.requireViewById(R.id.list_bottom_padding);
mDeviceListLayout.getViewTreeObserver().addOnGlobalLayoutListener(
mDeviceListLayoutListener);
@@ -162,7 +155,9 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
}
if (mHeaderIcon.getVisibility() == View.VISIBLE) {
final int size = getHeaderIconSize();
- mHeaderIcon.setLayoutParams(new LinearLayout.LayoutParams(size, size));
+ final int padding = mContext.getResources().getDimensionPixelSize(
+ R.dimen.media_output_dialog_header_icon_padding);
+ mHeaderIcon.setLayoutParams(new LinearLayout.LayoutParams(size + padding, size));
}
// Update title and subtitle
mHeaderTitle.setText(getHeaderText());
@@ -175,15 +170,11 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
mHeaderSubtitle.setText(subTitle);
mHeaderTitle.setGravity(Gravity.NO_GRAVITY);
}
- if (!mAdapter.isDragging()) {
+ if (!mAdapter.isDragging() && !mAdapter.isAnimating()) {
mAdapter.notifyDataSetChanged();
}
- // Add extra padding when device amount is less than 6
- if (mMediaOutputController.getMediaDevices().size() < 6) {
- mListBottomPadding.setVisibility(View.VISIBLE);
- } else {
- mListBottomPadding.setVisibility(View.GONE);
- }
+ // Show when remote media session is available
+ mStopButton.setVisibility(getStopButtonVisibility());
}
abstract int getHeaderIconRes();
@@ -196,6 +187,8 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
abstract CharSequence getHeaderSubtitle();
+ abstract int getStopButtonVisibility();
+
@Override
public void onMediaChanged() {
mMainThreadHandler.post(() -> refresh());
@@ -217,4 +210,12 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
public void dismissDialog() {
dismiss();
}
+
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+ super.onWindowFocusChanged(hasFocus);
+ if (!hasFocus && isShowing()) {
+ dismiss();
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 64d20a273931..b1f1bda25961 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -24,6 +24,7 @@ import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.media.MediaMetadata;
+import android.media.MediaRoute2Info;
import android.media.RoutingSessionInfo;
import android.media.session.MediaController;
import android.media.session.MediaSessionManager;
@@ -63,7 +64,7 @@ import javax.inject.Inject;
public class MediaOutputController implements LocalMediaManager.DeviceCallback{
private static final String TAG = "MediaOutputController";
- private static final boolean DEBUG = false;
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private final String mPackageName;
private final Context mContext;
@@ -406,6 +407,14 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback{
mActivityStarter.dismissKeyguardThenExecute(postKeyguardAction, null, true);
}
+ boolean isActiveRemoteDevice(@NonNull MediaDevice device) {
+ final List<String> features = device.getFeatures();
+ return (features.contains(MediaRoute2Info.FEATURE_REMOTE_PLAYBACK)
+ || features.contains(MediaRoute2Info.FEATURE_REMOTE_AUDIO_PLAYBACK)
+ || features.contains(MediaRoute2Info.FEATURE_REMOTE_VIDEO_PLAYBACK)
+ || features.contains(MediaRoute2Info.FEATURE_REMOTE_GROUP_PLAYBACK));
+ }
+
private final MediaController.Callback mCb = new MediaController.Callback() {
@Override
public void onMetadataChanged(MediaMetadata metadata) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
index ac9d8ce52d88..a892a12f387b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
@@ -45,8 +45,6 @@ public class MediaOutputDialog extends MediaOutputBaseDialog {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- mGroupItemController.setVisibility(View.GONE);
- mGroupDivider.setVisibility(View.GONE);
}
@Override
@@ -74,4 +72,10 @@ public class MediaOutputDialog extends MediaOutputBaseDialog {
CharSequence getHeaderSubtitle() {
return mMediaOutputController.getHeaderSubTitle();
}
+
+ @Override
+ int getStopButtonVisibility() {
+ return mMediaOutputController.isActiveRemoteDevice(
+ mMediaOutputController.getCurrentConnectedMediaDevice()) ? View.VISIBLE : View.GONE;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
index bc1dca58990d..4cdca4cbcf1e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
@@ -33,10 +33,22 @@ class MediaOutputDialogFactory @Inject constructor(
private val shadeController: ShadeController,
private val starter: ActivityStarter
) {
+ companion object {
+ var mediaOutputDialog: MediaOutputDialog? = null
+ }
+
/** Creates a [MediaOutputDialog] for the given package. */
fun create(packageName: String, aboveStatusBar: Boolean) {
- MediaOutputController(context, packageName, mediaSessionManager, lbm, shadeController,
- starter).run {
+ mediaOutputDialog?.dismiss()
+
+ mediaOutputDialog = MediaOutputController(context, packageName, mediaSessionManager, lbm,
+ shadeController, starter).run {
MediaOutputDialog(context, aboveStatusBar, this) }
}
+
+ /** dismiss [MediaOutputDialog] if exist. */
+ fun dismiss() {
+ mediaOutputDialog?.dismiss()
+ mediaOutputDialog = null
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
index 6cbf065ea6a9..df9e7a428877 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
@@ -47,6 +47,7 @@ import com.android.systemui.R;
import com.android.systemui.navigationbar.buttons.KeyButtonDrawable;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.statusbar.policy.RotationLockController;
@@ -156,7 +157,7 @@ public class RotationButtonController {
throw e.rethrowFromSystemServer();
}
- ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
+ TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
}
void unregisterListeners() {
@@ -171,7 +172,7 @@ public class RotationButtonController {
throw e.rethrowFromSystemServer();
}
- ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener);
+ TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
}
void addRotationCallback(Consumer<Integer> watcher) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonDrawable.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonDrawable.java
index fc2016913292..702be72ff4ed 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonDrawable.java
@@ -171,6 +171,24 @@ public class KeyButtonDrawable extends Drawable {
}
@Override
+ public boolean setVisible(boolean visible, boolean restart) {
+ boolean changed = super.setVisible(visible, restart);
+ if (changed) {
+ // End any existing animations when the visibility changes
+ jumpToCurrentState();
+ }
+ return changed;
+ }
+
+ @Override
+ public void jumpToCurrentState() {
+ super.jumpToCurrentState();
+ if (mAnimatedDrawable != null) {
+ mAnimatedDrawable.jumpToCurrentState();
+ }
+ }
+
+ @Override
public void setAlpha(int alpha) {
mState.mAlpha = alpha;
mIconPaint.setAlpha(alpha);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java
index 72cd4f1343e6..cf45f52e3367 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java
@@ -225,6 +225,16 @@ public class KeyButtonRipple extends Drawable {
}
@Override
+ public boolean setVisible(boolean visible, boolean restart) {
+ boolean changed = super.setVisible(visible, restart);
+ if (changed) {
+ // End any existing animations when the visibility changes
+ jumpToCurrentState();
+ }
+ return changed;
+ }
+
+ @Override
public void jumpToCurrentState() {
endAnimations("jumpToCurrentState", false /* cancel */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
index d6b831640326..4efe4d85156b 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
@@ -58,7 +58,6 @@ import com.android.internal.logging.UiEventLoggerImpl;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.Dependency;
import com.android.systemui.R;
-import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.shared.system.QuickStepContract;
@@ -426,12 +425,6 @@ public class KeyButtonView extends ImageView implements ButtonInterface {
if (getDisplay() != null) {
displayId = getDisplay().getDisplayId();
}
- // Bubble controller will give us a valid display id if it should get the back event
- BubbleController bubbleController = Dependency.get(BubbleController.class);
- int bubbleDisplayId = bubbleController.getExpandedDisplayId(mContext);
- if (mCode == KeyEvent.KEYCODE_BACK && bubbleDisplayId != INVALID_DISPLAY) {
- displayId = bubbleDisplayId;
- }
if (displayId != INVALID_DISPLAY) {
ev.setDisplayId(displayId);
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 56943604e9b3..18cc746666d8 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -15,8 +15,6 @@
*/
package com.android.systemui.navigationbar.gestural;
-import static android.view.Display.INVALID_DISPLAY;
-
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
@@ -57,7 +55,6 @@ import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.SystemUIFactory;
import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.navigationbar.NavigationModeController;
@@ -71,6 +68,7 @@ import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.shared.tracing.ProtoTraceable;
import com.android.systemui.tracing.ProtoTracer;
import com.android.systemui.tracing.nano.EdgeBackGestureHandlerProto;
@@ -207,6 +205,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa
private boolean mUseMLModel;
private float mMLModelThreshold;
private String mPackageName;
+ private float mMLResults;
private final GestureNavigationSettingsObserver mGestureNavigationSettingsObserver;
@@ -386,7 +385,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa
mGestureNavigationSettingsObserver.unregister();
mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this);
mPluginManager.removePluginListener(this);
- ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener);
+ TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
DeviceConfig.removeOnPropertiesChangedListener(mOnPropertiesChangedListener);
try {
@@ -402,7 +401,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa
updateDisplaySize();
mContext.getSystemService(DisplayManager.class).registerDisplayListener(this,
mContext.getMainThreadHandler());
- ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
+ TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
runnable -> (mContext.getMainThreadHandler()).post(runnable),
mOnPropertiesChangedListener);
@@ -531,10 +530,10 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa
new long[]{(long) y},
};
- final float results = mBackGestureTfClassifierProvider.predict(featuresVector);
- if (results == -1) return -1;
+ mMLResults = mBackGestureTfClassifierProvider.predict(featuresVector);
+ if (mMLResults == -1) return -1;
- return results >= mMLModelThreshold ? 1 : 0;
+ return mMLResults >= mMLModelThreshold ? 1 : 0;
}
private boolean isWithinTouchRegion(int x, int y) {
@@ -604,6 +603,11 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa
return;
}
mLogGesture = false;
+ String logPackageName = "";
+ // Due to privacy, only top 100 most used apps by all users can be logged.
+ if (mUseMLModel && mVocab.containsKey(mPackageName) && mVocab.get(mPackageName) < 100) {
+ logPackageName = mPackageName;
+ }
SysUiStatsLog.write(SysUiStatsLog.BACK_GESTURE_REPORTED_REPORTED, backType,
(int) mDownPoint.y, mIsOnLeftEdge
? SysUiStatsLog.BACK_GESTURE__X_LOCATION__LEFT
@@ -611,7 +615,8 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa
(int) mDownPoint.x, (int) mDownPoint.y,
(int) mEndPoint.x, (int) mEndPoint.y,
mEdgeWidthLeft + mLeftInset,
- mDisplaySize.x - (mEdgeWidthRight + mRightInset));
+ mDisplaySize.x - (mEdgeWidthRight + mRightInset),
+ mUseMLModel ? mMLResults : -2, logPackageName);
}
private void onMotionEvent(MotionEvent ev) {
@@ -621,6 +626,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa
// either the bouncer is showing or the notification panel is hidden
mInputEventReceiver.setBatchingEnabled(false);
mIsOnLeftEdge = ev.getX() <= mEdgeWidthLeft + mLeftInset;
+ mMLResults = 0;
mLogGesture = false;
mInRejectedExclusion = false;
mAllowGesture = !mDisabledForQuickstep && mIsBackGestureAllowed
@@ -725,14 +731,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa
KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
InputDevice.SOURCE_KEYBOARD);
- // Bubble controller will give us a valid display id if it should get the back event
- BubbleController bubbleController = Dependency.get(BubbleController.class);
- int bubbleDisplayId = bubbleController.getExpandedDisplayId(mContext);
- if (bubbleDisplayId != INVALID_DISPLAY) {
- ev.setDisplayId(bubbleDisplayId);
- } else {
- ev.setDisplayId(mContext.getDisplay().getDisplayId());
- }
+ ev.setDisplayId(mContext.getDisplay().getDisplayId());
InputManager.getInstance().injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
index 284f41a416d6..fbbda5f31093 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
@@ -16,6 +16,8 @@
package com.android.systemui.navigationbar.gestural;
+import static android.view.Display.DEFAULT_DISPLAY;
+
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Configuration;
@@ -334,6 +336,7 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl
.getDimension(R.dimen.navigation_edge_action_drag_threshold);
setVisibility(GONE);
+ boolean isPrimaryDisplay = mContext.getDisplayId() == DEFAULT_DISPLAY;
mRegionSamplingHelper = new RegionSamplingHelper(this,
new RegionSamplingHelper.SamplingCallback() {
@Override
@@ -345,8 +348,14 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl
public Rect getSampledRegion(View sampledView) {
return mSamplingRect;
}
+
+ @Override
+ public boolean isSamplingEnabled() {
+ return isPrimaryDisplay;
+ }
});
mRegionSamplingHelper.setWindowVisible(true);
+ mShowProtection = !isPrimaryDisplay;
}
@Override
@@ -366,11 +375,6 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl
updateIsDark(animate);
}
- private void setShowProtection(boolean showProtection) {
- mShowProtection = showProtection;
- invalidate();
- }
-
@Override
public void setIsLeftPanel(boolean isLeftPanel) {
mIsLeftPanel = isLeftPanel;
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
new file mode 100644
index 000000000000..eeb93bb7d766
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.people;
+
+import android.app.Activity;
+import android.app.INotificationManager;
+import android.app.people.IPeopleManager;
+import android.content.Context;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
+import android.content.pm.ShortcutInfo;
+import android.icu.text.MeasureFormat;
+import android.icu.util.Measure;
+import android.icu.util.MeasureUnit;
+import android.os.Bundle;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.service.notification.ConversationChannelWrapper;
+import android.util.Log;
+import android.view.ViewGroup;
+
+import com.android.systemui.R;
+
+import java.time.Duration;
+import java.util.List;
+import java.util.Locale;
+import java.util.stream.Collectors;
+
+/**
+ * Shows the user their tiles for their priority People (go/live-status).
+ */
+public class PeopleSpaceActivity extends Activity {
+
+ private static String sTAG = "PeopleSpaceActivity";
+
+ private ViewGroup mPeopleSpaceLayout;
+ private IPeopleManager mPeopleManager;
+ private INotificationManager mNotificationManager;
+ private PackageManager mPackageManager;
+ private LauncherApps mLauncherApps;
+ private List<ConversationChannelWrapper> mConversations;
+ private Context mContext;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.people_space_activity);
+ mPeopleSpaceLayout = findViewById(R.id.people_space_layout);
+ mContext = getApplicationContext();
+ mNotificationManager =
+ INotificationManager.Stub.asInterface(
+ ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+ mPackageManager = getPackageManager();
+ mPeopleManager = IPeopleManager.Stub.asInterface(
+ ServiceManager.getService(Context.PEOPLE_SERVICE));
+ mLauncherApps = mContext.getSystemService(LauncherApps.class);
+ setTileViewsWithPriorityConversations();
+ }
+
+ /**
+ * Retrieves all priority conversations and sets a {@link PeopleSpaceTileView}s for each
+ * priority conversation.
+ */
+ private void setTileViewsWithPriorityConversations() {
+ try {
+ List<ConversationChannelWrapper> conversations =
+ mNotificationManager.getConversations(
+ true /* priority only */).getList();
+ mConversations = conversations.stream().filter(
+ c -> shouldKeepConversation(c)).collect(Collectors.toList());
+ for (ConversationChannelWrapper conversation : mConversations) {
+ PeopleSpaceTileView tileView = new PeopleSpaceTileView(mContext,
+ mPeopleSpaceLayout,
+ conversation.getShortcutInfo().getId());
+ setTileView(tileView, conversation);
+ }
+ } catch (Exception e) {
+ Log.e(sTAG, "Couldn't retrieve conversations", e);
+ }
+ }
+
+ /** Sets {@code tileView} with the data in {@code conversation}. */
+ private void setTileView(PeopleSpaceTileView tileView,
+ ConversationChannelWrapper conversation) {
+ try {
+ ShortcutInfo shortcutInfo = conversation.getShortcutInfo();
+ int userId = UserHandle.getUserHandleForUid(
+ conversation.getUid()).getIdentifier();
+
+ String pkg = shortcutInfo.getPackage();
+ long lastInteraction = mPeopleManager.getLastInteraction(
+ pkg, userId,
+ shortcutInfo.getId());
+ String status = lastInteraction != 0l ? mContext.getString(
+ R.string.last_interaction_status,
+ getLastInteractionString(
+ lastInteraction)) : mContext.getString(R.string.basic_status);
+ tileView.setStatus(status);
+
+ tileView.setName(shortcutInfo.getLabel().toString());
+ tileView.setPackageIcon(mPackageManager.getApplicationIcon(pkg));
+ tileView.setPersonIcon(mLauncherApps.getShortcutIconDrawable(shortcutInfo, 0));
+ tileView.setOnClickListener(mLauncherApps, shortcutInfo);
+ } catch (Exception e) {
+ Log.e(sTAG, "Couldn't retrieve shortcut information", e);
+ }
+ }
+
+ /** Returns a readable representation of {@code lastInteraction}. */
+ private String getLastInteractionString(long lastInteraction) {
+ long now = System.currentTimeMillis();
+ Duration durationSinceLastInteraction = Duration.ofMillis(
+ now - lastInteraction);
+ MeasureFormat formatter = MeasureFormat.getInstance(Locale.getDefault(),
+ MeasureFormat.FormatWidth.WIDE);
+ if (durationSinceLastInteraction.toDays() >= 1) {
+ return
+ formatter
+ .formatMeasures(new Measure(durationSinceLastInteraction.toDays(),
+ MeasureUnit.DAY));
+ } else if (durationSinceLastInteraction.toHours() >= 1) {
+ return formatter.formatMeasures(new Measure(durationSinceLastInteraction.toHours(),
+ MeasureUnit.HOUR));
+ } else if (durationSinceLastInteraction.toMinutes() >= 1) {
+ return formatter.formatMeasures(new Measure(durationSinceLastInteraction.toMinutes(),
+ MeasureUnit.MINUTE));
+ } else {
+ return formatter.formatMeasures(
+ new Measure(durationSinceLastInteraction.toMillis() / 1000,
+ MeasureUnit.SECOND));
+ }
+ }
+
+ /**
+ * Returns whether the {@code conversation} should be kept for display in the People Space.
+ *
+ * <p>A valid {@code conversation} must:
+ * <ul>
+ * <li>Have a non-null {@link ShortcutInfo}
+ * <li>Have an associated label in the {@link ShortcutInfo}
+ * </ul>
+ * </li>
+ */
+ private boolean shouldKeepConversation(ConversationChannelWrapper conversation) {
+ ShortcutInfo shortcutInfo = conversation.getShortcutInfo();
+ return shortcutInfo != null && shortcutInfo.getLabel().length() != 0;
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ // Refresh tile views to sync new conversations.
+ setTileViewsWithPriorityConversations();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java
new file mode 100644
index 000000000000..d5ef190d5ff1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.people;
+
+import android.content.Context;
+import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
+import android.graphics.drawable.Drawable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+
+/**
+ * PeopleSpaceTileView renders an individual person's tile with associated status.
+ */
+public class PeopleSpaceTileView extends LinearLayout {
+
+ private View mTileView;
+ private TextView mNameView;
+ private TextView mStatusView;
+ private ImageView mPackageIconView;
+ private ImageView mPersonIconView;
+
+ public PeopleSpaceTileView(Context context, ViewGroup view, String shortcutId) {
+ super(context);
+ mTileView = view.findViewWithTag(shortcutId);
+ if (mTileView == null) {
+ LayoutInflater inflater = LayoutInflater.from(context);
+ mTileView = inflater.inflate(R.layout.people_space_tile_view, view, false);
+ view.addView(mTileView, LayoutParams.MATCH_PARENT,
+ LayoutParams.MATCH_PARENT);
+ mTileView.setTag(shortcutId);
+ }
+ mNameView = mTileView.findViewById(R.id.tile_view_name);
+ mStatusView = mTileView.findViewById(R.id.tile_view_status);
+ mPackageIconView = mTileView.findViewById(R.id.tile_view_package_icon);
+ mPersonIconView = mTileView.findViewById(R.id.tile_view_person_icon);
+ }
+
+ /** Sets the name text on the tile. */
+ public void setName(String name) {
+ mNameView.setText(name);
+ }
+
+ /** Sets the status text on the tile. */
+ public void setStatus(String status) {
+ mStatusView.setText(status);
+ }
+
+ /** Sets the package drawable on the tile. */
+ public void setPackageIcon(Drawable drawable) {
+ mPackageIconView.setImageDrawable(drawable);
+ }
+
+ /** Sets the person drawable on the tile. */
+ public void setPersonIcon(Drawable drawable) {
+ mPersonIconView.setImageDrawable(drawable);
+ }
+
+ /** Sets the click listener of the tile. */
+ public void setOnClickListener(LauncherApps launcherApps, ShortcutInfo shortcutInfo) {
+ mTileView.setOnClickListener(v -> launcherApps.startShortcut(shortcutInfo, null, null));
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/Pip.java b/packages/SystemUI/src/com/android/systemui/pip/Pip.java
deleted file mode 100644
index b068370da9e3..000000000000
--- a/packages/SystemUI/src/com/android/systemui/pip/Pip.java
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.pip;
-
-import android.content.res.Configuration;
-import android.media.session.MediaController;
-
-import com.android.systemui.pip.tv.PipController;
-import com.android.systemui.shared.recents.IPinnedStackAnimationListener;
-
-import java.io.PrintWriter;
-
-/**
- * Interface to engage picture in picture feature.
- */
-public interface Pip {
- /**
- * Called when showing Pip menu.
- */
- void showPictureInPictureMenu();
-
- /**
- * Registers {@link com.android.systemui.pip.tv.PipController.Listener} that gets called.
- * whenever receiving notification on changes in PIP.
- */
- default void addListener(PipController.Listener listener) {
- }
-
- /**
- * Registers a {@link com.android.systemui.pip.tv.PipController.MediaListener} to PipController.
- */
- default void addMediaListener(PipController.MediaListener listener) {
- }
-
- /**
- * Closes PIP (PIPed activity and PIP system UI).
- */
- default void closePip() {
- }
-
- /**
- * Expand PIP, it's possible that specific request to activate the window via Alt-tab.
- */
- default void expandPip() {
- }
-
- /**
- * Get current play back state. (e.g: Used in TV)
- *
- * @return The state of defined in PipController.
- */
- default int getPlaybackState() {
- return 0;
- }
-
- /**
- * Get MediaController.
- *
- * @return The MediaController instance.
- */
- default MediaController getMediaController() {
- return null;
- }
-
- /**
- * Hides the PIP menu.
- */
- void hidePipMenu(Runnable onStartCallback, Runnable onEndCallback);
-
- /**
- * Returns {@code true} if PIP is shown.
- */
- default boolean isPipShown() {
- return false;
- }
-
- /**
- * Moves the PIPed activity to the fullscreen and closes PIP system UI.
- */
- default void movePipToFullscreen() {
- }
-
- /**
- * Called when configuration change invoked.
- */
- void onConfigurationChanged(Configuration newConfig);
-
- /**
- * Removes a {@link PipController.Listener} from PipController.
- */
- default void removeListener(PipController.Listener listener) {
- }
-
- /**
- * Removes a {@link com.android.systemui.pip.tv.PipController.MediaListener} from PipController.
- */
- default void removeMediaListener(PipController.MediaListener listener) {
- }
-
- /**
- * Resize the Pip to the appropriate size for the input state.
- *
- * @param state In Pip state also used to determine the new size for the Pip.
- */
- default void resizePinnedStack(int state) {
- }
-
- /**
- * Resumes resizing operation on the Pip that was previously suspended.
- *
- * @param reason The reason resizing operations on the Pip was suspended.
- */
- default void resumePipResizing(int reason) {
- }
-
- /**
- * Sets both shelf visibility and its height.
- *
- * @param visible visibility of shelf.
- * @param height to specify the height for shelf.
- */
- default void setShelfHeight(boolean visible, int height) {
- }
-
- /**
- * Set the pinned stack with {@link PipAnimationController.AnimationType}
- *
- * @param animationType The pre-defined {@link PipAnimationController.AnimationType}
- */
- default void setPinnedStackAnimationType(int animationType) {
- }
-
- /**
- * Registers the pinned stack animation listener.
- *
- * @param listener The listener of pinned stack animation.
- */
- default void setPinnedStackAnimationListener(IPinnedStackAnimationListener listener) {
- }
-
- /**
- * Suspends resizing operation on the Pip until {@link #resumePipResizing} is called.
- *
- * @param reason The reason for suspending resizing operations on the Pip.
- */
- default void suspendPipResizing(int reason) {
- }
-
- /**
- * Dump the current state and information if need.
- *
- * @param pw The stream to dump information to.
- */
- default void dump(PrintWriter pw) {
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java b/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
deleted file mode 100644
index 419d8d590124..000000000000
--- a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
+++ /dev/null
@@ -1,448 +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.pip;
-
-import android.animation.AnimationHandler;
-import android.animation.Animator;
-import android.animation.RectEvaluator;
-import android.animation.ValueAnimator;
-import android.annotation.IntDef;
-import android.graphics.Rect;
-import android.view.SurfaceControl;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
-import com.android.systemui.Interpolators;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Controller class of PiP animations (both from and to PiP mode).
- */
-public class PipAnimationController {
- private static final float FRACTION_START = 0f;
- private static final float FRACTION_END = 1f;
-
- public static final int ANIM_TYPE_BOUNDS = 0;
- public static final int ANIM_TYPE_ALPHA = 1;
-
- @IntDef(prefix = { "ANIM_TYPE_" }, value = {
- ANIM_TYPE_BOUNDS,
- ANIM_TYPE_ALPHA
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface AnimationType {}
-
- public static final int TRANSITION_DIRECTION_NONE = 0;
- public static final int TRANSITION_DIRECTION_SAME = 1;
- public static final int TRANSITION_DIRECTION_TO_PIP = 2;
- public static final int TRANSITION_DIRECTION_LEAVE_PIP = 3;
- public static final int TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN = 4;
- public static final int TRANSITION_DIRECTION_REMOVE_STACK = 5;
-
- @IntDef(prefix = { "TRANSITION_DIRECTION_" }, value = {
- TRANSITION_DIRECTION_NONE,
- TRANSITION_DIRECTION_SAME,
- TRANSITION_DIRECTION_TO_PIP,
- TRANSITION_DIRECTION_LEAVE_PIP,
- TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN,
- TRANSITION_DIRECTION_REMOVE_STACK
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface TransitionDirection {}
-
- public static boolean isInPipDirection(@TransitionDirection int direction) {
- return direction == TRANSITION_DIRECTION_TO_PIP;
- }
-
- public static boolean isOutPipDirection(@TransitionDirection int direction) {
- return direction == TRANSITION_DIRECTION_LEAVE_PIP
- || direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN;
- }
-
- private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
-
- private PipTransitionAnimator mCurrentAnimator;
-
- private ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal =
- ThreadLocal.withInitial(() -> {
- AnimationHandler handler = new AnimationHandler();
- handler.setProvider(new SfVsyncFrameCallbackProvider());
- return handler;
- });
-
- PipAnimationController(PipSurfaceTransactionHelper helper) {
- mSurfaceTransactionHelper = helper;
- }
-
- @SuppressWarnings("unchecked")
- PipTransitionAnimator getAnimator(SurfaceControl leash,
- Rect destinationBounds, float alphaStart, float alphaEnd) {
- if (mCurrentAnimator == null) {
- mCurrentAnimator = setupPipTransitionAnimator(
- PipTransitionAnimator.ofAlpha(leash, destinationBounds, alphaStart, alphaEnd));
- } else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_ALPHA
- && mCurrentAnimator.isRunning()) {
- mCurrentAnimator.updateEndValue(alphaEnd);
- } else {
- mCurrentAnimator.cancel();
- mCurrentAnimator = setupPipTransitionAnimator(
- PipTransitionAnimator.ofAlpha(leash, destinationBounds, alphaStart, alphaEnd));
- }
- return mCurrentAnimator;
- }
-
- @SuppressWarnings("unchecked")
- PipTransitionAnimator getAnimator(SurfaceControl leash, Rect startBounds, Rect endBounds,
- Rect sourceHintRect, @PipAnimationController.TransitionDirection int direction) {
- if (mCurrentAnimator == null) {
- mCurrentAnimator = setupPipTransitionAnimator(
- PipTransitionAnimator.ofBounds(leash, startBounds, endBounds, sourceHintRect,
- direction));
- } else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_ALPHA
- && mCurrentAnimator.isRunning()) {
- // If we are still animating the fade into pip, then just move the surface and ensure
- // we update with the new destination bounds, but don't interrupt the existing animation
- // with a new bounds
- mCurrentAnimator.setDestinationBounds(endBounds);
- } else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_BOUNDS
- && mCurrentAnimator.isRunning()) {
- mCurrentAnimator.setDestinationBounds(endBounds);
- // construct new Rect instances in case they are recycled
- mCurrentAnimator.updateEndValue(new Rect(endBounds));
- } else {
- mCurrentAnimator.cancel();
- mCurrentAnimator = setupPipTransitionAnimator(
- PipTransitionAnimator.ofBounds(leash, startBounds, endBounds, sourceHintRect,
- direction));
- }
- return mCurrentAnimator;
- }
-
- PipTransitionAnimator getCurrentAnimator() {
- return mCurrentAnimator;
- }
-
- private PipTransitionAnimator setupPipTransitionAnimator(PipTransitionAnimator animator) {
- animator.setSurfaceTransactionHelper(mSurfaceTransactionHelper);
- animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
- animator.setFloatValues(FRACTION_START, FRACTION_END);
- animator.setAnimationHandler(mSfAnimationHandlerThreadLocal.get());
- return animator;
- }
-
- /**
- * Additional callback interface for PiP animation
- */
- public static class PipAnimationCallback {
- /**
- * Called when PiP animation is started.
- */
- public void onPipAnimationStart(PipTransitionAnimator animator) {}
-
- /**
- * Called when PiP animation is ended.
- */
- public void onPipAnimationEnd(SurfaceControl.Transaction tx,
- PipTransitionAnimator animator) {}
-
- /**
- * Called when PiP animation is cancelled.
- */
- public void onPipAnimationCancel(PipTransitionAnimator animator) {}
- }
-
- /**
- * Animator for PiP transition animation which supports both alpha and bounds animation.
- * @param <T> Type of property to animate, either alpha (float) or bounds (Rect)
- */
- public abstract static class PipTransitionAnimator<T> extends ValueAnimator implements
- ValueAnimator.AnimatorUpdateListener,
- ValueAnimator.AnimatorListener {
- private final SurfaceControl mLeash;
- private final @AnimationType int mAnimationType;
- private final Rect mDestinationBounds = new Rect();
-
- protected T mCurrentValue;
- protected T mStartValue;
- private T mEndValue;
- private PipAnimationCallback mPipAnimationCallback;
- private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
- mSurfaceControlTransactionFactory;
- private PipSurfaceTransactionHelper mSurfaceTransactionHelper;
- private @TransitionDirection int mTransitionDirection;
-
- private PipTransitionAnimator(SurfaceControl leash, @AnimationType int animationType,
- Rect destinationBounds, T startValue, T endValue) {
- mLeash = leash;
- mAnimationType = animationType;
- mDestinationBounds.set(destinationBounds);
- mStartValue = startValue;
- mEndValue = endValue;
- addListener(this);
- addUpdateListener(this);
- mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
- mTransitionDirection = TRANSITION_DIRECTION_NONE;
- }
-
- @Override
- public void onAnimationStart(Animator animation) {
- mCurrentValue = mStartValue;
- onStartTransaction(mLeash, newSurfaceControlTransaction());
- if (mPipAnimationCallback != null) {
- mPipAnimationCallback.onPipAnimationStart(this);
- }
- }
-
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- applySurfaceControlTransaction(mLeash, newSurfaceControlTransaction(),
- animation.getAnimatedFraction());
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- mCurrentValue = mEndValue;
- final SurfaceControl.Transaction tx = newSurfaceControlTransaction();
- onEndTransaction(mLeash, tx);
- if (mPipAnimationCallback != null) {
- mPipAnimationCallback.onPipAnimationEnd(tx, this);
- }
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- if (mPipAnimationCallback != null) {
- mPipAnimationCallback.onPipAnimationCancel(this);
- }
- }
-
- @Override public void onAnimationRepeat(Animator animation) {}
-
- @AnimationType int getAnimationType() {
- return mAnimationType;
- }
-
- PipTransitionAnimator<T> setPipAnimationCallback(PipAnimationCallback callback) {
- mPipAnimationCallback = callback;
- return this;
- }
-
- @TransitionDirection int getTransitionDirection() {
- return mTransitionDirection;
- }
-
- PipTransitionAnimator<T> setTransitionDirection(@TransitionDirection int direction) {
- if (direction != TRANSITION_DIRECTION_SAME) {
- mTransitionDirection = direction;
- }
- return this;
- }
-
- T getStartValue() {
- return mStartValue;
- }
-
- T getEndValue() {
- return mEndValue;
- }
-
- Rect getDestinationBounds() {
- return mDestinationBounds;
- }
-
- void setDestinationBounds(Rect destinationBounds) {
- mDestinationBounds.set(destinationBounds);
- if (mAnimationType == ANIM_TYPE_ALPHA) {
- onStartTransaction(mLeash, newSurfaceControlTransaction());
- }
- }
-
- void setCurrentValue(T value) {
- mCurrentValue = value;
- }
-
- boolean shouldApplyCornerRadius() {
- return !isOutPipDirection(mTransitionDirection);
- }
-
- boolean inScaleTransition() {
- if (mAnimationType != ANIM_TYPE_BOUNDS) return false;
- final int direction = getTransitionDirection();
- return !isInPipDirection(direction) && !isOutPipDirection(direction);
- }
-
- /**
- * Updates the {@link #mEndValue}.
- *
- * NOTE: Do not forget to call {@link #setDestinationBounds(Rect)} for bounds animation.
- * This is typically used when we receive a shelf height adjustment during the bounds
- * animation. In which case we can update the end bounds and keep the existing animation
- * running instead of cancelling it.
- */
- void updateEndValue(T endValue) {
- mEndValue = endValue;
- }
-
- SurfaceControl.Transaction newSurfaceControlTransaction() {
- return mSurfaceControlTransactionFactory.getTransaction();
- }
-
- @VisibleForTesting
- void setSurfaceControlTransactionFactory(
- PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) {
- mSurfaceControlTransactionFactory = factory;
- }
-
- PipSurfaceTransactionHelper getSurfaceTransactionHelper() {
- return mSurfaceTransactionHelper;
- }
-
- void setSurfaceTransactionHelper(PipSurfaceTransactionHelper helper) {
- mSurfaceTransactionHelper = helper;
- }
-
- void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) {}
-
- void onEndTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) {}
-
- abstract void applySurfaceControlTransaction(SurfaceControl leash,
- SurfaceControl.Transaction tx, float fraction);
-
- static PipTransitionAnimator<Float> ofAlpha(SurfaceControl leash,
- Rect destinationBounds, float startValue, float endValue) {
- return new PipTransitionAnimator<Float>(leash, ANIM_TYPE_ALPHA,
- destinationBounds, startValue, endValue) {
- @Override
- void applySurfaceControlTransaction(SurfaceControl leash,
- SurfaceControl.Transaction tx, float fraction) {
- final float alpha = getStartValue() * (1 - fraction) + getEndValue() * fraction;
- setCurrentValue(alpha);
- getSurfaceTransactionHelper().alpha(tx, leash, alpha);
- tx.apply();
- }
-
- @Override
- void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) {
- if (getTransitionDirection() == TRANSITION_DIRECTION_REMOVE_STACK) {
- // while removing the pip stack, no extra work needs to be done here.
- return;
- }
- getSurfaceTransactionHelper()
- .resetScale(tx, leash, getDestinationBounds())
- .crop(tx, leash, getDestinationBounds())
- .round(tx, leash, shouldApplyCornerRadius());
- tx.show(leash);
- tx.apply();
- }
-
- @Override
- void updateEndValue(Float endValue) {
- super.updateEndValue(endValue);
- mStartValue = mCurrentValue;
- }
- };
- }
-
- static PipTransitionAnimator<Rect> ofBounds(SurfaceControl leash,
- Rect startValue, Rect endValue, Rect sourceHintRect,
- @PipAnimationController.TransitionDirection int direction) {
- // Just for simplicity we'll interpolate between the source rect hint insets and empty
- // insets to calculate the window crop
- final Rect initialSourceValue;
- if (isOutPipDirection(direction)) {
- initialSourceValue = new Rect(endValue);
- } else {
- initialSourceValue = new Rect(startValue);
- }
-
- final Rect sourceHintRectInsets;
- if (sourceHintRect == null) {
- sourceHintRectInsets = null;
- } else {
- sourceHintRectInsets = new Rect(sourceHintRect.left - initialSourceValue.left,
- sourceHintRect.top - initialSourceValue.top,
- initialSourceValue.right - sourceHintRect.right,
- initialSourceValue.bottom - sourceHintRect.bottom);
- }
- final Rect sourceInsets = new Rect(0, 0, 0, 0);
-
- // construct new Rect instances in case they are recycled
- return new PipTransitionAnimator<Rect>(leash, ANIM_TYPE_BOUNDS,
- endValue, new Rect(startValue), new Rect(endValue)) {
- private final RectEvaluator mRectEvaluator = new RectEvaluator(new Rect());
- private final RectEvaluator mInsetsEvaluator = new RectEvaluator(new Rect());
-
- @Override
- void applySurfaceControlTransaction(SurfaceControl leash,
- SurfaceControl.Transaction tx, float fraction) {
- final Rect start = getStartValue();
- final Rect end = getEndValue();
- Rect bounds = mRectEvaluator.evaluate(fraction, start, end);
- setCurrentValue(bounds);
- if (inScaleTransition() || sourceHintRect == null) {
- if (isOutPipDirection(direction)) {
- getSurfaceTransactionHelper().scale(tx, leash, end, bounds);
- } else {
- getSurfaceTransactionHelper().scale(tx, leash, start, bounds);
- }
- } else {
- final Rect insets;
- if (isOutPipDirection(direction)) {
- insets = mInsetsEvaluator.evaluate(fraction, sourceHintRectInsets,
- sourceInsets);
- } else {
- insets = mInsetsEvaluator.evaluate(fraction, sourceInsets,
- sourceHintRectInsets);
- }
- getSurfaceTransactionHelper().scaleAndCrop(tx, leash,
- initialSourceValue, bounds, insets);
- }
- tx.apply();
- }
-
- @Override
- void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) {
- getSurfaceTransactionHelper()
- .alpha(tx, leash, 1f)
- .round(tx, leash, shouldApplyCornerRadius());
- tx.show(leash);
- tx.apply();
- }
-
- @Override
- void onEndTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) {
- // NOTE: intentionally does not apply the transaction here.
- // this end transaction should get executed synchronously with the final
- // WindowContainerTransaction in task organizer
- getSurfaceTransactionHelper()
- .resetScale(tx, leash, getDestinationBounds())
- .crop(tx, leash, getDestinationBounds());
- }
-
- @Override
- void updateEndValue(Rect endValue) {
- super.updateEndValue(endValue);
- if (mStartValue != null && mCurrentValue != null) {
- mStartValue.set(mCurrentValue);
- }
- }
- };
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
deleted file mode 100644
index 89b5c38d94a7..000000000000
--- a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
+++ /dev/null
@@ -1,553 +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.pip;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.util.TypedValue.COMPLEX_UNIT_DIP;
-import static android.view.Surface.ROTATION_0;
-import static android.view.Surface.ROTATION_180;
-
-import android.app.ActivityTaskManager;
-import android.app.ActivityTaskManager.RootTaskInfo;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.os.RemoteException;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.util.Size;
-import android.util.TypedValue;
-import android.view.Display;
-import android.view.DisplayInfo;
-import android.view.Gravity;
-import android.window.WindowContainerTransaction;
-
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.wm.shell.common.DisplayLayout;
-
-import java.io.PrintWriter;
-
-/**
- * Handles bounds calculation for PIP on Phone and other form factors, it keeps tracking variant
- * state changes originated from Window Manager and is the source of truth for PiP window bounds.
- */
-@SysUISingleton
-public class PipBoundsHandler {
-
- private static final String TAG = PipBoundsHandler.class.getSimpleName();
- private static final float INVALID_SNAP_FRACTION = -1f;
-
- private final PipSnapAlgorithm mSnapAlgorithm;
- private final DisplayInfo mDisplayInfo = new DisplayInfo();
- private DisplayLayout mDisplayLayout;
-
- private ComponentName mLastPipComponentName;
- private float mReentrySnapFraction = INVALID_SNAP_FRACTION;
- private Size mReentrySize;
-
- private float mDefaultAspectRatio;
- private float mMinAspectRatio;
- private float mMaxAspectRatio;
- private float mAspectRatio;
- private int mDefaultStackGravity;
- private int mDefaultMinSize;
- private Point mScreenEdgeInsets;
- private int mCurrentMinSize;
- private Size mOverrideMinimalSize;
-
- private boolean mIsImeShowing;
- private int mImeHeight;
- private boolean mIsShelfShowing;
- private int mShelfHeight;
-
- public PipBoundsHandler(Context context) {
- mSnapAlgorithm = new PipSnapAlgorithm(context);
- mDisplayLayout = new DisplayLayout();
- reloadResources(context);
- // Initialize the aspect ratio to the default aspect ratio. Don't do this in reload
- // resources as it would clobber mAspectRatio when entering PiP from fullscreen which
- // triggers a configuration change and the resources to be reloaded.
- mAspectRatio = mDefaultAspectRatio;
- }
-
- /**
- * TODO: move the resources to SysUI package.
- */
- private void reloadResources(Context context) {
- final Resources res = context.getResources();
- mDefaultAspectRatio = res.getFloat(
- com.android.internal.R.dimen.config_pictureInPictureDefaultAspectRatio);
- mDefaultStackGravity = res.getInteger(
- com.android.internal.R.integer.config_defaultPictureInPictureGravity);
- mDefaultMinSize = res.getDimensionPixelSize(
- com.android.internal.R.dimen.default_minimal_size_pip_resizable_task);
- mCurrentMinSize = mDefaultMinSize;
- final String screenEdgeInsetsDpString = res.getString(
- com.android.internal.R.string.config_defaultPictureInPictureScreenEdgeInsets);
- final Size screenEdgeInsetsDp = !screenEdgeInsetsDpString.isEmpty()
- ? Size.parseSize(screenEdgeInsetsDpString)
- : null;
- mScreenEdgeInsets = screenEdgeInsetsDp == null ? new Point()
- : new Point(dpToPx(screenEdgeInsetsDp.getWidth(), res.getDisplayMetrics()),
- dpToPx(screenEdgeInsetsDp.getHeight(), res.getDisplayMetrics()));
- mMinAspectRatio = res.getFloat(
- com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio);
- mMaxAspectRatio = res.getFloat(
- com.android.internal.R.dimen.config_pictureInPictureMaxAspectRatio);
- }
-
- /**
- * Sets or update latest {@link DisplayLayout} when new display added or rotation callbacks
- * from {@link DisplayController.OnDisplaysChangedListener}
- * @param newDisplayLayout latest {@link DisplayLayout}
- */
- public void setDisplayLayout(DisplayLayout newDisplayLayout) {
- mDisplayLayout.set(newDisplayLayout);
- }
-
- /**
- * Update the Min edge size for {@link PipSnapAlgorithm} to calculate corresponding bounds
- * @param minEdgeSize
- */
- public void setMinEdgeSize(int minEdgeSize) {
- 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.
- */
- public boolean setShelfHeight(boolean shelfVisible, int shelfHeight) {
- final boolean shelfShowing = shelfVisible && shelfHeight > 0;
- if (shelfShowing == mIsShelfShowing && shelfHeight == mShelfHeight) {
- return false;
- }
-
- mIsShelfShowing = shelfVisible;
- mShelfHeight = shelfHeight;
- return true;
- }
-
- /**
- * Responds to IPinnedStackListener on IME visibility change.
- */
- public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
- mIsImeShowing = imeVisible;
- mImeHeight = imeHeight;
- }
-
- /**
- * Responds to IPinnedStackListener on movement bounds change.
- * Note that both inset and normal bounds will be calculated here rather than in the caller.
- */
- public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds,
- Rect animatingBounds, DisplayInfo displayInfo) {
- getInsetBounds(insetBounds);
- final Rect defaultBounds = getDefaultBounds(INVALID_SNAP_FRACTION, null);
- normalBounds.set(defaultBounds);
- if (animatingBounds.isEmpty()) {
- animatingBounds.set(defaultBounds);
- }
- if (isValidPictureInPictureAspectRatio(mAspectRatio)) {
- transformBoundsToAspectRatio(normalBounds, mAspectRatio,
- false /* useCurrentMinEdgeSize */, false /* useCurrentSize */);
- }
- displayInfo.copyFrom(mDisplayInfo);
- }
-
- /**
- * Responds to IPinnedStackListener on saving reentry snap fraction and size
- * for a given {@link ComponentName}.
- */
- public void onSaveReentryBounds(ComponentName componentName, Rect bounds) {
- mReentrySnapFraction = getSnapFraction(bounds);
- mReentrySize = new Size(bounds.width(), bounds.height());
- mLastPipComponentName = componentName;
- }
-
- /**
- * Responds to IPinnedStackListener on resetting reentry snap fraction and size
- * for a given {@link ComponentName}.
- */
- public void onResetReentryBounds(ComponentName componentName) {
- if (componentName.equals(mLastPipComponentName)) {
- onResetReentryBoundsUnchecked();
- }
- }
-
- private void onResetReentryBoundsUnchecked() {
- mReentrySnapFraction = INVALID_SNAP_FRACTION;
- mReentrySize = null;
- mLastPipComponentName = null;
- }
-
- /**
- * Returns ture if there's a valid snap fraction. This is used with {@link EXTRA_IS_FIRST_ENTRY}
- * to see if this is the first time user has entered PIP for the component.
- */
- public boolean hasSaveReentryBounds() {
- return mReentrySnapFraction != INVALID_SNAP_FRACTION;
- }
-
- /**
- * The {@link PipSnapAlgorithm} is couple on display bounds
- * @return {@link PipSnapAlgorithm}.
- */
- public PipSnapAlgorithm getSnapAlgorithm() {
- return mSnapAlgorithm;
- }
-
- public Rect getDisplayBounds() {
- return new Rect(0, 0, mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
- }
-
- public int getDisplayRotation() {
- return mDisplayInfo.rotation;
- }
-
- /**
- * Responds to IPinnedStackListener on {@link DisplayInfo} change.
- * It will normally follow up with a
- * {@link #onMovementBoundsChanged(Rect, Rect, Rect, DisplayInfo)} callback.
- */
- public void onDisplayInfoChanged(DisplayInfo displayInfo) {
- mDisplayInfo.copyFrom(displayInfo);
- }
-
- /**
- * Responds to IPinnedStackListener on configuration change.
- */
- public void onConfigurationChanged(Context context) {
- reloadResources(context);
- }
-
- /**
- * Responds to IPinnedStackListener on resetting aspect ratio for the pinned window.
- * It will normally follow up with a
- * {@link #onMovementBoundsChanged(Rect, Rect, Rect, DisplayInfo)} callback.
- */
- public void onAspectRatioChanged(float aspectRatio) {
- mAspectRatio = aspectRatio;
- }
-
- /**
- * See {@link #getDestinationBounds(ComponentName, float, Rect, Size, boolean)}
- */
- Rect getDestinationBounds(ComponentName componentName, float aspectRatio, Rect bounds,
- Size minimalSize) {
- return getDestinationBounds(componentName, aspectRatio, bounds, minimalSize,
- false /* useCurrentMinEdgeSize */);
- }
-
- /**
- * @return {@link Rect} of the destination PiP window bounds.
- */
- Rect getDestinationBounds(ComponentName componentName, float aspectRatio, Rect bounds,
- Size minimalSize, boolean useCurrentMinEdgeSize) {
- if (!componentName.equals(mLastPipComponentName)) {
- onResetReentryBoundsUnchecked();
- mLastPipComponentName = componentName;
- }
- final Rect destinationBounds;
- if (bounds == null) {
- final Rect defaultBounds = getDefaultBounds(mReentrySnapFraction, mReentrySize);
- destinationBounds = new Rect(defaultBounds);
- if (mReentrySnapFraction == INVALID_SNAP_FRACTION && mReentrySize == null) {
- mOverrideMinimalSize = minimalSize;
- }
- } else {
- destinationBounds = new Rect(bounds);
- }
- if (isValidPictureInPictureAspectRatio(aspectRatio)) {
- boolean useCurrentSize = bounds == null && mReentrySize != null;
- transformBoundsToAspectRatio(destinationBounds, aspectRatio, useCurrentMinEdgeSize,
- useCurrentSize);
- }
- mAspectRatio = aspectRatio;
- return destinationBounds;
- }
-
- float getDefaultAspectRatio() {
- return mDefaultAspectRatio;
- }
-
- public void onOverlayChanged(Context context, Display display) {
- mDisplayLayout = new DisplayLayout(context, display);
- }
-
- /**
- * Updatest the display info and display layout on rotation change. This is needed even when we
- * aren't in PIP because the rotation layout is used to calculate the proper insets for the
- * next enter animation into PIP.
- */
- public void onDisplayRotationChangedNotInPip(Context context, int toRotation) {
- // Update the display layout, note that we have to do this on every rotation even if we
- // aren't in PIP since we need to update the display layout to get the right resources
- mDisplayLayout.rotateTo(context.getResources(), toRotation);
-
- // Populate the new {@link #mDisplayInfo}.
- // The {@link DisplayInfo} queried from DisplayManager would be the one before rotation,
- // therefore, the width/height may require a swap first.
- // Moving forward, we should get the new dimensions after rotation from DisplayLayout.
- mDisplayInfo.rotation = toRotation;
- updateDisplayInfoIfNeeded();
- }
-
- /**
- * Updates the display info, calculating and returning the new stack and movement bounds in the
- * new orientation of the device if necessary.
- *
- * @return {@code true} if internal {@link DisplayInfo} is rotated, {@code false} otherwise.
- */
- public boolean onDisplayRotationChanged(Context context, Rect outBounds, Rect oldBounds,
- Rect outInsetBounds,
- int displayId, int fromRotation, int toRotation, WindowContainerTransaction t) {
- // Bail early if the event is not sent to current {@link #mDisplayInfo}
- if ((displayId != mDisplayInfo.displayId) || (fromRotation == toRotation)) {
- return false;
- }
-
- // Bail early if the pinned task is staled.
- final RootTaskInfo pinnedTaskInfo;
- try {
- pinnedTaskInfo = ActivityTaskManager.getService()
- .getRootTaskInfo(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
- if (pinnedTaskInfo == null) return false;
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to get RootTaskInfo for pinned task", e);
- return false;
- }
-
- // Calculate the snap fraction of the current stack along the old movement bounds
- final Rect postChangeStackBounds = new Rect(oldBounds);
- final float snapFraction = getSnapFraction(postChangeStackBounds);
-
- // Update the display layout
- mDisplayLayout.rotateTo(context.getResources(), toRotation);
-
- // Populate the new {@link #mDisplayInfo}.
- // The {@link DisplayInfo} queried from DisplayManager would be the one before rotation,
- // therefore, the width/height may require a swap first.
- // Moving forward, we should get the new dimensions after rotation from DisplayLayout.
- mDisplayInfo.rotation = toRotation;
- updateDisplayInfoIfNeeded();
-
- // Calculate the stack bounds in the new orientation based on same fraction along the
- // rotated movement bounds.
- final Rect postChangeMovementBounds = getMovementBounds(postChangeStackBounds,
- false /* adjustForIme */);
- mSnapAlgorithm.applySnapFraction(postChangeStackBounds, postChangeMovementBounds,
- snapFraction);
-
- getInsetBounds(outInsetBounds);
- outBounds.set(postChangeStackBounds);
- t.setBounds(pinnedTaskInfo.token, outBounds);
- return true;
- }
-
- private void updateDisplayInfoIfNeeded() {
- final boolean updateNeeded;
- if ((mDisplayInfo.rotation == ROTATION_0) || (mDisplayInfo.rotation == ROTATION_180)) {
- updateNeeded = (mDisplayInfo.logicalWidth > mDisplayInfo.logicalHeight);
- } else {
- updateNeeded = (mDisplayInfo.logicalWidth < mDisplayInfo.logicalHeight);
- }
- if (updateNeeded) {
- final int newLogicalHeight = mDisplayInfo.logicalWidth;
- mDisplayInfo.logicalWidth = mDisplayInfo.logicalHeight;
- mDisplayInfo.logicalHeight = newLogicalHeight;
- }
- }
-
- /**
- * @return whether the given {@param aspectRatio} is valid.
- */
- private boolean isValidPictureInPictureAspectRatio(float aspectRatio) {
- return Float.compare(mMinAspectRatio, aspectRatio) <= 0
- && Float.compare(aspectRatio, mMaxAspectRatio) <= 0;
- }
-
- /**
- * Sets the current bound with the currently store aspect ratio.
- * @param stackBounds
- */
- public void transformBoundsToAspectRatio(Rect stackBounds) {
- transformBoundsToAspectRatio(stackBounds, mAspectRatio, true /* useCurrentMinEdgeSize */,
- true /* useCurrentSize */);
- }
-
- /**
- * Set the current bounds (or the default bounds if there are no current bounds) with the
- * specified aspect ratio.
- */
- private void transformBoundsToAspectRatio(Rect stackBounds, float aspectRatio,
- boolean useCurrentMinEdgeSize, boolean useCurrentSize) {
- // Save the snap fraction and adjust the size based on the new aspect ratio.
- final float snapFraction = mSnapAlgorithm.getSnapFraction(stackBounds,
- getMovementBounds(stackBounds));
- final int minEdgeSize = useCurrentMinEdgeSize ? mCurrentMinSize : mDefaultMinSize;
- final Size size;
- if (useCurrentMinEdgeSize || useCurrentSize) {
- size = mSnapAlgorithm.getSizeForAspectRatio(
- new Size(stackBounds.width(), stackBounds.height()), aspectRatio, minEdgeSize);
- } else {
- size = mSnapAlgorithm.getSizeForAspectRatio(aspectRatio, minEdgeSize,
- mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
- }
-
- final int left = (int) (stackBounds.centerX() - size.getWidth() / 2f);
- final int top = (int) (stackBounds.centerY() - size.getHeight() / 2f);
- stackBounds.set(left, top, left + size.getWidth(), top + size.getHeight());
- // apply the override minimal size if applicable, this minimal size is specified by app
- if (mOverrideMinimalSize != null) {
- transformBoundsToMinimalSize(stackBounds, aspectRatio, mOverrideMinimalSize);
- }
- mSnapAlgorithm.applySnapFraction(stackBounds, getMovementBounds(stackBounds), snapFraction);
- }
-
- /**
- * Transforms a given bounds to meet the minimal size constraints.
- * This function assumes the given {@param stackBounds} qualifies {@param aspectRatio}.
- */
- private void transformBoundsToMinimalSize(Rect stackBounds, float aspectRatio,
- Size minimalSize) {
- if (minimalSize == null) return;
- final Size adjustedMinimalSize;
- final float minimalSizeAspectRatio =
- minimalSize.getWidth() / (float) minimalSize.getHeight();
- if (minimalSizeAspectRatio > aspectRatio) {
- // minimal size is wider, fixed the width and increase the height
- adjustedMinimalSize = new Size(
- minimalSize.getWidth(), (int) (minimalSize.getWidth() / aspectRatio));
- } else {
- adjustedMinimalSize = new Size(
- (int) (minimalSize.getHeight() * aspectRatio), minimalSize.getHeight());
- }
- final Rect containerBounds = new Rect(stackBounds);
- Gravity.apply(mDefaultStackGravity,
- adjustedMinimalSize.getWidth(), adjustedMinimalSize.getHeight(),
- containerBounds, stackBounds);
- }
-
- /**
- * @return the default bounds to show the PIP, if a {@param snapFraction} and {@param size} are
- * provided, then it will apply the default bounds to the provided snap fraction and size.
- */
- private Rect getDefaultBounds(float snapFraction, Size size) {
- final Rect defaultBounds = new Rect();
- if (snapFraction != INVALID_SNAP_FRACTION && size != null) {
- defaultBounds.set(0, 0, size.getWidth(), size.getHeight());
- final Rect movementBounds = getMovementBounds(defaultBounds);
- mSnapAlgorithm.applySnapFraction(defaultBounds, movementBounds, snapFraction);
- } else {
- final Rect insetBounds = new Rect();
- getInsetBounds(insetBounds);
- size = mSnapAlgorithm.getSizeForAspectRatio(mDefaultAspectRatio,
- mDefaultMinSize, mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
- Gravity.apply(mDefaultStackGravity, size.getWidth(), size.getHeight(), insetBounds,
- 0, Math.max(mIsImeShowing ? mImeHeight : 0,
- mIsShelfShowing ? mShelfHeight : 0),
- defaultBounds);
- }
- return defaultBounds;
- }
-
- /**
- * Populates the bounds on the screen that the PIP can be visible in.
- */
- protected void getInsetBounds(Rect outRect) {
- Rect insets = mDisplayLayout.stableInsets();
- outRect.set(insets.left + mScreenEdgeInsets.x,
- insets.top + mScreenEdgeInsets.y,
- mDisplayInfo.logicalWidth - insets.right - mScreenEdgeInsets.x,
- mDisplayInfo.logicalHeight - insets.bottom - mScreenEdgeInsets.y);
- }
-
- /**
- * @return the movement bounds for the given {@param stackBounds} and the current state of the
- * controller.
- */
- private Rect getMovementBounds(Rect stackBounds) {
- return getMovementBounds(stackBounds, true /* adjustForIme */);
- }
-
- /**
- * @return the movement bounds for the given {@param stackBounds} and the current state of the
- * controller.
- */
- private Rect getMovementBounds(Rect stackBounds, boolean adjustForIme) {
- final Rect movementBounds = new Rect();
- getInsetBounds(movementBounds);
-
- // Apply the movement bounds adjustments based on the current state.
- mSnapAlgorithm.getMovementBounds(stackBounds, movementBounds, movementBounds,
- (adjustForIme && mIsImeShowing) ? mImeHeight : 0);
- return movementBounds;
- }
-
- /**
- * @return the default snap fraction to apply instead of the default gravity when calculating
- * the default stack bounds when first entering PiP.
- */
- public float getSnapFraction(Rect stackBounds) {
- return mSnapAlgorithm.getSnapFraction(stackBounds, getMovementBounds(stackBounds));
- }
-
- /**
- * Applies the given snap fraction to the given stack bounds.
- */
- public void applySnapFraction(Rect stackBounds, float snapFraction) {
- final Rect movementBounds = getMovementBounds(stackBounds);
- mSnapAlgorithm.applySnapFraction(stackBounds, movementBounds, snapFraction);
- }
-
- /**
- * @return the pixels for a given dp value.
- */
- private int dpToPx(float dpValue, DisplayMetrics dm) {
- return (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, dpValue, dm);
- }
-
- /**
- * Dumps internal states.
- */
- public void dump(PrintWriter pw, String prefix) {
- final String innerPrefix = prefix + " ";
- pw.println(prefix + TAG);
- pw.println(innerPrefix + "mLastPipComponentName=" + mLastPipComponentName);
- pw.println(innerPrefix + "mReentrySnapFraction=" + mReentrySnapFraction);
- pw.println(innerPrefix + "mReentrySize=" + mReentrySize);
- pw.println(innerPrefix + "mDisplayInfo=" + mDisplayInfo);
- pw.println(innerPrefix + "mDefaultAspectRatio=" + mDefaultAspectRatio);
- pw.println(innerPrefix + "mMinAspectRatio=" + mMinAspectRatio);
- pw.println(innerPrefix + "mMaxAspectRatio=" + mMaxAspectRatio);
- pw.println(innerPrefix + "mAspectRatio=" + mAspectRatio);
- pw.println(innerPrefix + "mDefaultStackGravity=" + mDefaultStackGravity);
- pw.println(innerPrefix + "mIsImeShowing=" + mIsImeShowing);
- pw.println(innerPrefix + "mImeHeight=" + mImeHeight);
- pw.println(innerPrefix + "mIsShelfShowing=" + mIsShelfShowing);
- pw.println(innerPrefix + "mShelfHeight=" + mShelfHeight);
- pw.println(innerPrefix + "mSnapAlgorithm" + mSnapAlgorithm);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipSnapAlgorithm.java b/packages/SystemUI/src/com/android/systemui/pip/PipSnapAlgorithm.java
deleted file mode 100644
index 5d23e4207c33..000000000000
--- a/packages/SystemUI/src/com/android/systemui/pip/PipSnapAlgorithm.java
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.pip;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.util.Size;
-
-/**
- * Calculates the snap targets and the snap position for the PIP given a position and a velocity.
- * All bounds are relative to the display top/left.
- */
-public class PipSnapAlgorithm {
-
- private final float mDefaultSizePercent;
- private final float mMinAspectRatioForMinSize;
- private final float mMaxAspectRatioForMinSize;
-
- public PipSnapAlgorithm(Context context) {
- Resources res = context.getResources();
- mDefaultSizePercent = res.getFloat(
- com.android.internal.R.dimen.config_pictureInPictureDefaultSizePercent);
- mMaxAspectRatioForMinSize = res.getFloat(
- com.android.internal.R.dimen.config_pictureInPictureAspectRatioLimitForMinSize);
- mMinAspectRatioForMinSize = 1f / mMaxAspectRatioForMinSize;
- }
-
- /**
- * @return returns a fraction that describes where along the {@param movementBounds} the
- * {@param stackBounds} are. If the {@param stackBounds} are not currently on the
- * {@param movementBounds} exactly, then they will be snapped to the movement bounds.
- *
- * The fraction is defined in a clockwise fashion against the {@param movementBounds}:
- *
- * 0 1
- * 4 +---+ 1
- * | |
- * 3 +---+ 2
- * 3 2
- */
- public float getSnapFraction(Rect stackBounds, Rect movementBounds) {
- final Rect tmpBounds = new Rect();
- snapRectToClosestEdge(stackBounds, movementBounds, tmpBounds);
- final float widthFraction = (float) (tmpBounds.left - movementBounds.left) /
- movementBounds.width();
- final float heightFraction = (float) (tmpBounds.top - movementBounds.top) /
- movementBounds.height();
- if (tmpBounds.top == movementBounds.top) {
- return widthFraction;
- } else if (tmpBounds.left == movementBounds.right) {
- return 1f + heightFraction;
- } else if (tmpBounds.top == movementBounds.bottom) {
- return 2f + (1f - widthFraction);
- } else {
- return 3f + (1f - heightFraction);
- }
- }
-
- /**
- * Moves the {@param stackBounds} along the {@param movementBounds} to the given snap fraction.
- * See {@link #getSnapFraction(Rect, Rect)}.
- *
- * The fraction is define in a clockwise fashion against the {@param movementBounds}:
- *
- * 0 1
- * 4 +---+ 1
- * | |
- * 3 +---+ 2
- * 3 2
- */
- public void applySnapFraction(Rect stackBounds, Rect movementBounds, float snapFraction) {
- if (snapFraction < 1f) {
- int offset = movementBounds.left + (int) (snapFraction * movementBounds.width());
- stackBounds.offsetTo(offset, movementBounds.top);
- } else if (snapFraction < 2f) {
- snapFraction -= 1f;
- int offset = movementBounds.top + (int) (snapFraction * movementBounds.height());
- stackBounds.offsetTo(movementBounds.right, offset);
- } else if (snapFraction < 3f) {
- snapFraction -= 2f;
- int offset = movementBounds.left + (int) ((1f - snapFraction) * movementBounds.width());
- stackBounds.offsetTo(offset, movementBounds.bottom);
- } else {
- snapFraction -= 3f;
- int offset = movementBounds.top + (int) ((1f - snapFraction) * movementBounds.height());
- stackBounds.offsetTo(movementBounds.left, offset);
- }
- }
-
- /**
- * Adjusts {@param movementBoundsOut} so that it is the movement bounds for the given
- * {@param stackBounds}.
- */
- public void getMovementBounds(Rect stackBounds, Rect insetBounds, Rect movementBoundsOut,
- int bottomOffset) {
- // Adjust the right/bottom to ensure the stack bounds never goes offscreen
- movementBoundsOut.set(insetBounds);
- movementBoundsOut.right = Math.max(insetBounds.left, insetBounds.right -
- stackBounds.width());
- movementBoundsOut.bottom = Math.max(insetBounds.top, insetBounds.bottom -
- stackBounds.height());
- movementBoundsOut.bottom -= bottomOffset;
- }
-
- /**
- * @return the size of the PiP at the given {@param aspectRatio}, ensuring that the minimum edge
- * is at least {@param minEdgeSize}.
- */
- public Size getSizeForAspectRatio(float aspectRatio, float minEdgeSize, int displayWidth,
- int displayHeight) {
- final int smallestDisplaySize = Math.min(displayWidth, displayHeight);
- final int minSize = (int) Math.max(minEdgeSize, smallestDisplaySize * mDefaultSizePercent);
-
- final int width;
- final int height;
- if (aspectRatio <= mMinAspectRatioForMinSize || aspectRatio > mMaxAspectRatioForMinSize) {
- // Beyond these points, we can just use the min size as the shorter edge
- if (aspectRatio <= 1) {
- // Portrait, width is the minimum size
- width = minSize;
- height = Math.round(width / aspectRatio);
- } else {
- // Landscape, height is the minimum size
- height = minSize;
- width = Math.round(height * aspectRatio);
- }
- } else {
- // Within these points, we ensure that the bounds fit within the radius of the limits
- // at the points
- final float widthAtMaxAspectRatioForMinSize = mMaxAspectRatioForMinSize * minSize;
- final float radius = PointF.length(widthAtMaxAspectRatioForMinSize, minSize);
- height = (int) Math.round(Math.sqrt((radius * radius) /
- (aspectRatio * aspectRatio + 1)));
- width = Math.round(height * aspectRatio);
- }
- return new Size(width, height);
- }
-
- /**
- * @return the adjusted size so that it conforms to the given aspectRatio, ensuring that the
- * minimum edge is at least minEdgeSize.
- */
- public Size getSizeForAspectRatio(Size size, float aspectRatio, float minEdgeSize) {
- final int smallestSize = Math.min(size.getWidth(), size.getHeight());
- final int minSize = (int) Math.max(minEdgeSize, smallestSize);
-
- final int width;
- final int height;
- if (aspectRatio <= 1) {
- // Portrait, width is the minimum size.
- width = minSize;
- height = Math.round(width / aspectRatio);
- } else {
- // Landscape, height is the minimum size
- height = minSize;
- width = Math.round(height * aspectRatio);
- }
- return new Size(width, height);
- }
-
- /**
- * Snaps the {@param stackBounds} to the closest edge of the {@param movementBounds} and writes
- * the new bounds out to {@param boundsOut}.
- */
- public void snapRectToClosestEdge(Rect stackBounds, Rect movementBounds, Rect boundsOut) {
- final int boundedLeft = Math.max(movementBounds.left, Math.min(movementBounds.right,
- stackBounds.left));
- final int boundedTop = Math.max(movementBounds.top, Math.min(movementBounds.bottom,
- stackBounds.top));
- boundsOut.set(stackBounds);
-
- // Otherwise, just find the closest edge
- final int fromLeft = Math.abs(stackBounds.left - movementBounds.left);
- final int fromTop = Math.abs(stackBounds.top - movementBounds.top);
- final int fromRight = Math.abs(movementBounds.right - stackBounds.left);
- final int fromBottom = Math.abs(movementBounds.bottom - stackBounds.top);
- final int shortest = Math.min(Math.min(fromLeft, fromRight), Math.min(fromTop, fromBottom));
- if (shortest == fromLeft) {
- boundsOut.offsetTo(movementBounds.left, boundedTop);
- } else if (shortest == fromTop) {
- boundsOut.offsetTo(boundedLeft, movementBounds.top);
- } else if (shortest == fromRight) {
- boundsOut.offsetTo(movementBounds.right, boundedTop);
- } else {
- boundsOut.offsetTo(boundedLeft, movementBounds.bottom);
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipSurfaceTransactionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/PipSurfaceTransactionHelper.java
deleted file mode 100644
index 3e98169c5b2b..000000000000
--- a/packages/SystemUI/src/com/android/systemui/pip/PipSurfaceTransactionHelper.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.pip;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Matrix;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.view.SurfaceControl;
-
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.wm.shell.R;
-
-/**
- * Abstracts the common operations on {@link SurfaceControl.Transaction} for PiP transition.
- */
-@SysUISingleton
-public class PipSurfaceTransactionHelper implements ConfigurationController.ConfigurationListener {
-
- private final Context mContext;
- private final boolean mEnableCornerRadius;
- private int mCornerRadius;
-
- /** for {@link #scale(SurfaceControl.Transaction, SurfaceControl, Rect, Rect)} operation */
- private final Matrix mTmpTransform = new Matrix();
- private final float[] mTmpFloat9 = new float[9];
- private final RectF mTmpSourceRectF = new RectF();
- private final RectF mTmpDestinationRectF = new RectF();
- private final Rect mTmpDestinationRect = new Rect();
-
- public PipSurfaceTransactionHelper(Context context, ConfigurationController configController) {
- final Resources res = context.getResources();
- mContext = context;
- mEnableCornerRadius = res.getBoolean(R.bool.config_pipEnableRoundCorner);
- configController.addCallback(this);
- }
-
- @Override
- public void onDensityOrFontScaleChanged() {
- final Resources res = mContext.getResources();
- mCornerRadius = res.getDimensionPixelSize(R.dimen.pip_corner_radius);
- }
-
- /**
- * Operates the alpha on a given transaction and leash
- * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
- */
- PipSurfaceTransactionHelper alpha(SurfaceControl.Transaction tx, SurfaceControl leash,
- float alpha) {
- tx.setAlpha(leash, alpha);
- return this;
- }
-
- /**
- * Operates the crop (and position) on a given transaction and leash
- * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
- */
- PipSurfaceTransactionHelper crop(SurfaceControl.Transaction tx, SurfaceControl leash,
- Rect destinationBounds) {
- tx.setWindowCrop(leash, destinationBounds.width(), destinationBounds.height())
- .setPosition(leash, destinationBounds.left, destinationBounds.top);
- return this;
- }
-
- /**
- * Operates the scale (setMatrix) on a given transaction and leash
- * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
- */
- PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash,
- Rect sourceBounds, Rect destinationBounds) {
- mTmpSourceRectF.set(sourceBounds);
- mTmpDestinationRectF.set(destinationBounds);
- mTmpTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL);
- tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
- .setPosition(leash, mTmpDestinationRectF.left, mTmpDestinationRectF.top);
- return this;
- }
-
- /**
- * Operates the scale (setMatrix) on a given transaction and leash
- * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
- */
- PipSurfaceTransactionHelper scaleAndCrop(SurfaceControl.Transaction tx, SurfaceControl leash,
- Rect sourceBounds, Rect destinationBounds, Rect insets) {
- mTmpSourceRectF.set(sourceBounds);
- mTmpDestinationRect.set(sourceBounds);
- mTmpDestinationRect.inset(insets);
- // Scale by the shortest edge and offset such that the top/left of the scaled inset source
- // rect aligns with the top/left of the destination bounds
- final float scale = sourceBounds.width() <= sourceBounds.height()
- ? (float) destinationBounds.width() / sourceBounds.width()
- : (float) destinationBounds.height() / sourceBounds.height();
- final float left = destinationBounds.left - insets.left * scale;
- final float top = destinationBounds.top - insets.top * scale;
- mTmpTransform.setScale(scale, scale);
- tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
- .setWindowCrop(leash, mTmpDestinationRect)
- .setPosition(leash, left, top);
- return this;
- }
-
- /**
- * Resets the scale (setMatrix) on a given transaction and leash if there's any
- * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
- */
- PipSurfaceTransactionHelper resetScale(SurfaceControl.Transaction tx, SurfaceControl leash,
- Rect destinationBounds) {
- tx.setMatrix(leash, Matrix.IDENTITY_MATRIX, mTmpFloat9)
- .setPosition(leash, destinationBounds.left, destinationBounds.top);
- return this;
- }
-
- /**
- * Operates the round corner radius on a given transaction and leash
- * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
- */
- PipSurfaceTransactionHelper round(SurfaceControl.Transaction tx, SurfaceControl leash,
- boolean applyCornerRadius) {
- if (mEnableCornerRadius) {
- tx.setCornerRadius(leash, applyCornerRadius ? mCornerRadius : 0);
- }
- return this;
- }
-
- interface SurfaceControlTransactionFactory {
- SurfaceControl.Transaction getTransaction();
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
deleted file mode 100644
index b6334c562a02..000000000000
--- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
+++ /dev/null
@@ -1,1091 +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.pip;
-
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-
-import static com.android.systemui.pip.PipAnimationController.ANIM_TYPE_ALPHA;
-import static com.android.systemui.pip.PipAnimationController.ANIM_TYPE_BOUNDS;
-import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP;
-import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN;
-import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_NONE;
-import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_REMOVE_STACK;
-import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_SAME;
-import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
-import static com.android.systemui.pip.PipAnimationController.isInPipDirection;
-import static com.android.systemui.pip.PipAnimationController.isOutPipDirection;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.ActivityTaskManager;
-import android.app.PictureInPictureParams;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.ActivityInfo;
-import android.content.res.Configuration;
-import android.graphics.Rect;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.util.Log;
-import android.util.Rational;
-import android.util.Size;
-import android.view.SurfaceControl;
-import android.view.SurfaceControlViewHost;
-import android.view.View;
-import android.view.WindowManager;
-import android.window.TaskOrganizer;
-import android.window.WindowContainerToken;
-import android.window.WindowContainerTransaction;
-import android.window.WindowContainerTransactionCallback;
-
-import com.android.internal.os.SomeArgs;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.pip.phone.PipMenuActivityController;
-import com.android.systemui.pip.phone.PipUpdateThread;
-import com.android.wm.shell.R;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.splitscreen.SplitScreen;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.function.Consumer;
-
-/**
- * Manages PiP tasks such as resize and offset.
- *
- * This class listens on {@link TaskOrganizer} callbacks for windowing mode change
- * both to and from PiP and issues corresponding animation if applicable.
- * Normally, we apply series of {@link SurfaceControl.Transaction} when the animator is running
- * and files a final {@link WindowContainerTransaction} at the end of the transition.
- *
- * This class is also responsible for general resize/offset PiP operations within SysUI component,
- * see also {@link com.android.systemui.pip.phone.PipMotionHelper}.
- */
-@SysUISingleton
-public class PipTaskOrganizer extends TaskOrganizer implements ShellTaskOrganizer.TaskListener,
- DisplayController.OnDisplaysChangedListener {
- private static final String TAG = PipTaskOrganizer.class.getSimpleName();
- private static final boolean DEBUG = false;
-
- private static final int MSG_RESIZE_IMMEDIATE = 1;
- private static final int MSG_RESIZE_ANIMATE = 2;
- private static final int MSG_OFFSET_ANIMATE = 3;
- private static final int MSG_FINISH_RESIZE = 4;
- private static final int MSG_RESIZE_USER = 5;
-
- // Not a complete set of states but serves what we want right now.
- private enum State {
- UNDEFINED(0),
- TASK_APPEARED(1),
- ENTERING_PIP(2),
- EXITING_PIP(3);
-
- private final int mStateValue;
-
- State(int value) {
- mStateValue = value;
- }
-
- private boolean isInPip() {
- return mStateValue >= TASK_APPEARED.mStateValue
- && mStateValue != EXITING_PIP.mStateValue;
- }
-
- /**
- * Resize request can be initiated in other component, ignore if we are no longer in PIP,
- * still waiting for animation or we're exiting from it.
- *
- * @return {@code true} if the resize request should be blocked/ignored.
- */
- private boolean shouldBlockResizeRequest() {
- return mStateValue < ENTERING_PIP.mStateValue
- || mStateValue == EXITING_PIP.mStateValue;
- }
- }
-
- private final Context mContext;
- private final Handler mMainHandler;
- private final Handler mUpdateHandler;
- private final PipBoundsHandler mPipBoundsHandler;
- private final PipAnimationController mPipAnimationController;
- private final PipUiEventLogger mPipUiEventLoggerLogger;
- private final List<PipTransitionCallback> mPipTransitionCallbacks = new ArrayList<>();
- private final Rect mLastReportedBounds = new Rect();
- private final int mEnterExitAnimationDuration;
- private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
- private final Map<IBinder, Configuration> mInitialState = new HashMap<>();
- private final Optional<SplitScreen> mSplitScreenOptional;
- protected final ShellTaskOrganizer mTaskOrganizer;
- private SurfaceControlViewHost mPipViewHost;
- private SurfaceControl mPipMenuSurface;
-
- // These callbacks are called on the update thread
- private final PipAnimationController.PipAnimationCallback mPipAnimationCallback =
- new PipAnimationController.PipAnimationCallback() {
- @Override
- public void onPipAnimationStart(PipAnimationController.PipTransitionAnimator animator) {
- sendOnPipTransitionStarted(animator.getTransitionDirection());
- }
-
- @Override
- public void onPipAnimationEnd(SurfaceControl.Transaction tx,
- PipAnimationController.PipTransitionAnimator animator) {
- finishResize(tx, animator.getDestinationBounds(), animator.getTransitionDirection(),
- animator.getAnimationType());
- sendOnPipTransitionFinished(animator.getTransitionDirection());
- }
-
- @Override
- public void onPipAnimationCancel(PipAnimationController.PipTransitionAnimator animator) {
- sendOnPipTransitionCancelled(animator.getTransitionDirection());
- }
- };
-
- @SuppressWarnings("unchecked")
- private final Handler.Callback mUpdateCallbacks = (msg) -> {
- SomeArgs args = (SomeArgs) msg.obj;
- Consumer<Rect> updateBoundsCallback = (Consumer<Rect>) args.arg1;
- switch (msg.what) {
- case MSG_RESIZE_IMMEDIATE: {
- Rect toBounds = (Rect) args.arg2;
- resizePip(toBounds);
- if (updateBoundsCallback != null) {
- updateBoundsCallback.accept(toBounds);
- }
- break;
- }
- case MSG_RESIZE_ANIMATE: {
- Rect currentBounds = (Rect) args.arg2;
- Rect toBounds = (Rect) args.arg3;
- Rect sourceHintRect = (Rect) args.arg4;
- int duration = args.argi2;
- animateResizePip(currentBounds, toBounds, sourceHintRect,
- args.argi1 /* direction */, duration);
- if (updateBoundsCallback != null) {
- updateBoundsCallback.accept(toBounds);
- }
- break;
- }
- case MSG_OFFSET_ANIMATE: {
- Rect originalBounds = (Rect) args.arg2;
- final int offset = args.argi1;
- final int duration = args.argi2;
- offsetPip(originalBounds, 0 /* xOffset */, offset, duration);
- Rect toBounds = new Rect(originalBounds);
- toBounds.offset(0, offset);
- if (updateBoundsCallback != null) {
- updateBoundsCallback.accept(toBounds);
- }
- break;
- }
- case MSG_FINISH_RESIZE: {
- SurfaceControl.Transaction tx = (SurfaceControl.Transaction) args.arg2;
- Rect toBounds = (Rect) args.arg3;
- finishResize(tx, toBounds, args.argi1 /* direction */, -1);
- if (updateBoundsCallback != null) {
- updateBoundsCallback.accept(toBounds);
- }
- break;
- }
- case MSG_RESIZE_USER: {
- Rect startBounds = (Rect) args.arg2;
- Rect toBounds = (Rect) args.arg3;
- userResizePip(startBounds, toBounds);
- if (updateBoundsCallback != null) {
- updateBoundsCallback.accept(toBounds);
- }
- break;
- }
- }
- args.recycle();
- return true;
- };
-
- private ActivityManager.RunningTaskInfo mTaskInfo;
- private WindowContainerToken mToken;
- private SurfaceControl mLeash;
- private State mState = State.UNDEFINED;
- private @PipAnimationController.AnimationType int mOneShotAnimationType = ANIM_TYPE_BOUNDS;
- private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
- mSurfaceControlTransactionFactory;
- private PictureInPictureParams mPictureInPictureParams;
-
- /**
- * If set to {@code true}, the entering animation will be skipped and we will wait for
- * {@link #onFixedRotationFinished(int)} callback to actually enter PiP.
- */
- private boolean mShouldDeferEnteringPip;
-
- public PipTaskOrganizer(Context context, @NonNull PipBoundsHandler boundsHandler,
- @NonNull PipSurfaceTransactionHelper surfaceTransactionHelper,
- Optional<SplitScreen> splitScreenOptional,
- @NonNull DisplayController displayController,
- @NonNull PipUiEventLogger pipUiEventLogger,
- @NonNull ShellTaskOrganizer shellTaskOrganizer) {
- mContext = context;
- mMainHandler = new Handler(Looper.getMainLooper());
- mUpdateHandler = new Handler(PipUpdateThread.get().getLooper(), mUpdateCallbacks);
- mPipBoundsHandler = boundsHandler;
- mEnterExitAnimationDuration = context.getResources()
- .getInteger(R.integer.config_pipResizeAnimationDuration);
- mSurfaceTransactionHelper = surfaceTransactionHelper;
- mPipAnimationController = new PipAnimationController(mSurfaceTransactionHelper);
- mPipUiEventLoggerLogger = pipUiEventLogger;
- mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
- mSplitScreenOptional = splitScreenOptional;
- mTaskOrganizer = shellTaskOrganizer;
- mTaskOrganizer.addListener(this, WINDOWING_MODE_PINNED);
- displayController.addDisplayWindowListener(this);
- }
-
- public Handler getUpdateHandler() {
- return mUpdateHandler;
- }
-
- public Rect getLastReportedBounds() {
- return new Rect(mLastReportedBounds);
- }
-
- public Rect getCurrentOrAnimatingBounds() {
- PipAnimationController.PipTransitionAnimator animator =
- mPipAnimationController.getCurrentAnimator();
- if (animator != null && animator.isRunning()) {
- return new Rect(animator.getDestinationBounds());
- }
- return getLastReportedBounds();
- }
-
- public boolean isInPip() {
- return mState.isInPip();
- }
-
- public boolean isDeferringEnterPipAnimation() {
- return mState.isInPip() && mShouldDeferEnteringPip;
- }
-
- /**
- * Registers {@link PipTransitionCallback} to receive transition callbacks.
- */
- public void registerPipTransitionCallback(PipTransitionCallback callback) {
- mPipTransitionCallbacks.add(callback);
- }
-
- /**
- * Sets the preferred animation type for one time.
- * This is typically used to set the animation type to
- * {@link PipAnimationController#ANIM_TYPE_ALPHA}.
- */
- public void setOneShotAnimationType(@PipAnimationController.AnimationType int animationType) {
- mOneShotAnimationType = animationType;
- }
-
- /**
- * Expands PiP to the previous bounds, this is done in two phases using
- * {@link WindowContainerTransaction}
- * - setActivityWindowingMode to either fullscreen or split-secondary 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 undefined at the end of transition
- * @param animationDurationMs duration in millisecond for the exiting PiP transition
- */
- public void exitPip(int animationDurationMs) {
- if (!mState.isInPip() || mState == State.EXITING_PIP || mToken == null) {
- Log.wtf(TAG, "Not allowed to exitPip in current state"
- + " mState=" + mState + " mToken=" + mToken);
- return;
- }
-
- mPipUiEventLoggerLogger.log(
- PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_EXPAND_TO_FULLSCREEN);
- final Configuration initialConfig = mInitialState.remove(mToken.asBinder());
- final boolean orientationDiffers = initialConfig.windowConfiguration.getRotation()
- != mPipBoundsHandler.getDisplayRotation();
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- final Rect destinationBounds = initialConfig.windowConfiguration.getBounds();
- final int direction = syncWithSplitScreenBounds(destinationBounds)
- ? TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN
- : TRANSITION_DIRECTION_LEAVE_PIP;
- if (orientationDiffers) {
- mState = State.EXITING_PIP;
- // Send started callback though animation is ignored.
- sendOnPipTransitionStarted(direction);
- // Don't bother doing an animation if the display rotation differs or if it's in
- // a non-supported windowing mode
- applyWindowingModeChangeOnExit(wct, direction);
- mTaskOrganizer.applyTransaction(wct);
- // Send finished callback though animation is ignored.
- sendOnPipTransitionFinished(direction);
- } else {
- final SurfaceControl.Transaction tx =
- mSurfaceControlTransactionFactory.getTransaction();
- mSurfaceTransactionHelper.scale(tx, mLeash, destinationBounds,
- mLastReportedBounds);
- tx.setWindowCrop(mLeash, destinationBounds.width(), destinationBounds.height());
- // We set to fullscreen here for now, but later it will be set to UNDEFINED for
- // the proper windowing mode to take place. See #applyWindowingModeChangeOnExit.
- wct.setActivityWindowingMode(mToken,
- direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN
- ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
- : WINDOWING_MODE_FULLSCREEN);
- wct.setBounds(mToken, destinationBounds);
- wct.setBoundsChangeTransaction(mToken, tx);
- mTaskOrganizer.applySyncTransaction(wct, new WindowContainerTransactionCallback() {
- @Override
- public void onTransactionReady(int id, SurfaceControl.Transaction t) {
- t.apply();
- scheduleAnimateResizePip(mLastReportedBounds, destinationBounds,
- getValidSourceHintRect(mTaskInfo, destinationBounds), direction,
- animationDurationMs, null /* updateBoundsCallback */);
- mState = State.EXITING_PIP;
- }
- });
- }
- }
-
- private void applyWindowingModeChangeOnExit(WindowContainerTransaction wct, int direction) {
- // Reset the final windowing mode.
- wct.setWindowingMode(mToken, getOutPipWindowingMode());
- // Simply reset the activity mode set prior to the animation running.
- wct.setActivityWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
- mSplitScreenOptional.ifPresent(splitScreen -> {
- if (direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN) {
- wct.reparent(mToken, splitScreen.getSecondaryRoot(), true /* onTop */);
- }
- });
- }
-
- /**
- * Removes PiP immediately.
- */
- public void removePip() {
- if (!mState.isInPip() || mToken == null) {
- Log.wtf(TAG, "Not allowed to removePip in current state"
- + " mState=" + mState + " mToken=" + mToken);
- return;
- }
-
- // removePipImmediately is expected when the following animation finishes.
- mUpdateHandler.post(() -> mPipAnimationController
- .getAnimator(mLeash, mLastReportedBounds, 1f, 0f)
- .setTransitionDirection(TRANSITION_DIRECTION_REMOVE_STACK)
- .setPipAnimationCallback(mPipAnimationCallback)
- .setDuration(mEnterExitAnimationDuration)
- .start());
- mInitialState.remove(mToken.asBinder());
- mState = State.EXITING_PIP;
- }
-
- private void removePipImmediately() {
- try {
- // Reset the task bounds first to ensure the activity configuration is reset as well
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.setBounds(mToken, null);
- mTaskOrganizer.applyTransaction(wct);
-
- ActivityTaskManager.getService().removeStacksInWindowingModes(
- new int[]{ WINDOWING_MODE_PINNED });
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to remove PiP", e);
- }
- }
-
- @Override
- public void onTaskAppeared(ActivityManager.RunningTaskInfo info, SurfaceControl leash) {
- Objects.requireNonNull(info, "Requires RunningTaskInfo");
- mTaskInfo = info;
- mToken = mTaskInfo.token;
- mState = State.TASK_APPEARED;
- mLeash = leash;
- mInitialState.put(mToken.asBinder(), new Configuration(mTaskInfo.configuration));
- mPictureInPictureParams = mTaskInfo.pictureInPictureParams;
-
- mPipUiEventLoggerLogger.setTaskInfo(mTaskInfo);
- mPipUiEventLoggerLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_ENTER);
-
- if (mShouldDeferEnteringPip) {
- if (DEBUG) Log.d(TAG, "Defer entering PiP animation, fixed rotation is ongoing");
- // if deferred, hide the surface till fixed rotation is completed
- final SurfaceControl.Transaction tx =
- mSurfaceControlTransactionFactory.getTransaction();
- tx.setAlpha(mLeash, 0f);
- tx.show(mLeash);
- tx.apply();
- return;
- }
-
- final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
- mTaskInfo.topActivity, getAspectRatioOrDefault(mPictureInPictureParams),
- null /* bounds */, getMinimalSize(mTaskInfo.topActivityInfo));
- Objects.requireNonNull(destinationBounds, "Missing destination bounds");
- final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds();
-
- if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
- final Rect sourceHintRect = getValidSourceHintRect(info, currentBounds);
- scheduleAnimateResizePip(currentBounds, destinationBounds, sourceHintRect,
- TRANSITION_DIRECTION_TO_PIP, mEnterExitAnimationDuration,
- null /* updateBoundsCallback */);
- mState = State.ENTERING_PIP;
- } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
- enterPipWithAlphaAnimation(destinationBounds, mEnterExitAnimationDuration);
- mOneShotAnimationType = ANIM_TYPE_BOUNDS;
- } else {
- throw new RuntimeException("Unrecognized animation type: " + mOneShotAnimationType);
- }
- }
-
- /**
- * Returns the source hint rect if it is valid (if provided and is contained by the current
- * task bounds).
- */
- private Rect getValidSourceHintRect(ActivityManager.RunningTaskInfo info, Rect sourceBounds) {
- final Rect sourceHintRect = info.pictureInPictureParams != null
- && info.pictureInPictureParams.hasSourceBoundsHint()
- ? info.pictureInPictureParams.getSourceRectHint()
- : null;
- if (sourceHintRect != null && sourceBounds.contains(sourceHintRect)) {
- return sourceHintRect;
- }
- return null;
- }
-
- private void enterPipWithAlphaAnimation(Rect destinationBounds, long durationMs) {
- // If we are fading the PIP in, then we should move the pip to the final location as
- // soon as possible, but set the alpha immediately since the transaction can take a
- // while to process
- final SurfaceControl.Transaction tx =
- mSurfaceControlTransactionFactory.getTransaction();
- tx.setAlpha(mLeash, 0f);
- tx.apply();
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.setActivityWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
- wct.setBounds(mToken, destinationBounds);
- wct.scheduleFinishEnterPip(mToken, destinationBounds);
- mTaskOrganizer.applySyncTransaction(wct, new WindowContainerTransactionCallback() {
- @Override
- public void onTransactionReady(int id, SurfaceControl.Transaction t) {
- t.apply();
- mUpdateHandler.post(() -> mPipAnimationController
- .getAnimator(mLeash, destinationBounds, 0f, 1f)
- .setTransitionDirection(TRANSITION_DIRECTION_TO_PIP)
- .setPipAnimationCallback(mPipAnimationCallback)
- .setDuration(durationMs)
- .start());
- // mState is set right after the animation is kicked off to block any resize
- // requests such as offsetPip that may have been called prior to the transition.
- mState = State.ENTERING_PIP;
- }
- });
- }
-
- private void sendOnPipTransitionStarted(
- @PipAnimationController.TransitionDirection int direction) {
- final Rect pipBounds = new Rect(mLastReportedBounds);
- runOnMainHandler(() -> {
- for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
- final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
- callback.onPipTransitionStarted(mTaskInfo.baseActivity, direction, pipBounds);
- }
- });
- }
-
- private void sendOnPipTransitionFinished(
- @PipAnimationController.TransitionDirection int direction) {
- runOnMainHandler(() -> {
- for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
- final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
- callback.onPipTransitionFinished(mTaskInfo.baseActivity, direction);
- }
- });
- }
-
- private void sendOnPipTransitionCancelled(
- @PipAnimationController.TransitionDirection int direction) {
- runOnMainHandler(() -> {
- for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
- final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
- callback.onPipTransitionCanceled(mTaskInfo.baseActivity, direction);
- }
- });
- }
-
- private void runOnMainHandler(Runnable r) {
- if (Looper.getMainLooper() == Looper.myLooper()) {
- r.run();
- } else {
- mMainHandler.post(r);
- }
- }
-
- /**
- * Setup the ViewHost and attach the provided menu view to the ViewHost.
- */
- public void attachPipMenuViewHost(View menuView, WindowManager.LayoutParams lp) {
- if (mPipMenuSurface != null) {
- Log.e(TAG, "PIP Menu View already created and attached.");
- return;
- }
-
- if (mLeash == null) {
- Log.e(TAG, "PiP Leash is not yet ready.");
- return;
- }
-
- if (Looper.getMainLooper() != Looper.myLooper()) {
- throw new RuntimeException("PipMenuView needs to be attached on the main thread.");
- }
-
- mPipViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(),
- (android.os.Binder) null);
- mPipMenuSurface = mPipViewHost.getSurfacePackage().getSurfaceControl();
- SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
- transaction.reparent(mPipMenuSurface, mLeash);
- transaction.show(mPipMenuSurface);
- transaction.setRelativeLayer(mPipMenuSurface, mLeash, 1);
- transaction.apply();
- mPipViewHost.setView(menuView, lp);
- }
-
-
- /**
- * Releases the PIP Menu's View host, remove it from PIP task surface.
- */
- public void detachPipMenuViewHost() {
- if (mPipMenuSurface != null) {
- SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
- transaction.remove(mPipMenuSurface);
- transaction.apply();
- mPipMenuSurface = null;
- mPipViewHost = null;
- }
- }
-
- /**
- * Return whether the PiP Menu has been attached to the leash yet.
- */
- public boolean isPipMenuViewHostAttached() {
- return mPipViewHost != null;
- }
-
-
- /**
- * Note that dismissing PiP is now originated from SystemUI, see {@link #exitPip(int)}.
- * 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) {
- if (!mState.isInPip()) {
- return;
- }
- final WindowContainerToken token = info.token;
- Objects.requireNonNull(token, "Requires valid WindowContainerToken");
- if (token.asBinder() != mToken.asBinder()) {
- Log.wtf(TAG, "Unrecognized token: " + token);
- return;
- }
- mShouldDeferEnteringPip = false;
- mPictureInPictureParams = null;
- mState = State.UNDEFINED;
- mPipUiEventLoggerLogger.setTaskInfo(null);
- }
-
- @Override
- public void onTaskInfoChanged(ActivityManager.RunningTaskInfo info) {
- Objects.requireNonNull(mToken, "onTaskInfoChanged requires valid existing mToken");
- final PictureInPictureParams newParams = info.pictureInPictureParams;
- if (newParams == null || !applyPictureInPictureParams(newParams)) {
- Log.d(TAG, "Ignored onTaskInfoChanged with PiP param: " + newParams);
- return;
- }
- final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
- info.topActivity, getAspectRatioOrDefault(newParams),
- mLastReportedBounds, getMinimalSize(info.topActivityInfo),
- true /* userCurrentMinEdgeSize */);
- Objects.requireNonNull(destinationBounds, "Missing destination bounds");
- scheduleAnimateResizePip(destinationBounds, mEnterExitAnimationDuration,
- null /* updateBoundsCallback */);
- }
-
- @Override
- public void onFixedRotationStarted(int displayId, int newRotation) {
- mShouldDeferEnteringPip = true;
- }
-
- @Override
- public void onFixedRotationFinished(int displayId) {
- if (mShouldDeferEnteringPip && mState.isInPip()) {
- final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
- mTaskInfo.topActivity, getAspectRatioOrDefault(mPictureInPictureParams),
- null /* bounds */, getMinimalSize(mTaskInfo.topActivityInfo));
- // schedule a regular animation to ensure all the callbacks are still being sent
- enterPipWithAlphaAnimation(destinationBounds, 0 /* durationMs */);
- }
- mShouldDeferEnteringPip = false;
- }
-
- /**
- * TODO(b/152809058): consolidate the display info handling logic in SysUI
- *
- * @param destinationBoundsOut the current destination bounds will be populated to this param
- */
- @SuppressWarnings("unchecked")
- public void onMovementBoundsChanged(Rect destinationBoundsOut, boolean fromRotation,
- boolean fromImeAdjustment, boolean fromShelfAdjustment,
- WindowContainerTransaction wct) {
- final PipAnimationController.PipTransitionAnimator animator =
- mPipAnimationController.getCurrentAnimator();
- if (animator == null || !animator.isRunning()
- || animator.getTransitionDirection() != TRANSITION_DIRECTION_TO_PIP) {
- if (mState.isInPip() && fromRotation) {
- // If we are rotating while there is a current animation, immediately cancel the
- // animation (remove the listeners so we don't trigger the normal finish resize
- // call that should only happen on the update thread)
- int direction = TRANSITION_DIRECTION_NONE;
- if (animator != null) {
- direction = animator.getTransitionDirection();
- animator.removeAllUpdateListeners();
- animator.removeAllListeners();
- animator.cancel();
- // Do notify the listeners that this was canceled
- sendOnPipTransitionCancelled(direction);
- sendOnPipTransitionFinished(direction);
- }
- mLastReportedBounds.set(destinationBoundsOut);
-
- // Create a reset surface transaction for the new bounds and update the window
- // container transaction
- final SurfaceControl.Transaction tx = createFinishResizeSurfaceTransaction(
- destinationBoundsOut);
- prepareFinishResizeTransaction(destinationBoundsOut, direction, tx, wct);
- } else {
- // There could be an animation on-going. If there is one on-going, last-reported
- // bounds isn't yet updated. We'll use the animator's bounds instead.
- if (animator != null && animator.isRunning()) {
- if (!animator.getDestinationBounds().isEmpty()) {
- destinationBoundsOut.set(animator.getDestinationBounds());
- }
- } else {
- if (!mLastReportedBounds.isEmpty()) {
- destinationBoundsOut.set(mLastReportedBounds);
- }
- }
- }
- return;
- }
-
- final Rect currentDestinationBounds = animator.getDestinationBounds();
- destinationBoundsOut.set(currentDestinationBounds);
- if (!fromImeAdjustment && !fromShelfAdjustment
- && mPipBoundsHandler.getDisplayBounds().contains(currentDestinationBounds)) {
- // no need to update the destination bounds, bail early
- return;
- }
-
- final Rect newDestinationBounds = mPipBoundsHandler.getDestinationBounds(
- mTaskInfo.topActivity, getAspectRatioOrDefault(mPictureInPictureParams),
- null /* bounds */, getMinimalSize(mTaskInfo.topActivityInfo));
- if (newDestinationBounds.equals(currentDestinationBounds)) return;
- if (animator.getAnimationType() == ANIM_TYPE_BOUNDS) {
- animator.updateEndValue(newDestinationBounds);
- }
- animator.setDestinationBounds(newDestinationBounds);
- destinationBoundsOut.set(newDestinationBounds);
- }
-
- /**
- * @return {@code true} if the aspect ratio is changed since no other parameters within
- * {@link PictureInPictureParams} would affect the bounds.
- */
- private boolean applyPictureInPictureParams(@NonNull PictureInPictureParams params) {
- final Rational currentAspectRatio =
- mPictureInPictureParams != null ? mPictureInPictureParams.getAspectRatioRational()
- : null;
- final boolean aspectRatioChanged = !Objects.equals(currentAspectRatio,
- params.getAspectRatioRational());
- mPictureInPictureParams = params;
- if (aspectRatioChanged) {
- mPipBoundsHandler.onAspectRatioChanged(params.getAspectRatio());
- }
- return aspectRatioChanged;
- }
-
- /**
- * Animates resizing of the pinned stack given the duration.
- */
- public void scheduleAnimateResizePip(Rect toBounds, int duration,
- Consumer<Rect> updateBoundsCallback) {
- if (mShouldDeferEnteringPip) {
- Log.d(TAG, "skip scheduleAnimateResizePip, entering pip deferred");
- return;
- }
- scheduleAnimateResizePip(mLastReportedBounds, toBounds, null /* sourceHintRect */,
- TRANSITION_DIRECTION_NONE, duration, updateBoundsCallback);
- }
-
- private void scheduleAnimateResizePip(Rect currentBounds, Rect destinationBounds,
- Rect sourceHintRect, @PipAnimationController.TransitionDirection int direction,
- int durationMs, Consumer<Rect> updateBoundsCallback) {
- if (!mState.isInPip()) {
- // TODO: tend to use shouldBlockResizeRequest here as well but need to consider
- // the fact that when in exitPip, scheduleAnimateResizePip is executed in the window
- // container transaction callback and we want to set the mState immediately.
- return;
- }
-
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = updateBoundsCallback;
- args.arg2 = currentBounds;
- args.arg3 = destinationBounds;
- args.arg4 = sourceHintRect;
- args.argi1 = direction;
- args.argi2 = durationMs;
- mUpdateHandler.sendMessage(mUpdateHandler.obtainMessage(MSG_RESIZE_ANIMATE, args));
- }
-
- /**
- * Directly perform manipulation/resize on the leash. This will not perform any
- * {@link WindowContainerTransaction} until {@link #scheduleFinishResizePip} is called.
- */
- public void scheduleResizePip(Rect toBounds, Consumer<Rect> updateBoundsCallback) {
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = updateBoundsCallback;
- args.arg2 = toBounds;
- mUpdateHandler.sendMessage(mUpdateHandler.obtainMessage(MSG_RESIZE_IMMEDIATE, args));
- }
-
- /**
- * Directly perform a scaled matrix transformation on the leash. This will not perform any
- * {@link WindowContainerTransaction} until {@link #scheduleFinishResizePip} is called.
- */
- public void scheduleUserResizePip(Rect startBounds, Rect toBounds,
- Consumer<Rect> updateBoundsCallback) {
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = updateBoundsCallback;
- args.arg2 = startBounds;
- args.arg3 = toBounds;
- mUpdateHandler.sendMessage(mUpdateHandler.obtainMessage(MSG_RESIZE_USER, args));
- }
-
- /**
- * Finish an intermediate resize operation. This is expected to be called after
- * {@link #scheduleResizePip}.
- */
- public void scheduleFinishResizePip(Rect destinationBounds) {
- scheduleFinishResizePip(destinationBounds, null /* updateBoundsCallback */);
- }
-
- /**
- * Same as {@link #scheduleFinishResizePip} but with a callback.
- */
- public void scheduleFinishResizePip(Rect destinationBounds,
- Consumer<Rect> updateBoundsCallback) {
- scheduleFinishResizePip(destinationBounds, TRANSITION_DIRECTION_NONE, updateBoundsCallback);
- }
-
- private void scheduleFinishResizePip(Rect destinationBounds,
- @PipAnimationController.TransitionDirection int direction,
- Consumer<Rect> updateBoundsCallback) {
- if (mState.shouldBlockResizeRequest()) {
- return;
- }
-
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = updateBoundsCallback;
- args.arg2 = createFinishResizeSurfaceTransaction(
- destinationBounds);
- args.arg3 = destinationBounds;
- args.argi1 = direction;
- mUpdateHandler.sendMessage(mUpdateHandler.obtainMessage(MSG_FINISH_RESIZE, args));
- }
-
- private SurfaceControl.Transaction createFinishResizeSurfaceTransaction(
- Rect destinationBounds) {
- final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
- mSurfaceTransactionHelper
- .crop(tx, mLeash, destinationBounds)
- .resetScale(tx, mLeash, destinationBounds)
- .round(tx, mLeash, mState.isInPip());
- return tx;
- }
-
- /**
- * Offset the PiP window by a given offset on Y-axis, triggered also from screen rotation.
- */
- public void scheduleOffsetPip(Rect originalBounds, int offset, int duration,
- Consumer<Rect> updateBoundsCallback) {
- if (mState.shouldBlockResizeRequest()) {
- return;
- }
- if (mShouldDeferEnteringPip) {
- Log.d(TAG, "skip scheduleOffsetPip, entering pip deferred");
- return;
- }
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = updateBoundsCallback;
- args.arg2 = originalBounds;
- // offset would be zero if triggered from screen rotation.
- args.argi1 = offset;
- args.argi2 = duration;
- mUpdateHandler.sendMessage(mUpdateHandler.obtainMessage(MSG_OFFSET_ANIMATE, args));
- }
-
- private void offsetPip(Rect originalBounds, int xOffset, int yOffset, int durationMs) {
- if (Looper.myLooper() != mUpdateHandler.getLooper()) {
- throw new RuntimeException("Callers should call scheduleOffsetPip() instead of this "
- + "directly");
- }
- if (mTaskInfo == null) {
- Log.w(TAG, "mTaskInfo is not set");
- return;
- }
- final Rect destinationBounds = new Rect(originalBounds);
- destinationBounds.offset(xOffset, yOffset);
- animateResizePip(originalBounds, destinationBounds, null /* sourceHintRect */,
- TRANSITION_DIRECTION_SAME, durationMs);
- }
-
- private void resizePip(Rect destinationBounds) {
- if (Looper.myLooper() != mUpdateHandler.getLooper()) {
- throw new RuntimeException("Callers should call scheduleResizePip() instead of this "
- + "directly");
- }
- // Could happen when exitPip
- if (mToken == null || mLeash == null) {
- Log.w(TAG, "Abort animation, invalid leash");
- return;
- }
- mLastReportedBounds.set(destinationBounds);
- final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
- mSurfaceTransactionHelper
- .crop(tx, mLeash, destinationBounds)
- .round(tx, mLeash, mState.isInPip());
- tx.apply();
- }
-
- private void userResizePip(Rect startBounds, Rect destinationBounds) {
- if (Looper.myLooper() != mUpdateHandler.getLooper()) {
- throw new RuntimeException("Callers should call scheduleUserResizePip() instead of "
- + "this directly");
- }
- // Could happen when exitPip
- if (mToken == null || mLeash == null) {
- Log.w(TAG, "Abort animation, invalid leash");
- return;
- }
-
- if (startBounds.isEmpty() || destinationBounds.isEmpty()) {
- Log.w(TAG, "Attempted to user resize PIP to or from empty bounds, aborting.");
- return;
- }
-
- final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
- mSurfaceTransactionHelper.scale(tx, mLeash, startBounds, destinationBounds);
- tx.apply();
- }
-
- private void finishResize(SurfaceControl.Transaction tx, Rect destinationBounds,
- @PipAnimationController.TransitionDirection int direction,
- @PipAnimationController.AnimationType int type) {
- if (Looper.myLooper() != mUpdateHandler.getLooper()) {
- throw new RuntimeException("Callers should call scheduleResizePip() instead of this "
- + "directly");
- }
- mLastReportedBounds.set(destinationBounds);
- if (direction == TRANSITION_DIRECTION_REMOVE_STACK) {
- removePipImmediately();
- return;
- } else if (isInPipDirection(direction) && type == ANIM_TYPE_ALPHA) {
- return;
- }
-
- WindowContainerTransaction wct = new WindowContainerTransaction();
- prepareFinishResizeTransaction(destinationBounds, direction, tx, wct);
- applyFinishBoundsResize(wct, direction);
- runOnMainHandler(() -> {
- if (mPipViewHost != null) {
- mPipViewHost.relayout(PipMenuActivityController.getPipMenuLayoutParams(
- destinationBounds.width(), destinationBounds.height()));
- }
- });
- }
-
- private void prepareFinishResizeTransaction(Rect destinationBounds,
- @PipAnimationController.TransitionDirection int direction,
- SurfaceControl.Transaction tx,
- WindowContainerTransaction wct) {
- final Rect taskBounds;
- if (isInPipDirection(direction)) {
- // If we are animating from fullscreen using a bounds animation, then reset the
- // activity windowing mode set by WM, and set the task bounds to the final bounds
- taskBounds = destinationBounds;
- wct.setActivityWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
- wct.scheduleFinishEnterPip(mToken, destinationBounds);
- } else 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.
- taskBounds = (direction == TRANSITION_DIRECTION_LEAVE_PIP)
- ? null : destinationBounds;
- applyWindowingModeChangeOnExit(wct, direction);
- } else {
- // Just a resize in PIP
- taskBounds = destinationBounds;
- }
-
- wct.setBounds(mToken, taskBounds);
- wct.setBoundsChangeTransaction(mToken, tx);
- }
-
- /**
- * Applies the window container transaction to finish a bounds resize.
- *
- * Called by {@link #finishResize(SurfaceControl.Transaction, Rect, int, int)}} once it has
- * finished preparing the transaction. It allows subclasses to modify the transaction before
- * applying it.
- */
- public void applyFinishBoundsResize(@NonNull WindowContainerTransaction wct,
- @PipAnimationController.TransitionDirection int direction) {
- mTaskOrganizer.applyTransaction(wct);
- }
-
- /**
- * The windowing mode to restore to when resizing out of PIP direction. Defaults to undefined
- * and can be overridden to restore to an alternate windowing mode.
- */
- public int getOutPipWindowingMode() {
- // By default, simply reset the windowing mode to undefined.
- return WINDOWING_MODE_UNDEFINED;
- }
-
-
- private void animateResizePip(Rect currentBounds, Rect destinationBounds, Rect sourceHintRect,
- @PipAnimationController.TransitionDirection int direction, int durationMs) {
- if (Looper.myLooper() != mUpdateHandler.getLooper()) {
- throw new RuntimeException("Callers should call scheduleAnimateResizePip() instead of "
- + "this directly");
- }
- // Could happen when exitPip
- if (mToken == null || mLeash == null) {
- Log.w(TAG, "Abort animation, invalid leash");
- return;
- }
- mPipAnimationController
- .getAnimator(mLeash, currentBounds, destinationBounds, sourceHintRect, direction)
- .setTransitionDirection(direction)
- .setPipAnimationCallback(mPipAnimationCallback)
- .setDuration(durationMs)
- .start();
- }
-
- private Size getMinimalSize(ActivityInfo activityInfo) {
- if (activityInfo == null || activityInfo.windowLayout == null) {
- return null;
- }
- final ActivityInfo.WindowLayout windowLayout = activityInfo.windowLayout;
- // -1 will be populated if an activity specifies defaultWidth/defaultHeight in <layout>
- // without minWidth/minHeight
- if (windowLayout.minWidth > 0 && windowLayout.minHeight > 0) {
- return new Size(windowLayout.minWidth, windowLayout.minHeight);
- }
- return null;
- }
-
- private float getAspectRatioOrDefault(@Nullable PictureInPictureParams params) {
- return params == null || !params.hasSetAspectRatio()
- ? mPipBoundsHandler.getDefaultAspectRatio()
- : params.getAspectRatio();
- }
-
- /**
- * Sync with {@link SplitScreen} 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 (!mSplitScreenOptional.isPresent()) {
- return false;
- }
-
- SplitScreen splitScreen = mSplitScreenOptional.get();
- if (!splitScreen.isDividerVisible()) {
- // fail 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(splitScreen.getDividerView()
- .getNonMinimizedSplitScreenSecondaryBounds());
- return true;
- }
-
- /**
- * Dumps internal states.
- */
- public void dump(PrintWriter pw, String prefix) {
- final String innerPrefix = prefix + " ";
- pw.println(prefix + TAG);
- pw.println(innerPrefix + "mTaskInfo=" + mTaskInfo);
- pw.println(innerPrefix + "mToken=" + mToken
- + " binder=" + (mToken != null ? mToken.asBinder() : null));
- pw.println(innerPrefix + "mLeash=" + mLeash);
- pw.println(innerPrefix + "mState=" + mState);
- pw.println(innerPrefix + "mOneShotAnimationType=" + mOneShotAnimationType);
- pw.println(innerPrefix + "mPictureInPictureParams=" + mPictureInPictureParams);
- pw.println(innerPrefix + "mLastReportedBounds=" + mLastReportedBounds);
- pw.println(innerPrefix + "mInitialState:");
- for (Map.Entry<IBinder, Configuration> e : mInitialState.entrySet()) {
- pw.println(innerPrefix + " binder=" + e.getKey()
- + " winConfig=" + e.getValue().windowConfiguration);
- }
- }
-
- /**
- * Callback interface for PiP transitions (both from and to PiP mode)
- */
- public interface PipTransitionCallback {
- /**
- * Callback when the pip transition is started.
- */
- void onPipTransitionStarted(ComponentName activity, int direction, Rect pipBounds);
-
- /**
- * Callback when the pip transition is finished.
- */
- void onPipTransitionFinished(ComponentName activity, int direction);
-
- /**
- * Callback when the pip transition is cancelled.
- */
- void onPipTransitionCanceled(ComponentName activity, int direction);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipUiEventLogger.java b/packages/SystemUI/src/com/android/systemui/pip/PipUiEventLogger.java
deleted file mode 100644
index 22adbb77d70a..000000000000
--- a/packages/SystemUI/src/com/android/systemui/pip/PipUiEventLogger.java
+++ /dev/null
@@ -1,116 +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.pip;
-
-import android.app.TaskInfo;
-import android.content.pm.PackageManager;
-
-import com.android.internal.logging.UiEvent;
-import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.dagger.SysUISingleton;
-
-/**
- * Helper class that ends PiP log to UiEvent, see also go/uievent
- */
-@SysUISingleton
-public class PipUiEventLogger {
-
- private static final int INVALID_PACKAGE_UID = -1;
-
- private final UiEventLogger mUiEventLogger;
- private final PackageManager mPackageManager;
-
- private String mPackageName;
- private int mPackageUid = INVALID_PACKAGE_UID;
-
- public PipUiEventLogger(UiEventLogger uiEventLogger, PackageManager packageManager) {
- mUiEventLogger = uiEventLogger;
- mPackageManager = packageManager;
- }
-
- public void setTaskInfo(TaskInfo taskInfo) {
- if (taskInfo == null) {
- mPackageName = null;
- mPackageUid = INVALID_PACKAGE_UID;
- } else {
- mPackageName = taskInfo.topActivity.getPackageName();
- mPackageUid = getUid(mPackageName, taskInfo.userId);
- }
- }
-
- /**
- * Sends log via UiEvent, reference go/uievent for how to debug locally
- */
- public void log(PipUiEventEnum event) {
- if (mPackageName == null || mPackageUid == INVALID_PACKAGE_UID) {
- return;
- }
- mUiEventLogger.log(event, mPackageUid, mPackageName);
- }
-
- private int getUid(String packageName, int userId) {
- int uid = INVALID_PACKAGE_UID;
- try {
- uid = mPackageManager.getApplicationInfoAsUser(
- packageName, 0 /* ApplicationInfoFlags */, userId).uid;
- } catch (PackageManager.NameNotFoundException e) {
- // do nothing.
- }
- return uid;
- }
-
- /**
- * Enums for logging the PiP events to UiEvent
- */
- public enum PipUiEventEnum implements UiEventLogger.UiEventEnum {
- @UiEvent(doc = "Activity enters picture-in-picture mode")
- PICTURE_IN_PICTURE_ENTER(603),
-
- @UiEvent(doc = "Expands from picture-in-picture to fullscreen")
- PICTURE_IN_PICTURE_EXPAND_TO_FULLSCREEN(604),
-
- @UiEvent(doc = "Removes picture-in-picture by tap close button")
- PICTURE_IN_PICTURE_TAP_TO_REMOVE(605),
-
- @UiEvent(doc = "Removes picture-in-picture by drag to dismiss area")
- PICTURE_IN_PICTURE_DRAG_TO_REMOVE(606),
-
- @UiEvent(doc = "Shows picture-in-picture menu")
- PICTURE_IN_PICTURE_SHOW_MENU(607),
-
- @UiEvent(doc = "Hides picture-in-picture menu")
- PICTURE_IN_PICTURE_HIDE_MENU(608),
-
- @UiEvent(doc = "Changes the aspect ratio of picture-in-picture window. This is inherited"
- + " from previous Tron-based logging and currently not in use.")
- PICTURE_IN_PICTURE_CHANGE_ASPECT_RATIO(609),
-
- @UiEvent(doc = "User resize of the picture-in-picture window")
- PICTURE_IN_PICTURE_RESIZE(610);
-
- private final int mId;
-
- PipUiEventEnum(int id) {
- mId = id;
- }
-
- @Override
- public int getId() {
- return mId;
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipAccessibilityInteractionConnection.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipAccessibilityInteractionConnection.java
deleted file mode 100644
index a13318990f40..000000000000
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipAccessibilityInteractionConnection.java
+++ /dev/null
@@ -1,269 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.pip.phone;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.view.MagnificationSpec;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityWindowInfo;
-import android.view.accessibility.IAccessibilityInteractionConnection;
-import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
-
-import com.android.systemui.pip.PipSnapAlgorithm;
-import com.android.systemui.pip.PipTaskOrganizer;
-import com.android.wm.shell.R;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Expose the touch actions to accessibility as if this object were a window with a single view.
- * That pseudo-view exposes all of the actions this object can perform.
- */
-public class PipAccessibilityInteractionConnection
- extends IAccessibilityInteractionConnection.Stub {
-
- public interface AccessibilityCallbacks {
- void onAccessibilityShowMenu();
- }
-
- private static final long ACCESSIBILITY_NODE_ID = 1;
- private List<AccessibilityNodeInfo> mAccessibilityNodeInfoList;
-
- private Context mContext;
- private Handler mHandler;
- private PipMotionHelper mMotionHelper;
- private PipTaskOrganizer mTaskOrganizer;
- private PipSnapAlgorithm mSnapAlgorithm;
- private Runnable mUpdateMovementBoundCallback;
- private AccessibilityCallbacks mCallbacks;
-
- private final Rect mNormalBounds = new Rect();
- private final Rect mExpandedBounds = new Rect();
- private final Rect mNormalMovementBounds = new Rect();
- private final Rect mExpandedMovementBounds = new Rect();
- private Rect mTmpBounds = new Rect();
-
- public PipAccessibilityInteractionConnection(Context context, PipMotionHelper motionHelper,
- PipTaskOrganizer taskOrganizer, PipSnapAlgorithm snapAlgorithm,
- AccessibilityCallbacks callbacks, Runnable updateMovementBoundCallback,
- Handler handler) {
- mContext = context;
- mHandler = handler;
- mMotionHelper = motionHelper;
- mTaskOrganizer = taskOrganizer;
- mSnapAlgorithm = snapAlgorithm;
- mUpdateMovementBoundCallback = updateMovementBoundCallback;
- mCallbacks = callbacks;
- }
-
- @Override
- public void findAccessibilityNodeInfoByAccessibilityId(long accessibilityNodeId,
- Region interactiveRegion, int interactionId,
- IAccessibilityInteractionConnectionCallback callback, int flags,
- int interrogatingPid, long interrogatingTid, MagnificationSpec spec, Bundle args) {
- try {
- callback.setFindAccessibilityNodeInfosResult(
- (accessibilityNodeId == AccessibilityNodeInfo.ROOT_NODE_ID)
- ? getNodeList() : null, interactionId);
- } catch (RemoteException re) {
- /* best effort - ignore */
- }
- }
-
- @Override
- public void performAccessibilityAction(long accessibilityNodeId, int action,
- Bundle arguments, int interactionId,
- IAccessibilityInteractionConnectionCallback callback, int flags,
- int interrogatingPid, long interrogatingTid) {
- // We only support one view. A request for anything else is invalid
- boolean result = false;
- if (accessibilityNodeId == AccessibilityNodeInfo.ROOT_NODE_ID) {
-
- // R constants are not final so this cannot be put in the switch-case.
- if (action == R.id.action_pip_resize) {
- if (mMotionHelper.getBounds().width() == mNormalBounds.width()
- && mMotionHelper.getBounds().height() == mNormalBounds.height()) {
- setToExpandedBounds();
- } else {
- setToNormalBounds();
- }
- result = true;
- } else {
- switch (action) {
- case AccessibilityNodeInfo.ACTION_CLICK:
- mHandler.post(() -> {
- mCallbacks.onAccessibilityShowMenu();
- });
- result = true;
- break;
- case AccessibilityNodeInfo.ACTION_DISMISS:
- mMotionHelper.dismissPip();
- result = true;
- break;
- case com.android.internal.R.id.accessibilityActionMoveWindow:
- int newX = arguments.getInt(
- AccessibilityNodeInfo.ACTION_ARGUMENT_MOVE_WINDOW_X);
- int newY = arguments.getInt(
- AccessibilityNodeInfo.ACTION_ARGUMENT_MOVE_WINDOW_Y);
- Rect pipBounds = new Rect();
- pipBounds.set(mMotionHelper.getBounds());
- mTmpBounds.offsetTo(newX, newY);
- mMotionHelper.movePip(mTmpBounds);
- result = true;
- break;
- case AccessibilityNodeInfo.ACTION_EXPAND:
- mMotionHelper.expandLeavePip();
- result = true;
- break;
- default:
- // Leave result as false
- }
- }
- }
- try {
- callback.setPerformAccessibilityActionResult(result, interactionId);
- } catch (RemoteException re) {
- /* best effort - ignore */
- }
- }
-
- private void setToExpandedBounds() {
- float savedSnapFraction = mSnapAlgorithm.getSnapFraction(
- new Rect(mTaskOrganizer.getLastReportedBounds()), mNormalMovementBounds);
- mSnapAlgorithm.applySnapFraction(mExpandedBounds, mExpandedMovementBounds,
- savedSnapFraction);
- mTaskOrganizer.scheduleFinishResizePip(mExpandedBounds, (Rect bounds) -> {
- mMotionHelper.synchronizePinnedStackBounds();
- mUpdateMovementBoundCallback.run();
- });
- }
-
- private void setToNormalBounds() {
- float savedSnapFraction = mSnapAlgorithm.getSnapFraction(
- new Rect(mTaskOrganizer.getLastReportedBounds()), mExpandedMovementBounds);
- mSnapAlgorithm.applySnapFraction(mNormalBounds, mNormalMovementBounds, savedSnapFraction);
- mTaskOrganizer.scheduleFinishResizePip(mNormalBounds, (Rect bounds) -> {
- mMotionHelper.synchronizePinnedStackBounds();
- mUpdateMovementBoundCallback.run();
- });
- }
-
- @Override
- public void findAccessibilityNodeInfosByViewId(long accessibilityNodeId,
- String viewId, Region interactiveRegion, int interactionId,
- IAccessibilityInteractionConnectionCallback callback, int flags,
- int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
- // We have no view with a proper ID
- try {
- callback.setFindAccessibilityNodeInfoResult(null, interactionId);
- } catch (RemoteException re) {
- /* best effort - ignore */
- }
- }
-
- @Override
- public void findAccessibilityNodeInfosByText(long accessibilityNodeId, String text,
- Region interactiveRegion, int interactionId,
- IAccessibilityInteractionConnectionCallback callback, int flags,
- int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
- // We have no view with text
- try {
- callback.setFindAccessibilityNodeInfoResult(null, interactionId);
- } catch (RemoteException re) {
- /* best effort - ignore */
- }
- }
-
- @Override
- public void findFocus(long accessibilityNodeId, int focusType, Region interactiveRegion,
- int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
- int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
- // We have no view that can take focus
- try {
- callback.setFindAccessibilityNodeInfoResult(null, interactionId);
- } catch (RemoteException re) {
- /* best effort - ignore */
- }
- }
-
- @Override
- public void focusSearch(long accessibilityNodeId, int direction, Region interactiveRegion,
- int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
- int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
- // We have no view that can take focus
- try {
- callback.setFindAccessibilityNodeInfoResult(null, interactionId);
- } catch (RemoteException re) {
- /* best effort - ignore */
- }
- }
-
- @Override
- public void clearAccessibilityFocus() {
- // We should not be here.
- }
-
- @Override
- public void notifyOutsideTouch() {
- // Do nothing.
- }
-
- /**
- * Update the normal and expanded bounds so they can be used for Resize.
- */
- void onMovementBoundsChanged(Rect normalBounds, Rect expandedBounds, Rect normalMovementBounds,
- Rect expandedMovementBounds) {
- mNormalBounds.set(normalBounds);
- mExpandedBounds.set(expandedBounds);
- mNormalMovementBounds.set(normalMovementBounds);
- mExpandedMovementBounds.set(expandedMovementBounds);
- }
-
- /**
- * Update the Root node with PIP Accessibility action items.
- */
- public static AccessibilityNodeInfo obtainRootAccessibilityNodeInfo(Context context) {
- AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
- info.setSourceNodeId(AccessibilityNodeInfo.ROOT_NODE_ID,
- AccessibilityWindowInfo.PICTURE_IN_PICTURE_ACTION_REPLACER_WINDOW_ID);
- info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK);
- info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_DISMISS);
- info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_MOVE_WINDOW);
- info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND);
- info.addAction(new AccessibilityNodeInfo.AccessibilityAction(R.id.action_pip_resize,
- context.getString(R.string.accessibility_action_pip_resize)));
- info.setImportantForAccessibility(true);
- info.setClickable(true);
- info.setVisibleToUser(true);
- return info;
- }
-
- private List<AccessibilityNodeInfo> getNodeList() {
- if (mAccessibilityNodeInfoList == null) {
- mAccessibilityNodeInfoList = new ArrayList<>(1);
- }
- AccessibilityNodeInfo info = obtainRootAccessibilityNodeInfo(mContext);
- mAccessibilityNodeInfoList.clear();
- mAccessibilityNodeInfoList.add(info);
- return mAccessibilityNodeInfoList;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipAppOpsListener.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipAppOpsListener.java
deleted file mode 100644
index 7dfd99c2110d..000000000000
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipAppOpsListener.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.pip.phone;
-
-import static android.app.AppOpsManager.MODE_ALLOWED;
-import static android.app.AppOpsManager.OP_PICTURE_IN_PICTURE;
-
-import android.app.AppOpsManager;
-import android.app.AppOpsManager.OnOpChangedListener;
-import android.app.IActivityManager;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.os.Handler;
-import android.util.Pair;
-
-public class PipAppOpsListener {
- private static final String TAG = PipAppOpsListener.class.getSimpleName();
-
- private Context mContext;
- private Handler mHandler;
- private IActivityManager mActivityManager;
- private AppOpsManager mAppOpsManager;
- private Callback mCallback;
-
- private AppOpsManager.OnOpChangedListener mAppOpsChangedListener = new OnOpChangedListener() {
- @Override
- public void onOpChanged(String op, String packageName) {
- try {
- // Dismiss the PiP once the user disables the app ops setting for that package
- final Pair<ComponentName, Integer> topPipActivityInfo =
- PipUtils.getTopPipActivity(mContext, mActivityManager);
- if (topPipActivityInfo.first != null) {
- final ApplicationInfo appInfo = mContext.getPackageManager()
- .getApplicationInfoAsUser(packageName, 0, topPipActivityInfo.second);
- if (appInfo.packageName.equals(topPipActivityInfo.first.getPackageName()) &&
- mAppOpsManager.checkOpNoThrow(OP_PICTURE_IN_PICTURE, appInfo.uid,
- packageName) != MODE_ALLOWED) {
- mHandler.post(() -> mCallback.dismissPip());
- }
- }
- } catch (NameNotFoundException e) {
- // Unregister the listener if the package can't be found
- unregisterAppOpsListener();
- }
- }
- };
-
- public PipAppOpsListener(Context context, IActivityManager activityManager,
- Callback callback) {
- mContext = context;
- mHandler = new Handler(mContext.getMainLooper());
- mActivityManager = activityManager;
- mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
- mCallback = callback;
- }
-
- public void onActivityPinned(String packageName) {
- // Register for changes to the app ops setting for this package while it is in PiP
- registerAppOpsListener(packageName);
- }
-
- public void onActivityUnpinned() {
- // Unregister for changes to the previously PiP'ed package
- unregisterAppOpsListener();
- }
-
- private void registerAppOpsListener(String packageName) {
- mAppOpsManager.startWatchingMode(OP_PICTURE_IN_PICTURE, packageName,
- mAppOpsChangedListener);
- }
-
- private void unregisterAppOpsListener() {
- mAppOpsManager.stopWatchingMode(mAppOpsChangedListener);
- }
-
- /** Callback for PipAppOpsListener to request changes to the PIP window. */
- public interface Callback {
- /** Dismisses the PIP window. */
- void dismissPip();
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipController.java
deleted file mode 100644
index 5bb179484e5d..000000000000
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipController.java
+++ /dev/null
@@ -1,490 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.pip.phone;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
-
-import static com.android.systemui.pip.PipAnimationController.isOutPipDirection;
-
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.ActivityTaskManager;
-import android.app.ActivityTaskManager.RootTaskInfo;
-import android.app.IActivityManager;
-import android.app.RemoteAction;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.pm.ParceledListSlice;
-import android.content.res.Configuration;
-import android.graphics.Rect;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.util.Log;
-import android.util.Pair;
-import android.view.DisplayInfo;
-import android.view.IPinnedStackController;
-import android.window.WindowContainerTransaction;
-
-import com.android.systemui.Dependency;
-import com.android.systemui.UiOffloadThread;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.model.SysUiState;
-import com.android.systemui.pip.Pip;
-import com.android.systemui.pip.PipBoundsHandler;
-import com.android.systemui.pip.PipSurfaceTransactionHelper;
-import com.android.systemui.pip.PipTaskOrganizer;
-import com.android.systemui.pip.PipUiEventLogger;
-import com.android.systemui.shared.recents.IPinnedStackAnimationListener;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-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.statusbar.policy.ConfigurationController;
-import com.android.systemui.util.DeviceConfigProxy;
-import com.android.systemui.util.FloatingContentCoordinator;
-import com.android.wm.shell.common.DisplayChangeController;
-import com.android.wm.shell.common.DisplayController;
-
-import java.io.PrintWriter;
-
-/**
- * Manages the picture-in-picture (PIP) UI and states for Phones.
- */
-@SysUISingleton
-public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallback {
- private static final String TAG = "PipController";
-
- private Context mContext;
- private IActivityManager mActivityManager;
- private Handler mHandler = new Handler();
-
- private final DisplayInfo mTmpDisplayInfo = new DisplayInfo();
- private final Rect mTmpInsetBounds = new Rect();
- private final Rect mTmpNormalBounds = new Rect();
- protected final Rect mReentryBounds = new Rect();
-
- private DisplayController mDisplayController;
- private InputConsumerController mInputConsumerController;
- private PipAppOpsListener mAppOpsListener;
- private PipBoundsHandler mPipBoundsHandler;
- private PipMediaController mMediaController;
- private PipTouchHandler mTouchHandler;
- private PipSurfaceTransactionHelper mPipSurfaceTransactionHelper;
- private IPinnedStackAnimationListener mPinnedStackAnimationRecentsListener;
- private boolean mIsInFixedRotation;
-
- protected PipMenuActivityController mMenuController;
- protected PipTaskOrganizer mPipTaskOrganizer;
-
- /**
- * Handler for display rotation changes.
- */
- private final DisplayChangeController.OnDisplayChangingListener mRotationController = (
- int displayId, int fromRotation, int toRotation, WindowContainerTransaction t) -> {
- if (!mPipTaskOrganizer.isInPip() || mPipTaskOrganizer.isDeferringEnterPipAnimation()) {
- // Skip if we aren't in PIP or haven't actually entered PIP yet. We still need to update
- // the display layout in the bounds handler in this case.
- mPipBoundsHandler.onDisplayRotationChangedNotInPip(mContext, toRotation);
- return;
- }
- // If there is an animation running (ie. from a shelf offset), then ensure that we calculate
- // the bounds for the next orientation using the destination bounds of the animation
- // TODO: Techincally this should account for movement animation bounds as well
- Rect currentBounds = mPipTaskOrganizer.getCurrentOrAnimatingBounds();
- final boolean changed = mPipBoundsHandler.onDisplayRotationChanged(mContext,
- mTmpNormalBounds, currentBounds, mTmpInsetBounds, displayId, fromRotation,
- toRotation, t);
- if (changed) {
- // If the pip was in the offset zone earlier, adjust the new bounds to the bottom of the
- // movement bounds
- mTouchHandler.adjustBoundsForRotation(mTmpNormalBounds,
- mPipTaskOrganizer.getLastReportedBounds(), mTmpInsetBounds);
-
- // The bounds are being applied to a specific snap fraction, so reset any known offsets
- // for the previous orientation before updating the movement bounds.
- // We perform the resets if and only if this callback is due to screen rotation but
- // not during the fixed rotation. In fixed rotation case, app is about to enter PiP
- // and we need the offsets preserved to calculate the destination bounds.
- if (!mIsInFixedRotation) {
- mPipBoundsHandler.setShelfHeight(false, 0);
- mPipBoundsHandler.onImeVisibilityChanged(false, 0);
- mTouchHandler.onShelfVisibilityChanged(false, 0);
- mTouchHandler.onImeVisibilityChanged(false, 0);
- }
-
- updateMovementBounds(mTmpNormalBounds, true /* fromRotation */,
- false /* fromImeAdjustment */, false /* fromShelfAdjustment */, t);
- }
- };
-
- private DisplayController.OnDisplaysChangedListener mFixedRotationListener =
- new DisplayController.OnDisplaysChangedListener() {
- @Override
- public void onFixedRotationStarted(int displayId, int newRotation) {
- mIsInFixedRotation = true;
- }
-
- @Override
- public void onFixedRotationFinished(int displayId) {
- mIsInFixedRotation = false;
- }
-
- @Override
- public void onDisplayAdded(int displayId) {
- mPipBoundsHandler.setDisplayLayout(
- mDisplayController.getDisplayLayout(displayId));
- }
- };
-
- /**
- * Handler for system task stack changes.
- */
- private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
- @Override
- public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
- mTouchHandler.onActivityPinned();
- mMediaController.onActivityPinned();
- mMenuController.onActivityPinned();
- mAppOpsListener.onActivityPinned(packageName);
-
- Dependency.get(UiOffloadThread.class).execute(() -> {
- WindowManagerWrapper.getInstance().setPipVisibility(true);
- });
- }
-
- @Override
- public void onActivityUnpinned() {
- final Pair<ComponentName, Integer> topPipActivityInfo = PipUtils.getTopPipActivity(
- mContext, mActivityManager);
- final ComponentName topActivity = topPipActivityInfo.first;
- mMenuController.onActivityUnpinned();
- mTouchHandler.onActivityUnpinned(topActivity);
- mAppOpsListener.onActivityUnpinned();
-
- Dependency.get(UiOffloadThread.class).execute(() -> {
- WindowManagerWrapper.getInstance().setPipVisibility(topActivity != null);
- });
- }
-
- @Override
- public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
- boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
- if (task.configuration.windowConfiguration.getWindowingMode()
- != WINDOWING_MODE_PINNED) {
- return;
- }
- mTouchHandler.getMotionHelper().expandLeavePip(clearedTask /* skipAnimation */);
- }
- };
-
- /**
- * Handler for messages from the PIP controller.
- */
- private class PipControllerPinnedStackListener extends PinnedStackListener {
- @Override
- public void onListenerRegistered(IPinnedStackController controller) {
- mHandler.post(() -> mTouchHandler.setPinnedStackController(controller));
- }
-
- @Override
- public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
- mHandler.post(() -> {
- mPipBoundsHandler.onImeVisibilityChanged(imeVisible, imeHeight);
- mTouchHandler.onImeVisibilityChanged(imeVisible, imeHeight);
- });
- }
-
- @Override
- public void onMovementBoundsChanged(boolean fromImeAdjustment) {
- mHandler.post(() -> updateMovementBounds(null /* toBounds */,
- false /* fromRotation */, fromImeAdjustment, false /* fromShelfAdjustment */,
- null /* windowContainerTransaction */));
- }
-
- @Override
- public void onActionsChanged(ParceledListSlice<RemoteAction> actions) {
- mHandler.post(() -> mMenuController.setAppActions(actions));
- }
-
- @Override
- public void onActivityHidden(ComponentName componentName) {
- mHandler.post(() -> mPipBoundsHandler.onResetReentryBounds(componentName));
- }
-
- @Override
- public void onDisplayInfoChanged(DisplayInfo displayInfo) {
- mHandler.post(() -> mPipBoundsHandler.onDisplayInfoChanged(displayInfo));
- }
-
- @Override
- public void onConfigurationChanged() {
- mHandler.post(() -> mPipBoundsHandler.onConfigurationChanged(mContext));
- }
-
- @Override
- public void onAspectRatioChanged(float aspectRatio) {
- mHandler.post(() -> {
- mPipBoundsHandler.onAspectRatioChanged(aspectRatio);
- mTouchHandler.onAspectRatioChanged();
- });
- }
- }
-
- public ConfigurationController.ConfigurationListener mOverlayChangedListener =
- new ConfigurationController.ConfigurationListener() {
- @Override
- public void onOverlayChanged() {
- mHandler.post(() -> {
- mPipBoundsHandler.onOverlayChanged(mContext, mContext.getDisplay());
- updateMovementBounds(null /* toBounds */,
- false /* fromRotation */, false /* fromImeAdjustment */,
- false /* fromShelfAdjustment */,
- null /* windowContainerTransaction */);
- });
- }
- };
-
- public PipController(Context context, BroadcastDispatcher broadcastDispatcher,
- ConfigurationController configController,
- DeviceConfigProxy deviceConfig,
- DisplayController displayController,
- FloatingContentCoordinator floatingContentCoordinator,
- SysUiState sysUiState,
- PipBoundsHandler pipBoundsHandler,
- PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
- PipTaskOrganizer pipTaskOrganizer,
- PipUiEventLogger pipUiEventLogger) {
- mContext = context;
- mActivityManager = ActivityManager.getService();
-
- PackageManager pm = context.getPackageManager();
- boolean supportsPip = pm.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE);
- if (supportsPip) {
- initController(context, broadcastDispatcher, configController, deviceConfig,
- displayController, floatingContentCoordinator, sysUiState, pipBoundsHandler,
- pipSurfaceTransactionHelper, pipTaskOrganizer, pipUiEventLogger);
- } else {
- Log.w(TAG, "Device not support PIP feature");
- }
- }
-
- private void initController(Context context, BroadcastDispatcher broadcastDispatcher,
- ConfigurationController configController,
- DeviceConfigProxy deviceConfig,
- DisplayController displayController,
- FloatingContentCoordinator floatingContentCoordinator,
- SysUiState sysUiState,
- PipBoundsHandler pipBoundsHandler,
- PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
- PipTaskOrganizer pipTaskOrganizer,
- PipUiEventLogger pipUiEventLogger) {
-
- // Ensure that we are the primary user's SystemUI.
- final int processUser = UserManager.get(context).getUserHandle();
- if (processUser != UserHandle.USER_SYSTEM) {
- throw new IllegalStateException("Non-primary Pip component not currently supported.");
- }
-
- try {
- WindowManagerWrapper.getInstance().addPinnedStackListener(
- new PipControllerPinnedStackListener());
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to register pinned stack listener", e);
- }
- ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
-
- mDisplayController = displayController;
- mPipBoundsHandler = pipBoundsHandler;
- mPipSurfaceTransactionHelper = pipSurfaceTransactionHelper;
- mPipTaskOrganizer = pipTaskOrganizer;
- mPipTaskOrganizer.registerPipTransitionCallback(this);
- mInputConsumerController = InputConsumerController.getPipInputConsumer();
- mMediaController = new PipMediaController(context, mActivityManager, broadcastDispatcher);
- mMenuController = new PipMenuActivityController(context,
- mMediaController, mInputConsumerController, mPipTaskOrganizer);
- mTouchHandler = new PipTouchHandler(context, mActivityManager,
- mMenuController, mInputConsumerController, mPipBoundsHandler, mPipTaskOrganizer,
- floatingContentCoordinator, deviceConfig, sysUiState, pipUiEventLogger);
- mAppOpsListener = new PipAppOpsListener(context, mActivityManager,
- mTouchHandler.getMotionHelper());
- displayController.addDisplayChangingController(mRotationController);
- displayController.addDisplayWindowListener(mFixedRotationListener);
-
- // Ensure that we have the display info in case we get calls to update the bounds before the
- // listener calls back
- final DisplayInfo displayInfo = new DisplayInfo();
- context.getDisplay().getDisplayInfo(displayInfo);
- mPipBoundsHandler.onDisplayInfoChanged(displayInfo);
-
- configController.addCallback(mOverlayChangedListener);
-
- try {
- RootTaskInfo taskInfo = ActivityTaskManager.getService().getRootTaskInfo(
- WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
- if (taskInfo != null) {
- // If SystemUI restart, and it already existed a pinned stack,
- // register the pip input consumer to ensure touch can send to it.
- mInputConsumerController.registerInputConsumer(true /* withSfVsync */);
- }
- } catch (RemoteException | UnsupportedOperationException e) {
- e.printStackTrace();
- }
- }
-
- /**
- * Updates the PIP per configuration changed.
- */
- public void onConfigurationChanged(Configuration newConfig) {
- mTouchHandler.onConfigurationChanged();
- }
-
- /**
- * Expands the PIP.
- */
- @Override
- public void expandPip() {
- mTouchHandler.getMotionHelper().expandLeavePip(false /* skipAnimation */);
- }
-
- /**
- * Hides the PIP menu.
- */
- @Override
- public void hidePipMenu(Runnable onStartCallback, Runnable onEndCallback) {
- mMenuController.hideMenu(onStartCallback, onEndCallback);
- }
-
- /**
- * Sent from KEYCODE_WINDOW handler in PhoneWindowManager, to request the menu to be shown.
- */
- public void showPictureInPictureMenu() {
- mTouchHandler.showPictureInPictureMenu();
- }
-
- /**
- * Sets a customized touch gesture that replaces the default one.
- */
- public void setTouchGesture(PipTouchGesture gesture) {
- mTouchHandler.setTouchGesture(gesture);
- }
-
- /**
- * Sets both shelf visibility and its height.
- */
- @Override
- public void setShelfHeight(boolean visible, int height) {
- mHandler.post(() -> {
- final int shelfHeight = visible ? height : 0;
- final boolean changed = mPipBoundsHandler.setShelfHeight(visible, shelfHeight);
- if (changed) {
- mTouchHandler.onShelfVisibilityChanged(visible, shelfHeight);
- updateMovementBounds(mPipTaskOrganizer.getLastReportedBounds(),
- false /* fromRotation */, false /* fromImeAdjustment */,
- true /* fromShelfAdjustment */, null /* windowContainerTransaction */);
- }
- });
- }
-
- @Override
- public void setPinnedStackAnimationType(int animationType) {
- mHandler.post(() -> mPipTaskOrganizer.setOneShotAnimationType(animationType));
- }
-
- @Override
- public void setPinnedStackAnimationListener(IPinnedStackAnimationListener listener) {
- mHandler.post(() -> mPinnedStackAnimationRecentsListener = listener);
- }
-
- @Override
- public void onPipTransitionStarted(ComponentName activity, int direction, Rect pipBounds) {
- if (isOutPipDirection(direction)) {
- // Exiting PIP, save the reentry bounds to restore to when re-entering.
- updateReentryBounds(pipBounds);
- mPipBoundsHandler.onSaveReentryBounds(activity, mReentryBounds);
- }
- // Disable touches while the animation is running
- mTouchHandler.setTouchEnabled(false);
- if (mPinnedStackAnimationRecentsListener != null) {
- try {
- mPinnedStackAnimationRecentsListener.onPinnedStackAnimationStarted();
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to callback recents", e);
- }
- }
- }
-
- /**
- * Update the bounds used to save the re-entry size and snap fraction when exiting PIP.
- */
- public void updateReentryBounds(Rect bounds) {
- final Rect reentryBounds = mTouchHandler.getUserResizeBounds();
- float snapFraction = mPipBoundsHandler.getSnapFraction(bounds);
- mPipBoundsHandler.applySnapFraction(reentryBounds, snapFraction);
- mReentryBounds.set(reentryBounds);
- }
-
- @Override
- public void onPipTransitionFinished(ComponentName activity, int direction) {
- onPipTransitionFinishedOrCanceled(direction);
- }
-
- @Override
- public void onPipTransitionCanceled(ComponentName activity, int direction) {
- onPipTransitionFinishedOrCanceled(direction);
- }
-
- private void onPipTransitionFinishedOrCanceled(int direction) {
- // Re-enable touches after the animation completes
- mTouchHandler.setTouchEnabled(true);
- mTouchHandler.onPinnedStackAnimationEnded(direction);
- mMenuController.onPinnedStackAnimationEnded();
- }
-
- private void updateMovementBounds(@Nullable Rect toBounds, boolean fromRotation,
- boolean fromImeAdjustment, boolean fromShelfAdjustment,
- WindowContainerTransaction wct) {
- // Populate inset / normal bounds and DisplayInfo from mPipBoundsHandler before
- // passing to mTouchHandler/mPipTaskOrganizer
- final Rect outBounds = new Rect(toBounds);
- mPipBoundsHandler.onMovementBoundsChanged(mTmpInsetBounds, mTmpNormalBounds,
- outBounds, mTmpDisplayInfo);
- // mTouchHandler would rely on the bounds populated from mPipTaskOrganizer
- mPipTaskOrganizer.onMovementBoundsChanged(outBounds, fromRotation, fromImeAdjustment,
- fromShelfAdjustment, wct);
- mTouchHandler.onMovementBoundsChanged(mTmpInsetBounds, mTmpNormalBounds,
- outBounds, fromImeAdjustment, fromShelfAdjustment,
- mTmpDisplayInfo.rotation);
- }
-
- @Override
- public void dump(PrintWriter pw) {
- final String innerPrefix = " ";
- pw.println(TAG);
- mInputConsumerController.dump(pw, innerPrefix);
- mMenuController.dump(pw, innerPrefix);
- mTouchHandler.dump(pw, innerPrefix);
- mPipBoundsHandler.dump(pw, innerPrefix);
- mPipTaskOrganizer.dump(pw, innerPrefix);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMediaController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMediaController.java
deleted file mode 100644
index 361aafacdf76..000000000000
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMediaController.java
+++ /dev/null
@@ -1,276 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.pip.phone;
-
-import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
-
-import android.app.IActivityManager;
-import android.app.PendingIntent;
-import android.app.RemoteAction;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
-import android.media.session.MediaController;
-import android.media.session.MediaSession;
-import android.media.session.MediaSessionManager;
-import android.media.session.MediaSessionManager.OnActiveSessionsChangedListener;
-import android.media.session.PlaybackState;
-import android.os.UserHandle;
-
-import com.android.systemui.Dependency;
-import com.android.systemui.R;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.statusbar.policy.UserInfoController;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Interfaces with the {@link MediaSessionManager} to compose the right set of actions to show (only
- * if there are no actions from the PiP activity itself). The active media controller is only set
- * when there is a media session from the top PiP activity.
- */
-public class PipMediaController {
-
- private static final String ACTION_PLAY = "com.android.systemui.pip.phone.PLAY";
- private static final String ACTION_PAUSE = "com.android.systemui.pip.phone.PAUSE";
- private static final String ACTION_NEXT = "com.android.systemui.pip.phone.NEXT";
- private static final String ACTION_PREV = "com.android.systemui.pip.phone.PREV";
-
- /**
- * A listener interface to receive notification on changes to the media actions.
- */
- public interface ActionListener {
- /**
- * Called when the media actions changes.
- */
- void onMediaActionsChanged(List<RemoteAction> actions);
- }
-
- private final Context mContext;
- private final IActivityManager mActivityManager;
-
- private final MediaSessionManager mMediaSessionManager;
- private MediaController mMediaController;
-
- private RemoteAction mPauseAction;
- private RemoteAction mPlayAction;
- private RemoteAction mNextAction;
- private RemoteAction mPrevAction;
-
- private BroadcastReceiver mPlayPauseActionReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- if (action.equals(ACTION_PLAY)) {
- mMediaController.getTransportControls().play();
- } else if (action.equals(ACTION_PAUSE)) {
- mMediaController.getTransportControls().pause();
- } else if (action.equals(ACTION_NEXT)) {
- mMediaController.getTransportControls().skipToNext();
- } else if (action.equals(ACTION_PREV)) {
- mMediaController.getTransportControls().skipToPrevious();
- }
- }
- };
-
- private final MediaController.Callback mPlaybackChangedListener = new MediaController.Callback() {
- @Override
- public void onPlaybackStateChanged(PlaybackState state) {
- notifyActionsChanged();
- }
- };
-
- private final MediaSessionManager.OnActiveSessionsChangedListener mSessionsChangedListener =
- new OnActiveSessionsChangedListener() {
- @Override
- public void onActiveSessionsChanged(List<MediaController> controllers) {
- resolveActiveMediaController(controllers);
- }
- };
-
- private ArrayList<ActionListener> mListeners = new ArrayList<>();
-
- public PipMediaController(Context context, IActivityManager activityManager,
- BroadcastDispatcher broadcastDispatcher) {
- mContext = context;
- mActivityManager = activityManager;
- IntentFilter mediaControlFilter = new IntentFilter();
- mediaControlFilter.addAction(ACTION_PLAY);
- mediaControlFilter.addAction(ACTION_PAUSE);
- mediaControlFilter.addAction(ACTION_NEXT);
- mediaControlFilter.addAction(ACTION_PREV);
- broadcastDispatcher.registerReceiver(mPlayPauseActionReceiver, mediaControlFilter);
-
- createMediaActions();
- mMediaSessionManager =
- (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
-
- // The media session listener needs to be re-registered when switching users
- UserInfoController userInfoController = Dependency.get(UserInfoController.class);
- userInfoController.addCallback((String name, Drawable picture, String userAccount) ->
- registerSessionListenerForCurrentUser());
- }
-
- /**
- * Handles when an activity is pinned.
- */
- public void onActivityPinned() {
- // Once we enter PiP, try to find the active media controller for the top most activity
- resolveActiveMediaController(mMediaSessionManager.getActiveSessionsForUser(null,
- UserHandle.USER_CURRENT));
- }
-
- /**
- * Adds a new media action listener.
- */
- public void addListener(ActionListener listener) {
- if (!mListeners.contains(listener)) {
- mListeners.add(listener);
- listener.onMediaActionsChanged(getMediaActions());
- }
- }
-
- /**
- * Removes a media action listener.
- */
- public void removeListener(ActionListener listener) {
- listener.onMediaActionsChanged(Collections.EMPTY_LIST);
- mListeners.remove(listener);
- }
-
- /**
- * Gets the set of media actions currently available.
- */
- private List<RemoteAction> getMediaActions() {
- if (mMediaController == null || mMediaController.getPlaybackState() == null) {
- return Collections.EMPTY_LIST;
- }
-
- ArrayList<RemoteAction> mediaActions = new ArrayList<>();
- int state = mMediaController.getPlaybackState().getState();
- boolean isPlaying = MediaSession.isActiveState(state);
- long actions = mMediaController.getPlaybackState().getActions();
-
- // Prev action
- mPrevAction.setEnabled((actions & PlaybackState.ACTION_SKIP_TO_PREVIOUS) != 0);
- mediaActions.add(mPrevAction);
-
- // Play/pause action
- if (!isPlaying && ((actions & PlaybackState.ACTION_PLAY) != 0)) {
- mediaActions.add(mPlayAction);
- } else if (isPlaying && ((actions & PlaybackState.ACTION_PAUSE) != 0)) {
- mediaActions.add(mPauseAction);
- }
-
- // Next action
- mNextAction.setEnabled((actions & PlaybackState.ACTION_SKIP_TO_NEXT) != 0);
- mediaActions.add(mNextAction);
- return mediaActions;
- }
-
- /**
- * Creates the standard media buttons that we may show.
- */
- private void createMediaActions() {
- String pauseDescription = mContext.getString(R.string.pip_pause);
- mPauseAction = new RemoteAction(Icon.createWithResource(mContext,
- R.drawable.pip_ic_pause_white), pauseDescription, pauseDescription,
- PendingIntent.getBroadcast(mContext, 0, new Intent(ACTION_PAUSE),
- FLAG_UPDATE_CURRENT));
-
- String playDescription = mContext.getString(R.string.pip_play);
- mPlayAction = new RemoteAction(Icon.createWithResource(mContext,
- R.drawable.pip_ic_play_arrow_white), playDescription, playDescription,
- PendingIntent.getBroadcast(mContext, 0, new Intent(ACTION_PLAY),
- FLAG_UPDATE_CURRENT));
-
- String nextDescription = mContext.getString(R.string.pip_skip_to_next);
- mNextAction = new RemoteAction(Icon.createWithResource(mContext,
- R.drawable.ic_skip_next_white), nextDescription, nextDescription,
- PendingIntent.getBroadcast(mContext, 0, new Intent(ACTION_NEXT),
- FLAG_UPDATE_CURRENT));
-
- String prevDescription = mContext.getString(R.string.pip_skip_to_prev);
- mPrevAction = new RemoteAction(Icon.createWithResource(mContext,
- R.drawable.ic_skip_previous_white), prevDescription, prevDescription,
- PendingIntent.getBroadcast(mContext, 0, new Intent(ACTION_PREV),
- FLAG_UPDATE_CURRENT));
- }
-
- /**
- * Re-registers the session listener for the current user.
- */
- private void registerSessionListenerForCurrentUser() {
- mMediaSessionManager.removeOnActiveSessionsChangedListener(mSessionsChangedListener);
- mMediaSessionManager.addOnActiveSessionsChangedListener(mSessionsChangedListener, null,
- UserHandle.USER_CURRENT, null);
- }
-
- /**
- * Tries to find and set the active media controller for the top PiP activity.
- */
- private void resolveActiveMediaController(List<MediaController> controllers) {
- if (controllers != null) {
- final ComponentName topActivity = PipUtils.getTopPipActivity(mContext,
- mActivityManager).first;
- if (topActivity != null) {
- for (int i = 0; i < controllers.size(); i++) {
- final MediaController controller = controllers.get(i);
- if (controller.getPackageName().equals(topActivity.getPackageName())) {
- setActiveMediaController(controller);
- return;
- }
- }
- }
- }
- setActiveMediaController(null);
- }
-
- /**
- * Sets the active media controller for the top PiP activity.
- */
- private void setActiveMediaController(MediaController controller) {
- if (controller != mMediaController) {
- if (mMediaController != null) {
- mMediaController.unregisterCallback(mPlaybackChangedListener);
- }
- mMediaController = controller;
- if (controller != null) {
- controller.registerCallback(mPlaybackChangedListener);
- }
- notifyActionsChanged();
-
- // TODO(winsonc): Consider if we want to close the PIP after a timeout (like on TV)
- }
- }
-
- /**
- * Notifies all listeners that the actions have changed.
- */
- private void notifyActionsChanged() {
- if (!mListeners.isEmpty()) {
- List<RemoteAction> actions = getMediaActions();
- mListeners.forEach(l -> l.onMediaActionsChanged(actions));
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
deleted file mode 100644
index 5b07db6c91a1..000000000000
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
+++ /dev/null
@@ -1,397 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.pip.phone;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-
-import android.app.ActivityTaskManager;
-import android.app.ActivityTaskManager.RootTaskInfo;
-import android.app.RemoteAction;
-import android.content.Context;
-import android.content.pm.ParceledListSlice;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.os.Debug;
-import android.os.RemoteException;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.WindowManager;
-
-import com.android.systemui.pip.PipTaskOrganizer;
-import com.android.systemui.pip.phone.PipMediaController.ActionListener;
-import com.android.systemui.shared.system.InputConsumerController;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Manages the PiP menu activity which can show menu options or a scrim.
- *
- * The current media session provides actions whenever there are no valid actions provided by the
- * current PiP activity. Otherwise, those actions always take precedence.
- */
-public class PipMenuActivityController {
-
- private static final String TAG = "PipMenuActController";
- private static final boolean DEBUG = false;
-
- public static final int MENU_STATE_NONE = 0;
- public static final int MENU_STATE_CLOSE = 1;
- public static final int MENU_STATE_FULL = 2;
-
- /**
- * A listener interface to receive notification on changes in PIP.
- */
- public interface Listener {
- /**
- * Called when the PIP menu visibility changes.
- *
- * @param menuState the current state of the menu
- * @param resize whether or not to resize the PiP with the state change
- */
- void onPipMenuStateChanged(int menuState, boolean resize, Runnable callback);
-
- /**
- * Called when the PIP requested to be expanded.
- */
- void onPipExpand();
-
- /**
- * Called when the PIP requested to be dismissed.
- */
- void onPipDismiss();
-
- /**
- * Called when the PIP requested to show the menu.
- */
- void onPipShowMenu();
- }
-
- private Context mContext;
- private PipTaskOrganizer mPipTaskOrganizer;
- private PipMediaController mMediaController;
- private InputConsumerController mInputConsumerController;
-
- private ArrayList<Listener> mListeners = new ArrayList<>();
- private ParceledListSlice<RemoteAction> mAppActions;
- private ParceledListSlice<RemoteAction> mMediaActions;
- private int mMenuState;
-
- private PipMenuView mPipMenuView;
-
- private ActionListener mMediaActionListener = new ActionListener() {
- @Override
- public void onMediaActionsChanged(List<RemoteAction> mediaActions) {
- mMediaActions = new ParceledListSlice<>(mediaActions);
- updateMenuActions();
- }
- };
-
- public PipMenuActivityController(Context context,
- PipMediaController mediaController, InputConsumerController inputConsumerController,
- PipTaskOrganizer pipTaskOrganizer
- ) {
- mContext = context;
- mMediaController = mediaController;
- mInputConsumerController = inputConsumerController;
- mPipTaskOrganizer = pipTaskOrganizer;
- }
-
- public boolean isMenuVisible() {
- return mPipMenuView != null && mMenuState != MENU_STATE_NONE;
- }
-
- public void onActivityPinned() {
- attachPipMenuView();
- mInputConsumerController.registerInputConsumer(true /* withSfVsync */);
- }
-
- public void onActivityUnpinned() {
- hideMenu();
- mInputConsumerController.unregisterInputConsumer();
- mPipTaskOrganizer.detachPipMenuViewHost();
- mPipMenuView = null;
- }
-
- public void onPinnedStackAnimationEnded() {
- if (isMenuVisible()) {
- mPipMenuView.onPipAnimationEnded();
- }
- }
-
- private void attachPipMenuView() {
- if (mPipMenuView == null) {
- mPipMenuView = new PipMenuView(mContext, this);
-
- }
- mPipTaskOrganizer.attachPipMenuViewHost(mPipMenuView, getPipMenuLayoutParams(0, 0));
- }
-
- /**
- * Adds a new menu activity listener.
- */
- public void addListener(Listener listener) {
- if (!mListeners.contains(listener)) {
- mListeners.add(listener);
- }
- }
-
- /**
- * Updates the appearance of the menu and scrim on top of the PiP while dismissing.
- */
- public void setDismissFraction(float fraction) {
- final boolean isMenuVisible = isMenuVisible();
- if (DEBUG) {
- Log.d(TAG, "setDismissFraction() isMenuVisible=" + isMenuVisible
- + " fraction=" + fraction);
- }
- if (isMenuVisible) {
- mPipMenuView.updateDismissFraction(fraction);
- }
- }
-
- /**
- * Similar to {@link #showMenu(int, Rect, boolean, boolean, boolean)} but only show the menu
- * upon PiP window transition is finished.
- */
- public void showMenuWithDelay(int menuState, Rect stackBounds, boolean allowMenuTimeout,
- boolean willResizeMenu, boolean showResizeHandle) {
- // hide all visible controls including close button and etc. first, this is to ensure
- // menu is totally invisible during the transition to eliminate unpleasant artifacts
- fadeOutMenu();
- showMenuInternal(menuState, stackBounds, allowMenuTimeout, willResizeMenu,
- true /* withDelay */, showResizeHandle);
- }
-
- /**
- * Shows the menu activity immediately.
- */
- public void showMenu(int menuState, Rect stackBounds, boolean allowMenuTimeout,
- boolean willResizeMenu, boolean showResizeHandle) {
- showMenuInternal(menuState, stackBounds, allowMenuTimeout, willResizeMenu,
- false /* withDelay */, showResizeHandle);
- }
-
- private void showMenuInternal(int menuState, Rect stackBounds, boolean allowMenuTimeout,
- boolean willResizeMenu, boolean withDelay, boolean showResizeHandle) {
- if (DEBUG) {
- Log.d(TAG, "showMenu() state=" + menuState
- + " isMenuVisible=" + isMenuVisible()
- + " allowMenuTimeout=" + allowMenuTimeout
- + " willResizeMenu=" + willResizeMenu
- + " withDelay=" + withDelay
- + " showResizeHandle=" + showResizeHandle
- + " callers=\n" + Debug.getCallers(5, " "));
- }
-
- if (!mPipTaskOrganizer.isPipMenuViewHostAttached()) {
- Log.d(TAG, "PipMenu has not been attached yet. Attaching now at showMenuInternal().");
- attachPipMenuView();
- }
-
- mPipMenuView.showMenu(menuState, stackBounds, allowMenuTimeout, willResizeMenu, withDelay,
- showResizeHandle);
- }
-
- /**
- * Pokes the menu, indicating that the user is interacting with it.
- */
- public void pokeMenu() {
- final boolean isMenuVisible = isMenuVisible();
- if (DEBUG) {
- Log.d(TAG, "pokeMenu() isMenuVisible=" + isMenuVisible);
- }
- if (isMenuVisible) {
- mPipMenuView.pokeMenu();
- }
- }
-
- private void fadeOutMenu() {
- final boolean isMenuVisible = isMenuVisible();
- if (DEBUG) {
- Log.d(TAG, "fadeOutMenu() isMenuVisible=" + isMenuVisible);
- }
- if (isMenuVisible) {
- mPipMenuView.fadeOutMenu();
- }
- }
-
- /**
- * Hides the menu activity.
- */
- public void hideMenu() {
- final boolean isMenuVisible = isMenuVisible();
- if (DEBUG) {
- Log.d(TAG, "hideMenu() state=" + mMenuState
- + " isMenuVisible=" + isMenuVisible
- + " callers=\n" + Debug.getCallers(5, " "));
- }
- if (isMenuVisible) {
- mPipMenuView.hideMenu();
- }
- }
-
- /**
- * Hides the menu activity.
- */
- public void hideMenu(Runnable onStartCallback, Runnable onEndCallback) {
- if (isMenuVisible()) {
- // If the menu is visible in either the closed or full state, then hide the menu and
- // trigger the animation trigger afterwards
- onStartCallback.run();
- mPipMenuView.hideMenu(onEndCallback);
- }
- }
-
- /**
- * Preemptively mark the menu as invisible, used when we are directly manipulating the pinned
- * stack and don't want to trigger a resize which can animate the stack in a conflicting way
- * (ie. when manually expanding or dismissing).
- */
- public void hideMenuWithoutResize() {
- onMenuStateChanged(MENU_STATE_NONE, false /* resize */, null /* callback */);
- }
-
- /**
- * Sets the menu actions to the actions provided by the current PiP activity.
- */
- public void setAppActions(ParceledListSlice<RemoteAction> appActions) {
- mAppActions = appActions;
- updateMenuActions();
- }
-
- void onPipExpand() {
- mListeners.forEach(Listener::onPipExpand);
- }
-
- void onPipDismiss() {
- mListeners.forEach(Listener::onPipDismiss);
- }
-
- void onPipShowMenu() {
- mListeners.forEach(Listener::onPipShowMenu);
- }
-
- /**
- * @return the best set of actions to show in the PiP menu.
- */
- private ParceledListSlice<RemoteAction> resolveMenuActions() {
- if (isValidActions(mAppActions)) {
- return mAppActions;
- }
- return mMediaActions;
- }
-
- /**
- * Returns a default LayoutParams for the PIP Menu.
- * @param width the PIP stack width.
- * @param height the PIP stack height.
- */
- public static WindowManager.LayoutParams getPipMenuLayoutParams(int width, int height) {
- return new WindowManager.LayoutParams(width, height,
- WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.TRANSLUCENT);
- }
-
- /**
- * Updates the PiP menu with the best set of actions provided.
- */
- private void updateMenuActions() {
- if (isMenuVisible()) {
- // Fetch the pinned stack bounds
- Rect stackBounds = null;
- try {
- RootTaskInfo pinnedTaskInfo = ActivityTaskManager.getService().getRootTaskInfo(
- WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
- if (pinnedTaskInfo != null) {
- stackBounds = pinnedTaskInfo.bounds;
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Error showing PIP menu", e);
- }
-
- mPipMenuView.setActions(stackBounds, resolveMenuActions().getList());
- }
- }
-
- /**
- * Returns whether the set of actions are valid.
- */
- private static boolean isValidActions(ParceledListSlice<?> actions) {
- return actions != null && actions.getList().size() > 0;
- }
-
- /**
- * Handles changes in menu visibility.
- */
- void onMenuStateChanged(int menuState, boolean resize, Runnable callback) {
- if (DEBUG) {
- Log.d(TAG, "onMenuStateChanged() mMenuState=" + mMenuState
- + " menuState=" + menuState + " resize=" + resize
- + " callers=\n" + Debug.getCallers(5, " "));
- }
-
- if (menuState != mMenuState) {
- mListeners.forEach(l -> l.onPipMenuStateChanged(menuState, resize, callback));
- if (menuState == MENU_STATE_FULL) {
- // Once visible, start listening for media action changes. This call will trigger
- // the menu actions to be updated again.
- mMediaController.addListener(mMediaActionListener);
- } else {
- // Once hidden, stop listening for media action changes. This call will trigger
- // the menu actions to be updated again.
- mMediaController.removeListener(mMediaActionListener);
- }
- }
- mMenuState = menuState;
- }
-
- /**
- * Handles a pointer event sent from pip input consumer.
- */
- void handlePointerEvent(MotionEvent ev) {
- if (ev.isTouchEvent()) {
- mPipMenuView.dispatchTouchEvent(ev);
- } else {
- mPipMenuView.dispatchGenericMotionEvent(ev);
- }
- }
-
- /**
- * Tell the PIP Menu to recalculate its layout given its current position on the display.
- */
- public void updateMenuLayout(Rect bounds) {
- final boolean isMenuVisible = isMenuVisible();
- if (DEBUG) {
- Log.d(TAG, "updateMenuLayout() state=" + mMenuState
- + " isMenuVisible=" + isMenuVisible
- + " callers=\n" + Debug.getCallers(5, " "));
- }
- if (isMenuVisible) {
- mPipMenuView.updateMenuLayout(bounds);
- }
- }
-
- public void dump(PrintWriter pw, String prefix) {
- final String innerPrefix = prefix + " ";
- pw.println(prefix + TAG);
- pw.println(innerPrefix + "mMenuState=" + mMenuState);
- pw.println(innerPrefix + "mPipMenuView=" + mPipMenuView);
- pw.println(innerPrefix + "mListeners=" + mListeners.size());
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuIconsAlgorithm.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuIconsAlgorithm.java
deleted file mode 100644
index 6cfed070198b..000000000000
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuIconsAlgorithm.java
+++ /dev/null
@@ -1,86 +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.pip.phone;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.util.Log;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-
-/**
- * Helper class to calculate and place the menu icons on the PIP Menu.
- */
-public class PipMenuIconsAlgorithm {
-
- private static final String TAG = "PipMenuIconsAlgorithm";
-
- private boolean mFinishedLayout = false;
- protected ViewGroup mViewRoot;
- protected ViewGroup mTopEndContainer;
- protected View mDragHandle;
- protected View mSettingsButton;
- protected View mDismissButton;
-
- protected PipMenuIconsAlgorithm(Context context) {
- }
-
- /**
- * Bind the necessary views.
- */
- public void bindViews(ViewGroup viewRoot, ViewGroup topEndContainer, View dragHandle,
- View settingsButton, View dismissButton) {
- mViewRoot = viewRoot;
- mTopEndContainer = topEndContainer;
- mDragHandle = dragHandle;
- mSettingsButton = settingsButton;
- mDismissButton = dismissButton;
- }
-
- /**
- * Updates the position of the drag handle based on where the PIP window is on the screen.
- */
- public void onBoundsChanged(Rect bounds) {
- if (mViewRoot == null || mTopEndContainer == null || mDragHandle == null
- || mSettingsButton == null || mDismissButton == null) {
- Log.e(TAG, "One if the required views is null.");
- }
-
- //We only need to calculate the layout once since it does not change.
- if (!mFinishedLayout) {
- mTopEndContainer.removeView(mSettingsButton);
- mViewRoot.addView(mSettingsButton);
-
- setLayoutGravity(mDragHandle, Gravity.START | Gravity.TOP);
- setLayoutGravity(mSettingsButton, Gravity.START | Gravity.TOP);
- mFinishedLayout = true;
- }
- }
-
- /**
- * Set the gravity on the given view.
- */
- protected static void setLayoutGravity(View v, int gravity) {
- if (v.getLayoutParams() instanceof FrameLayout.LayoutParams) {
- FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) v.getLayoutParams();
- params.gravity = gravity;
- v.setLayoutParams(params);
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuView.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuView.java
deleted file mode 100644
index c66f442c4c0d..000000000000
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuView.java
+++ /dev/null
@@ -1,495 +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.pip.phone;
-
-import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
-import static android.provider.Settings.ACTION_PICTURE_IN_PICTURE_SETTINGS;
-import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS;
-import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS;
-import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
-
-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;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
-import android.app.ActivityManager;
-import android.app.PendingIntent.CanceledException;
-import android.app.RemoteAction;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.util.Log;
-import android.util.Pair;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.widget.FrameLayout;
-import android.widget.ImageButton;
-import android.widget.LinearLayout;
-
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Translucent window that gets started on top of a task in PIP to allow the user to control it.
- */
-public class PipMenuView extends FrameLayout {
-
- private static final String TAG = "PipMenuView";
-
- private static final int MESSAGE_INVALID_TYPE = -1;
- public static final int MESSAGE_MENU_EXPANDED = 8;
-
- private static final int INITIAL_DISMISS_DELAY = 3500;
- private static final int POST_INTERACTION_DISMISS_DELAY = 2000;
- private static final long MENU_FADE_DURATION = 125;
- private static final long MENU_SLOW_FADE_DURATION = 175;
- private static final long MENU_SHOW_ON_EXPAND_START_DELAY = 30;
-
- private static final float MENU_BACKGROUND_ALPHA = 0.3f;
- private static final float DISMISS_BACKGROUND_ALPHA = 0.6f;
-
- private static final float DISABLED_ACTION_ALPHA = 0.54f;
-
- private static final boolean ENABLE_RESIZE_HANDLE = false;
-
- private int mMenuState;
- private boolean mResize = true;
- private boolean mAllowMenuTimeout = true;
- private boolean mAllowTouches = true;
-
- private final List<RemoteAction> mActions = new ArrayList<>();
-
- private AccessibilityManager mAccessibilityManager;
- private Drawable mBackgroundDrawable;
- private View mMenuContainer;
- private LinearLayout mActionsGroup;
- private int mBetweenActionPaddingLand;
-
- private AnimatorSet mMenuContainerAnimator;
- private PipMenuActivityController mController;
-
- private ValueAnimator.AnimatorUpdateListener mMenuBgUpdateListener =
- new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- final float alpha = (float) animation.getAnimatedValue();
- mBackgroundDrawable.setAlpha((int) (MENU_BACKGROUND_ALPHA * alpha * 255));
- }
- };
-
- private Handler mHandler = new Handler();
-
- private final Runnable mHideMenuRunnable = this::hideMenu;
-
- protected View mViewRoot;
- protected View mSettingsButton;
- protected View mDismissButton;
- protected View mResizeHandle;
- protected View mTopEndContainer;
- protected PipMenuIconsAlgorithm mPipMenuIconsAlgorithm;
-
- public PipMenuView(Context context, PipMenuActivityController controller) {
- super(context, null, 0);
- mContext = context;
- mController = controller;
-
- mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
- inflate(context, R.layout.pip_menu, this);
-
- mBackgroundDrawable = new ColorDrawable(Color.BLACK);
- mBackgroundDrawable.setAlpha(0);
- mViewRoot = findViewById(R.id.background);
- mViewRoot.setBackground(mBackgroundDrawable);
- mMenuContainer = findViewById(R.id.menu_container);
- mMenuContainer.setAlpha(0);
- mTopEndContainer = findViewById(R.id.top_end_container);
- mSettingsButton = findViewById(R.id.settings);
- mSettingsButton.setAlpha(0);
- mSettingsButton.setOnClickListener((v) -> {
- if (v.getAlpha() != 0) {
- showSettings();
- }
- });
- mDismissButton = findViewById(R.id.dismiss);
- mDismissButton.setAlpha(0);
- mDismissButton.setOnClickListener(v -> dismissPip());
- findViewById(R.id.expand_button).setOnClickListener(v -> {
- if (mMenuContainer.getAlpha() != 0) {
- expandPip();
- }
- });
- // TODO (b/161710689): Remove this once focusability for Windowless window is working
- findViewById(R.id.expand_button).setFocusable(false);
- mDismissButton.setFocusable(false);
- mSettingsButton.setFocusable(false);
-
- mResizeHandle = findViewById(R.id.resize_handle);
- mResizeHandle.setAlpha(0);
- mActionsGroup = findViewById(R.id.actions_group);
- mBetweenActionPaddingLand = getResources().getDimensionPixelSize(
- R.dimen.pip_between_action_padding_land);
- mPipMenuIconsAlgorithm = new PipMenuIconsAlgorithm(mContext);
- mPipMenuIconsAlgorithm.bindViews((ViewGroup) mViewRoot, (ViewGroup) mTopEndContainer,
- mResizeHandle, mSettingsButton, mDismissButton);
-
- initAccessibility();
- }
-
- private void initAccessibility() {
- this.setAccessibilityDelegate(new View.AccessibilityDelegate() {
- @Override
- public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(host, info);
- String label = getResources().getString(R.string.pip_menu_title);
- info.addAction(new AccessibilityNodeInfo.AccessibilityAction(ACTION_CLICK, label));
- }
-
- @Override
- public boolean performAccessibilityAction(View host, int action, Bundle args) {
- if (action == ACTION_CLICK && mMenuState == MENU_STATE_CLOSE) {
- mController.onPipShowMenu();
- }
- return super.performAccessibilityAction(host, action, args);
- }
- });
- }
-
- @Override
- public boolean onKeyUp(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_ESCAPE) {
- hideMenu();
- return true;
- }
- return super.onKeyUp(keyCode, event);
- }
-
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
- if (!mAllowTouches) {
- return false;
- }
-
- if (mAllowMenuTimeout) {
- repostDelayedHide(POST_INTERACTION_DISMISS_DELAY);
- }
-
- return super.dispatchTouchEvent(ev);
- }
-
- @Override
- public boolean dispatchGenericMotionEvent(MotionEvent event) {
- if (mAllowMenuTimeout) {
- repostDelayedHide(POST_INTERACTION_DISMISS_DELAY);
- }
-
- return super.dispatchGenericMotionEvent(event);
- }
-
- void showMenu(int menuState, Rect stackBounds, boolean allowMenuTimeout,
- boolean resizeMenuOnShow, boolean withDelay, boolean showResizeHandle) {
- mAllowMenuTimeout = allowMenuTimeout;
- if (mMenuState != menuState) {
- // Disallow touches if the menu needs to resize while showing, and we are transitioning
- // to/from a full menu state.
- boolean disallowTouchesUntilAnimationEnd = resizeMenuOnShow
- && (mMenuState == MENU_STATE_FULL || menuState == MENU_STATE_FULL);
- mAllowTouches = !disallowTouchesUntilAnimationEnd;
- cancelDelayedHide();
- updateActionViews(stackBounds);
- if (mMenuContainerAnimator != null) {
- mMenuContainerAnimator.cancel();
- }
- mMenuContainerAnimator = new AnimatorSet();
- ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA,
- mMenuContainer.getAlpha(), 1f);
- menuAnim.addUpdateListener(mMenuBgUpdateListener);
- ObjectAnimator settingsAnim = ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA,
- mSettingsButton.getAlpha(), 1f);
- ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA,
- mDismissButton.getAlpha(), 1f);
- ObjectAnimator resizeAnim = ObjectAnimator.ofFloat(mResizeHandle, View.ALPHA,
- mResizeHandle.getAlpha(),
- ENABLE_RESIZE_HANDLE && menuState == MENU_STATE_CLOSE && showResizeHandle
- ? 1f : 0f);
- if (menuState == MENU_STATE_FULL) {
- mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim,
- resizeAnim);
- } else {
- mMenuContainerAnimator.playTogether(dismissAnim, resizeAnim);
- }
- mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN);
- mMenuContainerAnimator.setDuration(menuState == MENU_STATE_CLOSE
- ? MENU_FADE_DURATION
- : MENU_SLOW_FADE_DURATION);
- if (allowMenuTimeout) {
- mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- repostDelayedHide(INITIAL_DISMISS_DELAY);
- }
- });
- }
- if (withDelay) {
- // starts the menu container animation after window expansion is completed
- notifyMenuStateChange(menuState, resizeMenuOnShow, () -> {
- if (mMenuContainerAnimator == null) {
- return;
- }
- mMenuContainerAnimator.setStartDelay(MENU_SHOW_ON_EXPAND_START_DELAY);
- mMenuContainerAnimator.start();
- });
- } else {
- notifyMenuStateChange(menuState, resizeMenuOnShow, null);
- mMenuContainerAnimator.start();
- }
- } else {
- // If we are already visible, then just start the delayed dismiss and unregister any
- // existing input consumers from the previous drag
- if (allowMenuTimeout) {
- repostDelayedHide(POST_INTERACTION_DISMISS_DELAY);
- }
- }
- }
-
- /**
- * Different from {@link #hideMenu()}, this function does not try to finish this menu activity
- * and instead, it fades out the controls by setting the alpha to 0 directly without menu
- * visibility callbacks invoked.
- */
- void fadeOutMenu() {
- mMenuContainer.setAlpha(0f);
- mSettingsButton.setAlpha(0f);
- mDismissButton.setAlpha(0f);
- mResizeHandle.setAlpha(0f);
- }
-
- void pokeMenu() {
- cancelDelayedHide();
- }
-
- void onPipAnimationEnded() {
- mAllowTouches = true;
- }
-
- void updateMenuLayout(Rect bounds) {
- mPipMenuIconsAlgorithm.onBoundsChanged(bounds);
- }
-
- void hideMenu() {
- hideMenu(null);
- }
-
- void hideMenu(Runnable animationEndCallback) {
- hideMenu(animationEndCallback, true /* notifyMenuVisibility */, false);
- }
-
- private void hideMenu(final Runnable animationFinishedRunnable, boolean notifyMenuVisibility,
- boolean animate) {
- if (mMenuState != MENU_STATE_NONE) {
- cancelDelayedHide();
- if (notifyMenuVisibility) {
- notifyMenuStateChange(MENU_STATE_NONE, mResize, null);
- }
- mMenuContainerAnimator = new AnimatorSet();
- ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA,
- mMenuContainer.getAlpha(), 0f);
- menuAnim.addUpdateListener(mMenuBgUpdateListener);
- ObjectAnimator settingsAnim = ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA,
- mSettingsButton.getAlpha(), 0f);
- ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA,
- mDismissButton.getAlpha(), 0f);
- ObjectAnimator resizeAnim = ObjectAnimator.ofFloat(mResizeHandle, View.ALPHA,
- mResizeHandle.getAlpha(), 0f);
- mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim, resizeAnim);
- mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_OUT);
- mMenuContainerAnimator.setDuration(animate ? MENU_FADE_DURATION : 0);
- mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- if (animationFinishedRunnable != null) {
- animationFinishedRunnable.run();
- }
- }
- });
- mMenuContainerAnimator.start();
- }
- }
-
- void setActions(Rect stackBounds, List<RemoteAction> actions) {
- mActions.clear();
- mActions.addAll(actions);
- updateActionViews(stackBounds);
- }
-
- private void updateActionViews(Rect stackBounds) {
- ViewGroup expandContainer = findViewById(R.id.expand_container);
- ViewGroup actionsContainer = findViewById(R.id.actions_container);
- actionsContainer.setOnTouchListener((v, ev) -> {
- // Do nothing, prevent click through to parent
- return true;
- });
-
- if (mActions.isEmpty() || mMenuState == MENU_STATE_CLOSE) {
- actionsContainer.setVisibility(View.INVISIBLE);
- } else {
- actionsContainer.setVisibility(View.VISIBLE);
- if (mActionsGroup != null) {
- // Ensure we have as many buttons as actions
- final LayoutInflater inflater = LayoutInflater.from(mContext);
- while (mActionsGroup.getChildCount() < mActions.size()) {
- final ImageButton actionView = (ImageButton) inflater.inflate(
- R.layout.pip_menu_action, mActionsGroup, false);
- mActionsGroup.addView(actionView);
- }
-
- // Update the visibility of all views
- for (int i = 0; i < mActionsGroup.getChildCount(); i++) {
- mActionsGroup.getChildAt(i).setVisibility(i < mActions.size()
- ? View.VISIBLE
- : View.GONE);
- }
-
- // Recreate the layout
- final boolean isLandscapePip = stackBounds != null
- && (stackBounds.width() > stackBounds.height());
- for (int i = 0; i < mActions.size(); i++) {
- final RemoteAction action = mActions.get(i);
- final ImageButton actionView = (ImageButton) mActionsGroup.getChildAt(i);
-
- // TODO: Check if the action drawable has changed before we reload it
- action.getIcon().loadDrawableAsync(mContext, d -> {
- d.setTint(Color.WHITE);
- actionView.setImageDrawable(d);
- }, mHandler);
- actionView.setContentDescription(action.getContentDescription());
- if (action.isEnabled()) {
- actionView.setOnClickListener(v -> {
- mHandler.post(() -> {
- try {
- action.getActionIntent().send();
- } catch (CanceledException e) {
- Log.w(TAG, "Failed to send action", e);
- }
- });
- });
- }
- actionView.setEnabled(action.isEnabled());
- actionView.setAlpha(action.isEnabled() ? 1f : DISABLED_ACTION_ALPHA);
-
- // Update the margin between actions
- LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
- actionView.getLayoutParams();
- lp.leftMargin = (isLandscapePip && i > 0) ? mBetweenActionPaddingLand : 0;
- }
- }
-
- // Update the expand container margin to adjust the center of the expand button to
- // account for the existence of the action container
- FrameLayout.LayoutParams expandedLp =
- (FrameLayout.LayoutParams) expandContainer.getLayoutParams();
- expandedLp.topMargin = getResources().getDimensionPixelSize(
- R.dimen.pip_action_padding);
- expandedLp.bottomMargin = getResources().getDimensionPixelSize(
- R.dimen.pip_expand_container_edge_margin);
- expandContainer.requestLayout();
- }
- }
-
- void updateDismissFraction(float fraction) {
- int alpha;
- final float menuAlpha = 1 - fraction;
- if (mMenuState == MENU_STATE_FULL) {
- mMenuContainer.setAlpha(menuAlpha);
- mSettingsButton.setAlpha(menuAlpha);
- mDismissButton.setAlpha(menuAlpha);
- final float interpolatedAlpha =
- MENU_BACKGROUND_ALPHA * menuAlpha + DISMISS_BACKGROUND_ALPHA * fraction;
- alpha = (int) (interpolatedAlpha * 255);
- } else {
- if (mMenuState == MENU_STATE_CLOSE) {
- mDismissButton.setAlpha(menuAlpha);
- }
- alpha = (int) (fraction * DISMISS_BACKGROUND_ALPHA * 255);
- }
- mBackgroundDrawable.setAlpha(alpha);
- }
-
- private void notifyMenuStateChange(int menuState, boolean resize, Runnable callback) {
- mMenuState = menuState;
- mController.onMenuStateChanged(menuState, resize, callback);
- }
-
- private void expandPip() {
- // Do not notify menu visibility when hiding the menu, the controller will do this when it
- // handles the message
- hideMenu(mController::onPipExpand, false /* notifyMenuVisibility */, true /* animate */);
- }
-
- private void dismissPip() {
- // Since tapping on the close-button invokes a double-tap wait callback in PipTouchHandler,
- // we want to disable animating the fadeout animation of the buttons in order to call on
- // PipTouchHandler#onPipDismiss fast enough.
- final boolean animate = mMenuState != MENU_STATE_CLOSE;
- // Do not notify menu visibility when hiding the menu, the controller will do this when it
- // handles the message
- hideMenu(mController::onPipDismiss, false /* notifyMenuVisibility */, animate);
- }
-
- private void showSettings() {
- final Pair<ComponentName, Integer> topPipActivityInfo =
- PipUtils.getTopPipActivity(mContext, ActivityManager.getService());
- if (topPipActivityInfo.first != null) {
- final Intent settingsIntent = new Intent(ACTION_PICTURE_IN_PICTURE_SETTINGS,
- Uri.fromParts("package", topPipActivityInfo.first.getPackageName(), null));
- settingsIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
- mContext.startActivityAsUser(settingsIntent, UserHandle.CURRENT);
- }
- }
-
- private void cancelDelayedHide() {
- mHandler.removeCallbacks(mHideMenuRunnable);
- }
-
- private void repostDelayedHide(int delay) {
- int recommendedTimeout = mAccessibilityManager.getRecommendedTimeoutMillis(delay,
- FLAG_CONTENT_ICONS | FLAG_CONTENT_CONTROLS);
- mHandler.removeCallbacks(mHideMenuRunnable);
- mHandler.postDelayed(mHideMenuRunnable, recommendedTimeout);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
deleted file mode 100644
index e24121928808..000000000000
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
+++ /dev/null
@@ -1,665 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.pip.phone;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.ComponentName;
-import android.content.Context;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.os.Debug;
-import android.os.Handler;
-import android.os.Looper;
-import android.util.Log;
-import android.view.Choreographer;
-
-import androidx.dynamicanimation.animation.AnimationHandler;
-import androidx.dynamicanimation.animation.AnimationHandler.FrameCallbackScheduler;
-import androidx.dynamicanimation.animation.SpringForce;
-
-import com.android.systemui.pip.PipSnapAlgorithm;
-import com.android.systemui.pip.PipTaskOrganizer;
-import com.android.systemui.util.FloatingContentCoordinator;
-import com.android.systemui.util.animation.FloatProperties;
-import com.android.systemui.util.animation.PhysicsAnimator;
-import com.android.systemui.util.magnetictarget.MagnetizedObject;
-
-import java.io.PrintWriter;
-import java.util.function.Consumer;
-
-import kotlin.Unit;
-import kotlin.jvm.functions.Function0;
-
-/**
- * A helper to animate and manipulate the PiP.
- */
-public class PipMotionHelper implements PipAppOpsListener.Callback,
- FloatingContentCoordinator.FloatingContent {
-
- private static final String TAG = "PipMotionHelper";
- private static final boolean DEBUG = false;
-
- private static final int SHRINK_STACK_FROM_MENU_DURATION = 250;
- private static final int EXPAND_STACK_TO_MENU_DURATION = 250;
- private static final int LEAVE_PIP_DURATION = 300;
- private static final int SHIFT_DURATION = 300;
- private static final float STASH_RATIO = 0.25f;
-
- /** Friction to use for PIP when it moves via physics fling animations. */
- private static final float DEFAULT_FRICTION = 2f;
-
- private final Context mContext;
- private final PipTaskOrganizer mPipTaskOrganizer;
-
- private PipMenuActivityController mMenuController;
- private PipSnapAlgorithm mSnapAlgorithm;
-
- private final Handler mMainHandler = new Handler(Looper.getMainLooper());
-
- /** PIP's current bounds on the screen. */
- private final Rect mBounds = new Rect();
-
- /** The bounds within which PIP's top-left coordinate is allowed to move. */
- private final Rect mMovementBounds = new Rect();
-
- /** The region that all of PIP must stay within. */
- private final Rect mFloatingAllowedArea = new Rect();
-
- /**
- * Temporary bounds used when PIP is being dragged or animated. These bounds are applied to PIP
- * using {@link PipTaskOrganizer#scheduleUserResizePip}, so that we can animate shrinking into
- * and expanding out of the magnetic dismiss target.
- *
- * Once PIP is done being dragged or animated, we set {@link #mBounds} equal to these temporary
- * bounds, and call {@link PipTaskOrganizer#scheduleFinishResizePip} to 'officially' move PIP to
- * its new bounds.
- */
- private final Rect mTemporaryBounds = new Rect();
-
- /** The destination bounds to which PIP is animating. */
- private final Rect mAnimatingToBounds = new Rect();
-
- /** Coordinator instance for resolving conflicts with other floating content. */
- private FloatingContentCoordinator mFloatingContentCoordinator;
-
- private ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal =
- ThreadLocal.withInitial(() -> {
- FrameCallbackScheduler scheduler = runnable ->
- Choreographer.getSfInstance().postFrameCallback(t -> runnable.run());
- AnimationHandler handler = new AnimationHandler(scheduler);
- return handler;
- });
-
- /**
- * PhysicsAnimator instance for animating {@link #mTemporaryBounds} using physics animations.
- */
- private PhysicsAnimator<Rect> mTemporaryBoundsPhysicsAnimator = PhysicsAnimator.getInstance(
- mTemporaryBounds);
-
- private MagnetizedObject<Rect> mMagnetizedPip;
-
- /**
- * Update listener that resizes the PIP to {@link #mTemporaryBounds}.
- */
- private final PhysicsAnimator.UpdateListener<Rect> mResizePipUpdateListener;
-
- /** FlingConfig instances provided to PhysicsAnimator for fling gestures. */
- private PhysicsAnimator.FlingConfig mFlingConfigX;
- private PhysicsAnimator.FlingConfig mFlingConfigY;
- /** FlingConfig instances proviced to PhysicsAnimator for stashing. */
- private PhysicsAnimator.FlingConfig mStashConfigX;
-
- /** SpringConfig to use for fling-then-spring animations. */
- private final PhysicsAnimator.SpringConfig mSpringConfig =
- new PhysicsAnimator.SpringConfig(
- SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_LOW_BOUNCY);
-
- /** SpringConfig to use for springing PIP away from conflicting floating content. */
- private final PhysicsAnimator.SpringConfig mConflictResolutionSpringConfig =
- new PhysicsAnimator.SpringConfig(
- SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_LOW_BOUNCY);
-
- private final Consumer<Rect> mUpdateBoundsCallback = (Rect newBounds) -> {
- mMainHandler.post(() -> {
- mMenuController.updateMenuLayout(newBounds);
- mBounds.set(newBounds);
- });
- };
-
- /**
- * Whether we're springing to the touch event location (vs. moving it to that position
- * instantly). We spring-to-touch after PIP is dragged out of the magnetic target, since it was
- * 'stuck' in the target and needs to catch up to the touch location.
- */
- private boolean mSpringingToTouch = false;
-
- /**
- * Whether PIP was released in the dismiss target, and will be animated out and dismissed
- * shortly.
- */
- private boolean mDismissalPending = false;
-
- /**
- * Gets set in {@link #animateToExpandedState(Rect, Rect, Rect, Runnable)}, this callback is
- * used to show menu activity when the expand animation is completed.
- */
- private Runnable mPostPipTransitionCallback;
-
- private final PipTaskOrganizer.PipTransitionCallback mPipTransitionCallback =
- new PipTaskOrganizer.PipTransitionCallback() {
- @Override
- public void onPipTransitionStarted(ComponentName activity, int direction, Rect pipBounds) {}
-
- @Override
- public void onPipTransitionFinished(ComponentName activity, int direction) {
- if (mPostPipTransitionCallback != null) {
- mPostPipTransitionCallback.run();
- mPostPipTransitionCallback = null;
- }
- }
-
- @Override
- public void onPipTransitionCanceled(ComponentName activity, int direction) {}
- };
-
- public PipMotionHelper(Context context, PipTaskOrganizer pipTaskOrganizer,
- PipMenuActivityController menuController, PipSnapAlgorithm snapAlgorithm,
- FloatingContentCoordinator floatingContentCoordinator) {
- mContext = context;
- mPipTaskOrganizer = pipTaskOrganizer;
- mMenuController = menuController;
- mSnapAlgorithm = snapAlgorithm;
- mFloatingContentCoordinator = floatingContentCoordinator;
- mPipTaskOrganizer.registerPipTransitionCallback(mPipTransitionCallback);
- mTemporaryBoundsPhysicsAnimator.setCustomAnimationHandler(
- mSfAnimationHandlerThreadLocal.get());
-
- mResizePipUpdateListener = (target, values) -> {
- if (!mTemporaryBounds.isEmpty()) {
- mPipTaskOrganizer.scheduleUserResizePip(
- mBounds, mTemporaryBounds, null);
- }
- };
- }
-
- @NonNull
- @Override
- public Rect getFloatingBoundsOnScreen() {
- return !mAnimatingToBounds.isEmpty() ? mAnimatingToBounds : mBounds;
- }
-
- @NonNull
- @Override
- public Rect getAllowedFloatingBoundsRegion() {
- return mFloatingAllowedArea;
- }
-
- @Override
- public void moveToBounds(@NonNull Rect bounds) {
- animateToBounds(bounds, mConflictResolutionSpringConfig);
- }
-
- /**
- * Synchronizes the current bounds with the pinned stack, cancelling any ongoing animations.
- */
- void synchronizePinnedStackBounds() {
- cancelAnimations();
- mBounds.set(mPipTaskOrganizer.getLastReportedBounds());
- mTemporaryBounds.setEmpty();
-
- if (mPipTaskOrganizer.isInPip()) {
- mFloatingContentCoordinator.onContentMoved(this);
- }
- }
-
- boolean isAnimating() {
- return mTemporaryBoundsPhysicsAnimator.isRunning();
- }
-
- /**
- * Tries to move the pinned stack to the given {@param bounds}.
- */
- void movePip(Rect toBounds) {
- movePip(toBounds, false /* isDragging */);
- }
-
- /**
- * Tries to move the pinned stack to the given {@param bounds}.
- *
- * @param isDragging Whether this movement is the result of a drag touch gesture. If so, we
- * won't notify the floating content coordinator of this move, since that will
- * happen when the gesture ends.
- */
- void movePip(Rect toBounds, boolean isDragging) {
- if (!isDragging) {
- mFloatingContentCoordinator.onContentMoved(this);
- }
-
- if (!mSpringingToTouch) {
- // If we are moving PIP directly to the touch event locations, cancel any animations and
- // move PIP to the given bounds.
- cancelAnimations();
-
- if (!isDragging) {
- resizePipUnchecked(toBounds);
- mBounds.set(toBounds);
- } else {
- mTemporaryBounds.set(toBounds);
- mPipTaskOrganizer.scheduleUserResizePip(mBounds, mTemporaryBounds,
- (Rect newBounds) -> {
- mMainHandler.post(() -> {
- mMenuController.updateMenuLayout(newBounds);
- });
- });
- }
- } else {
- // If PIP is 'catching up' after being stuck in the dismiss target, update the animation
- // to spring towards the new touch location.
- mTemporaryBoundsPhysicsAnimator
- .spring(FloatProperties.RECT_WIDTH, mBounds.width(), mSpringConfig)
- .spring(FloatProperties.RECT_HEIGHT, mBounds.height(), mSpringConfig)
- .spring(FloatProperties.RECT_X, toBounds.left, mSpringConfig)
- .spring(FloatProperties.RECT_Y, toBounds.top, mSpringConfig);
-
- startBoundsAnimator(toBounds.left /* toX */, toBounds.top /* toY */,
- false /* dismiss */);
- }
- }
-
- /** Animates the PIP into the dismiss target, scaling it down. */
- void animateIntoDismissTarget(
- MagnetizedObject.MagneticTarget target,
- float velX, float velY,
- boolean flung, Function0<Unit> after) {
- final PointF targetCenter = target.getCenterOnScreen();
-
- final float desiredWidth = mBounds.width() / 2;
- final float desiredHeight = mBounds.height() / 2;
-
- final float destinationX = targetCenter.x - (desiredWidth / 2f);
- final float destinationY = targetCenter.y - (desiredHeight / 2f);
-
- // If we're already in the dismiss target area, then there won't be a move to set the
- // temporary bounds, so just initialize it to the current bounds
- if (mTemporaryBounds.isEmpty()) {
- mTemporaryBounds.set(mBounds);
- }
- mTemporaryBoundsPhysicsAnimator
- .spring(FloatProperties.RECT_X, destinationX, velX, mSpringConfig)
- .spring(FloatProperties.RECT_Y, destinationY, velY, mSpringConfig)
- .spring(FloatProperties.RECT_WIDTH, desiredWidth, mSpringConfig)
- .spring(FloatProperties.RECT_HEIGHT, desiredHeight, mSpringConfig)
- .withEndActions(after);
-
- startBoundsAnimator(destinationX, destinationY, false);
- }
-
- /** Set whether we're springing-to-touch to catch up after being stuck in the dismiss target. */
- void setSpringingToTouch(boolean springingToTouch) {
- mSpringingToTouch = springingToTouch;
- }
-
- /**
- * Resizes the pinned stack back to unknown windowing mode, which could be freeform or
- * * fullscreen depending on the display area's windowing mode.
- */
- void expandLeavePip() {
- expandLeavePip(false /* skipAnimation */);
- }
-
- /**
- * Resizes the pinned stack back to unknown windowing mode, which could be freeform or
- * fullscreen depending on the display area's windowing mode.
- */
- void expandLeavePip(boolean skipAnimation) {
- if (DEBUG) {
- Log.d(TAG, "exitPip: skipAnimation=" + skipAnimation
- + " callers=\n" + Debug.getCallers(5, " "));
- }
- cancelAnimations();
- mMenuController.hideMenuWithoutResize();
- mPipTaskOrganizer.getUpdateHandler().post(() -> {
- mPipTaskOrganizer.exitPip(skipAnimation
- ? 0
- : LEAVE_PIP_DURATION);
- });
- }
-
- /**
- * Dismisses the pinned stack.
- */
- @Override
- public void dismissPip() {
- if (DEBUG) {
- Log.d(TAG, "removePip: callers=\n" + Debug.getCallers(5, " "));
- }
- cancelAnimations();
- mMenuController.hideMenuWithoutResize();
- mPipTaskOrganizer.removePip();
- }
-
- /** Sets the movement bounds to use to constrain PIP position animations. */
- void setCurrentMovementBounds(Rect movementBounds) {
- mMovementBounds.set(movementBounds);
- rebuildFlingConfigs();
-
- // The movement bounds represent the area within which we can move PIP's top-left position.
- // The allowed area for all of PIP is those bounds plus PIP's width and height.
- mFloatingAllowedArea.set(mMovementBounds);
- mFloatingAllowedArea.right += mBounds.width();
- mFloatingAllowedArea.bottom += mBounds.height();
- }
-
- /**
- * @return the PiP bounds.
- */
- Rect getBounds() {
- return mBounds;
- }
-
- /**
- * Returns the PIP bounds if we're not animating, or the current, temporary animating bounds
- * otherwise.
- */
- Rect getPossiblyAnimatingBounds() {
- return mTemporaryBounds.isEmpty() ? mBounds : mTemporaryBounds;
- }
-
- /**
- * Flings the PiP to the closest snap target.
- */
- void flingToSnapTarget(
- float velocityX, float velocityY,
- @Nullable Runnable updateAction, @Nullable Runnable endAction) {
- movetoTarget(velocityX, velocityY, updateAction, endAction, false /* isStash */);
- }
-
- /**
- * Stash PiP to the closest edge.
- */
- void stashToEdge(
- float velocityX, float velocityY,
- @Nullable Runnable updateAction, @Nullable Runnable endAction) {
- movetoTarget(velocityX, velocityY, updateAction, endAction, true /* isStash */);
- }
-
- private void movetoTarget(
- float velocityX, float velocityY,
- @Nullable Runnable updateAction, @Nullable Runnable endAction, boolean isStash) {
- // If we're flinging to a snap target now, we're not springing to catch up to the touch
- // location now.
- mSpringingToTouch = false;
-
- mTemporaryBoundsPhysicsAnimator
- .spring(FloatProperties.RECT_WIDTH, mBounds.width(), mSpringConfig)
- .spring(FloatProperties.RECT_HEIGHT, mBounds.height(), mSpringConfig)
- .flingThenSpring(
- FloatProperties.RECT_X, velocityX, isStash ? mStashConfigX : mFlingConfigX,
- mSpringConfig, true /* flingMustReachMinOrMax */)
- .flingThenSpring(
- FloatProperties.RECT_Y, velocityY, mFlingConfigY, mSpringConfig)
- .withEndActions(endAction);
-
- if (updateAction != null) {
- mTemporaryBoundsPhysicsAnimator.addUpdateListener(
- (target, values) -> updateAction.run());
- }
-
- final float offset = ((float) mBounds.width()) * (1.0f - STASH_RATIO);
- final float leftEdge = isStash ? mMovementBounds.left - offset : mMovementBounds.left;
- final float rightEdge = isStash ? mMovementBounds.right + offset : mMovementBounds.right;
-
- final float xEndValue = velocityX < 0 ? leftEdge : rightEdge;
- final float estimatedFlingYEndValue =
- PhysicsAnimator.estimateFlingEndValue(
- mTemporaryBounds.top, velocityY, mFlingConfigY);
-
- startBoundsAnimator(xEndValue /* toX */, estimatedFlingYEndValue /* toY */,
- false /* dismiss */);
- }
-
- /**
- * Animates PIP to the provided bounds, using physics animations and the given spring
- * configuration
- */
- void animateToBounds(Rect bounds, PhysicsAnimator.SpringConfig springConfig) {
- if (!mTemporaryBoundsPhysicsAnimator.isRunning()) {
- // Animate from the current bounds if we're not already animating.
- mTemporaryBounds.set(mBounds);
- }
-
- mTemporaryBoundsPhysicsAnimator
- .spring(FloatProperties.RECT_X, bounds.left, springConfig)
- .spring(FloatProperties.RECT_Y, bounds.top, springConfig);
- startBoundsAnimator(bounds.left /* toX */, bounds.top /* toY */,
- false /* dismiss */);
- }
-
- /**
- * Animates the dismissal of the PiP off the edge of the screen.
- */
- void animateDismiss() {
- // Animate off the bottom of the screen, then dismiss PIP.
- mTemporaryBoundsPhysicsAnimator
- .spring(FloatProperties.RECT_Y,
- mMovementBounds.bottom + mBounds.height() * 2,
- 0,
- mSpringConfig)
- .withEndActions(this::dismissPip);
-
- startBoundsAnimator(
- mBounds.left /* toX */, mBounds.bottom + mBounds.height() /* toY */,
- true /* dismiss */);
-
- mDismissalPending = false;
- }
-
- /**
- * Animates the PiP to the expanded state to show the menu.
- */
- float animateToExpandedState(Rect expandedBounds, Rect movementBounds,
- Rect expandedMovementBounds, Runnable callback) {
- float savedSnapFraction = mSnapAlgorithm.getSnapFraction(new Rect(mBounds), movementBounds);
- mSnapAlgorithm.applySnapFraction(expandedBounds, expandedMovementBounds, savedSnapFraction);
- mPostPipTransitionCallback = callback;
- resizeAndAnimatePipUnchecked(expandedBounds, EXPAND_STACK_TO_MENU_DURATION);
- return savedSnapFraction;
- }
-
- /**
- * Animates the PiP from the expanded state to the normal state after the menu is hidden.
- */
- void animateToUnexpandedState(Rect normalBounds, float savedSnapFraction,
- Rect normalMovementBounds, Rect currentMovementBounds, boolean immediate) {
- if (savedSnapFraction < 0f) {
- // If there are no saved snap fractions, then just use the current bounds
- savedSnapFraction = mSnapAlgorithm.getSnapFraction(new Rect(mBounds),
- currentMovementBounds);
- }
- mSnapAlgorithm.applySnapFraction(normalBounds, normalMovementBounds, savedSnapFraction);
-
- if (immediate) {
- movePip(normalBounds);
- } else {
- resizeAndAnimatePipUnchecked(normalBounds, SHRINK_STACK_FROM_MENU_DURATION);
- }
- }
-
- /**
- * Animates the PiP to offset it from the IME or shelf.
- */
- void animateToOffset(Rect originalBounds, int offset) {
- if (DEBUG) {
- Log.d(TAG, "animateToOffset: originalBounds=" + originalBounds + " offset=" + offset
- + " callers=\n" + Debug.getCallers(5, " "));
- }
- cancelAnimations();
- mPipTaskOrganizer.scheduleOffsetPip(originalBounds, offset, SHIFT_DURATION,
- mUpdateBoundsCallback);
- }
-
- /**
- * Cancels all existing animations.
- */
- private void cancelAnimations() {
- mTemporaryBoundsPhysicsAnimator.cancel();
- mAnimatingToBounds.setEmpty();
- mSpringingToTouch = false;
- }
-
- /** Set new fling configs whose min/max values respect the given movement bounds. */
- private void rebuildFlingConfigs() {
- mFlingConfigX = new PhysicsAnimator.FlingConfig(
- DEFAULT_FRICTION, mMovementBounds.left, mMovementBounds.right);
- mFlingConfigY = new PhysicsAnimator.FlingConfig(
- DEFAULT_FRICTION, mMovementBounds.top, mMovementBounds.bottom);
- final float offset = ((float) mBounds.width()) * (1.0f - STASH_RATIO);
- mStashConfigX = new PhysicsAnimator.FlingConfig(
- DEFAULT_FRICTION, mMovementBounds.left - offset, mMovementBounds.right + offset);
- }
-
- /**
- * Starts the physics animator which will update the animated PIP bounds using physics
- * animations, as well as the TimeAnimator which will apply those bounds to PIP.
- *
- * This will also add end actions to the bounds animator that cancel the TimeAnimator and update
- * the 'real' bounds to equal the final animated bounds.
- */
- private void startBoundsAnimator(float toX, float toY, boolean dismiss) {
- if (!mSpringingToTouch) {
- cancelAnimations();
- }
-
- // Set animatingToBounds directly to avoid allocating a new Rect, but then call
- // setAnimatingToBounds to run the normal logic for changing animatingToBounds.
- mAnimatingToBounds.set(
- (int) toX,
- (int) toY,
- (int) toX + mBounds.width(),
- (int) toY + mBounds.height());
- setAnimatingToBounds(mAnimatingToBounds);
-
- if (!mTemporaryBoundsPhysicsAnimator.isRunning()) {
- mTemporaryBoundsPhysicsAnimator
- .addUpdateListener(mResizePipUpdateListener)
- .withEndActions(this::onBoundsAnimationEnd);
- }
-
- mTemporaryBoundsPhysicsAnimator.start();
- }
-
- /**
- * Notify that PIP was released in the dismiss target and will be animated out and dismissed
- * shortly.
- */
- void notifyDismissalPending() {
- mDismissalPending = true;
- }
-
- private void onBoundsAnimationEnd() {
- if (!mDismissalPending
- && !mSpringingToTouch
- && !mMagnetizedPip.getObjectStuckToTarget()) {
- mBounds.set(mTemporaryBounds);
- if (!mDismissalPending) {
- // do not schedule resize if PiP is dismissing, which may cause app re-open to
- // mBounds instead of it's normal bounds.
- mPipTaskOrganizer.scheduleFinishResizePip(mBounds);
- }
- mTemporaryBounds.setEmpty();
- }
-
- mAnimatingToBounds.setEmpty();
- mSpringingToTouch = false;
- mDismissalPending = false;
- }
-
- /**
- * Notifies the floating coordinator that we're moving, and sets {@link #mAnimatingToBounds} so
- * we return these bounds from
- * {@link FloatingContentCoordinator.FloatingContent#getFloatingBoundsOnScreen()}.
- */
- private void setAnimatingToBounds(Rect bounds) {
- mAnimatingToBounds.set(bounds);
- mFloatingContentCoordinator.onContentMoved(this);
- }
-
- /**
- * Directly resizes the PiP to the given {@param bounds}.
- */
- private void resizePipUnchecked(Rect toBounds) {
- if (DEBUG) {
- Log.d(TAG, "resizePipUnchecked: toBounds=" + toBounds
- + " callers=\n" + Debug.getCallers(5, " "));
- }
- if (!toBounds.equals(mBounds)) {
- mPipTaskOrganizer.scheduleResizePip(toBounds, mUpdateBoundsCallback);
- }
- }
-
- /**
- * Directly resizes the PiP to the given {@param bounds}.
- */
- private void resizeAndAnimatePipUnchecked(Rect toBounds, int duration) {
- if (DEBUG) {
- Log.d(TAG, "resizeAndAnimatePipUnchecked: toBounds=" + toBounds
- + " duration=" + duration + " callers=\n" + Debug.getCallers(5, " "));
- }
-
- // Intentionally resize here even if the current bounds match the destination bounds.
- // This is so all the proper callbacks are performed.
- mPipTaskOrganizer.scheduleAnimateResizePip(toBounds, duration, mUpdateBoundsCallback);
- setAnimatingToBounds(toBounds);
- }
-
- /**
- * Returns a MagnetizedObject wrapper for PIP's animated bounds. This is provided to the
- * magnetic dismiss target so it can calculate PIP's size and position.
- */
- MagnetizedObject<Rect> getMagnetizedPip() {
- if (mMagnetizedPip == null) {
- mMagnetizedPip = new MagnetizedObject<Rect>(
- mContext, mTemporaryBounds, FloatProperties.RECT_X, FloatProperties.RECT_Y) {
- @Override
- public float getWidth(@NonNull Rect animatedPipBounds) {
- return animatedPipBounds.width();
- }
-
- @Override
- public float getHeight(@NonNull Rect animatedPipBounds) {
- return animatedPipBounds.height();
- }
-
- @Override
- public void getLocationOnScreen(
- @NonNull Rect animatedPipBounds, @NonNull int[] loc) {
- loc[0] = animatedPipBounds.left;
- loc[1] = animatedPipBounds.top;
- }
- };
- }
-
- return mMagnetizedPip;
- }
-
- public void dump(PrintWriter pw, String prefix) {
- final String innerPrefix = prefix + " ";
- pw.println(prefix + TAG);
- pw.println(innerPrefix + "mBounds=" + mBounds);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java
deleted file mode 100644
index 08d9b2ae21b0..000000000000
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java
+++ /dev/null
@@ -1,528 +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.pip.phone;
-
-import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_PINCH_RESIZE;
-import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_BOTTOM;
-import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_LEFT;
-import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_NONE;
-import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_RIGHT;
-import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_TOP;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Point;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.hardware.input.InputManager;
-import android.os.Handler;
-import android.os.Looper;
-import android.provider.DeviceConfig;
-import android.view.BatchedInputEventReceiver;
-import android.view.Choreographer;
-import android.view.InputChannel;
-import android.view.InputEvent;
-import android.view.InputEventReceiver;
-import android.view.InputMonitor;
-import android.view.MotionEvent;
-import android.view.ScaleGestureDetector;
-import android.view.ViewConfiguration;
-
-import com.android.internal.policy.TaskResizingAlgorithm;
-import com.android.systemui.model.SysUiState;
-import com.android.systemui.pip.PipBoundsHandler;
-import com.android.systemui.pip.PipTaskOrganizer;
-import com.android.systemui.pip.PipUiEventLogger;
-import com.android.systemui.util.DeviceConfigProxy;
-import com.android.wm.shell.R;
-
-import java.io.PrintWriter;
-import java.util.concurrent.Executor;
-import java.util.function.Function;
-
-/**
- * Helper on top of PipTouchHandler that handles inputs OUTSIDE of the PIP window, which is used to
- * trigger dynamic resize.
- */
-public class PipResizeGestureHandler {
-
- private static final String TAG = "PipResizeGestureHandler";
- private static final float PINCH_THRESHOLD = 0.05f;
- private static final float STARTING_SCALE_FACTOR = 1.0f;
-
- private static final int INVALID_SYSUI_STATE_MASK =
- SYSUI_STATE_GLOBAL_ACTIONS_SHOWING
- | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
- | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED
- | SYSUI_STATE_BOUNCER_SHOWING
- | SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
- | SYSUI_STATE_BUBBLES_EXPANDED
- | SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
-
- private final Context mContext;
- private final PipBoundsHandler mPipBoundsHandler;
- private final PipMotionHelper mMotionHelper;
- private final int mDisplayId;
- private final Executor mMainExecutor;
- private final SysUiState mSysUiState;
- private final ScaleGestureDetector mScaleGestureDetector;
- private final Region mTmpRegion = new Region();
-
- private final PointF mDownPoint = new PointF();
- private final Point mMaxSize = new Point();
- private final Point mMinSize = new Point();
- private final Rect mLastResizeBounds = new Rect();
- private final Rect mUserResizeBounds = new Rect();
- private final Rect mLastDownBounds = new Rect();
- private final Rect mDragCornerSize = new Rect();
- private final Rect mTmpTopLeftCorner = new Rect();
- private final Rect mTmpTopRightCorner = new Rect();
- private final Rect mTmpBottomLeftCorner = new Rect();
- private final Rect mTmpBottomRightCorner = new Rect();
- private final Rect mDisplayBounds = new Rect();
- private final Function<Rect, Rect> mMovementBoundsSupplier;
- private final Runnable mUpdateMovementBoundsRunnable;
-
- private int mDelta;
- private float mTouchSlop;
- private boolean mAllowGesture;
- private boolean mIsAttached;
- private boolean mIsEnabled;
- private boolean mEnablePinchResize;
- private boolean mThresholdCrossed;
- private boolean mUsingPinchToZoom = false;
- private float mScaleFactor = STARTING_SCALE_FACTOR;
-
- private InputMonitor mInputMonitor;
- private InputEventReceiver mInputEventReceiver;
- private PipTaskOrganizer mPipTaskOrganizer;
- private PipMenuActivityController mPipMenuActivityController;
- private PipUiEventLogger mPipUiEventLogger;
-
- private int mCtrlType;
-
- public PipResizeGestureHandler(Context context, PipBoundsHandler pipBoundsHandler,
- PipMotionHelper motionHelper, DeviceConfigProxy deviceConfig,
- PipTaskOrganizer pipTaskOrganizer, Function<Rect, Rect> movementBoundsSupplier,
- Runnable updateMovementBoundsRunnable, SysUiState sysUiState,
- PipUiEventLogger pipUiEventLogger, PipMenuActivityController menuActivityController) {
- mContext = context;
- mDisplayId = context.getDisplayId();
- mMainExecutor = context.getMainExecutor();
- mPipBoundsHandler = pipBoundsHandler;
- mMotionHelper = motionHelper;
- mPipTaskOrganizer = pipTaskOrganizer;
- mMovementBoundsSupplier = movementBoundsSupplier;
- mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable;
- mSysUiState = sysUiState;
- mPipMenuActivityController = menuActivityController;
- mPipUiEventLogger = pipUiEventLogger;
-
- context.getDisplay().getRealSize(mMaxSize);
- reloadResources();
-
- mScaleGestureDetector = new ScaleGestureDetector(context,
- new ScaleGestureDetector.OnScaleGestureListener() {
- @Override
- public boolean onScale(ScaleGestureDetector detector) {
- mScaleFactor *= detector.getScaleFactor();
-
- if (!mThresholdCrossed
- && (mScaleFactor > (STARTING_SCALE_FACTOR + PINCH_THRESHOLD)
- || mScaleFactor < (STARTING_SCALE_FACTOR - PINCH_THRESHOLD))) {
- mThresholdCrossed = true;
- mInputMonitor.pilferPointers();
- }
- if (mThresholdCrossed) {
- int height = Math.min(mMaxSize.y, Math.max(mMinSize.y,
- (int) (mScaleFactor * mLastDownBounds.height())));
- int width = Math.min(mMaxSize.x, Math.max(mMinSize.x,
- (int) (mScaleFactor * mLastDownBounds.width())));
- int top, bottom, left, right;
-
- if ((mCtrlType & CTRL_TOP) != 0) {
- top = mLastDownBounds.bottom - height;
- bottom = mLastDownBounds.bottom;
- } else {
- top = mLastDownBounds.top;
- bottom = mLastDownBounds.top + height;
- }
-
- if ((mCtrlType & CTRL_LEFT) != 0) {
- left = mLastDownBounds.right - width;
- right = mLastDownBounds.right;
- } else {
- left = mLastDownBounds.left;
- right = mLastDownBounds.left + width;
- }
-
- mLastResizeBounds.set(left, top, right, bottom);
- mPipTaskOrganizer.scheduleUserResizePip(mLastDownBounds,
- mLastResizeBounds,
- null);
- }
- return true;
- }
-
- @Override
- public boolean onScaleBegin(ScaleGestureDetector detector) {
- setCtrlTypeForPinchToZoom();
- return true;
- }
-
- @Override
- public void onScaleEnd(ScaleGestureDetector detector) {
- mScaleFactor = STARTING_SCALE_FACTOR;
- finishResize();
- }
- });
-
- mEnablePinchResize = DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_SYSTEMUI,
- PIP_PINCH_RESIZE,
- /* defaultValue = */ false);
- deviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, mMainExecutor,
- new DeviceConfig.OnPropertiesChangedListener() {
- @Override
- public void onPropertiesChanged(DeviceConfig.Properties properties) {
- if (properties.getKeyset().contains(PIP_PINCH_RESIZE)) {
- mEnablePinchResize = properties.getBoolean(
- PIP_PINCH_RESIZE, /* defaultValue = */ false);
- }
- }
- });
- }
-
- public void onConfigurationChanged() {
- reloadResources();
- }
-
- private void reloadResources() {
- final Resources res = mContext.getResources();
- mDelta = res.getDimensionPixelSize(R.dimen.pip_resize_edge_size);
- mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
- }
-
- private void resetDragCorners() {
- mDragCornerSize.set(0, 0, mDelta, mDelta);
- mTmpTopLeftCorner.set(mDragCornerSize);
- mTmpTopRightCorner.set(mDragCornerSize);
- mTmpBottomLeftCorner.set(mDragCornerSize);
- mTmpBottomRightCorner.set(mDragCornerSize);
- }
-
- private void disposeInputChannel() {
- if (mInputEventReceiver != null) {
- mInputEventReceiver.dispose();
- mInputEventReceiver = null;
- }
- if (mInputMonitor != null) {
- mInputMonitor.dispose();
- mInputMonitor = null;
- }
- }
-
- void onActivityPinned() {
- mIsAttached = true;
- updateIsEnabled();
- }
-
- void onActivityUnpinned() {
- mIsAttached = false;
- mUserResizeBounds.setEmpty();
- updateIsEnabled();
- }
-
- private void updateIsEnabled() {
- boolean isEnabled = mIsAttached;
- if (isEnabled == mIsEnabled) {
- return;
- }
- mIsEnabled = isEnabled;
- disposeInputChannel();
-
- if (mIsEnabled) {
- // Register input event receiver
- mInputMonitor = InputManager.getInstance().monitorGestureInput(
- "pip-resize", mDisplayId);
- mInputEventReceiver = new SysUiInputEventReceiver(
- mInputMonitor.getInputChannel(), Looper.getMainLooper());
- }
- }
-
- private void onInputEvent(InputEvent ev) {
- if (ev instanceof MotionEvent) {
- if (mUsingPinchToZoom) {
- mScaleGestureDetector.onTouchEvent((MotionEvent) ev);
- } else {
- onDragCornerResize((MotionEvent) ev);
- }
- }
- }
-
- /**
- * Check whether the current x,y coordinate is within the region in which drag-resize should
- * start.
- * This consists of 4 small squares on the 4 corners of the PIP window, a quarter of which
- * overlaps with the PIP window while the rest goes outside of the PIP window.
- * _ _ _ _
- * |_|_|_________|_|_|
- * |_|_| |_|_|
- * | PIP |
- * | WINDOW |
- * _|_ _|_
- * |_|_|_________|_|_|
- * |_|_| |_|_|
- */
- public boolean isWithinTouchRegion(int x, int y) {
- final Rect currentPipBounds = mMotionHelper.getBounds();
- if (currentPipBounds == null) {
- return false;
- }
- resetDragCorners();
- mTmpTopLeftCorner.offset(currentPipBounds.left - mDelta / 2,
- currentPipBounds.top - mDelta / 2);
- mTmpTopRightCorner.offset(currentPipBounds.right - mDelta / 2,
- currentPipBounds.top - mDelta / 2);
- mTmpBottomLeftCorner.offset(currentPipBounds.left - mDelta / 2,
- currentPipBounds.bottom - mDelta / 2);
- mTmpBottomRightCorner.offset(currentPipBounds.right - mDelta / 2,
- currentPipBounds.bottom - mDelta / 2);
-
- mTmpRegion.setEmpty();
- mTmpRegion.op(mTmpTopLeftCorner, Region.Op.UNION);
- mTmpRegion.op(mTmpTopRightCorner, Region.Op.UNION);
- mTmpRegion.op(mTmpBottomLeftCorner, Region.Op.UNION);
- mTmpRegion.op(mTmpBottomRightCorner, Region.Op.UNION);
-
- return mTmpRegion.contains(x, y);
- }
-
- public boolean willStartResizeGesture(MotionEvent ev) {
- if (isInValidSysUiState()) {
- switch (ev.getActionMasked()) {
- case MotionEvent.ACTION_DOWN:
- // Always pass the DOWN event to the ScaleGestureDetector
- mScaleGestureDetector.onTouchEvent(ev);
- if (isWithinTouchRegion((int) ev.getRawX(), (int) ev.getRawY())) {
- return true;
- }
- break;
-
- case MotionEvent.ACTION_POINTER_DOWN:
- if (mEnablePinchResize && ev.getPointerCount() == 2) {
- mUsingPinchToZoom = true;
- return true;
- }
- break;
-
- default:
- break;
- }
- }
- return false;
- }
-
- private void setCtrlTypeForPinchToZoom() {
- final Rect currentPipBounds = mMotionHelper.getBounds();
- mLastDownBounds.set(mMotionHelper.getBounds());
-
- Rect movementBounds = mMovementBoundsSupplier.apply(currentPipBounds);
- mDisplayBounds.set(movementBounds.left,
- movementBounds.top,
- movementBounds.right + currentPipBounds.width(),
- movementBounds.bottom + currentPipBounds.height());
-
- if (currentPipBounds.left == mDisplayBounds.left) {
- mCtrlType |= CTRL_RIGHT;
- } else {
- mCtrlType |= CTRL_LEFT;
- }
-
- if (currentPipBounds.top > mDisplayBounds.top + mDisplayBounds.height()) {
- mCtrlType |= CTRL_TOP;
- } else {
- mCtrlType |= CTRL_BOTTOM;
- }
- }
-
- private void setCtrlType(int x, int y) {
- final Rect currentPipBounds = mMotionHelper.getBounds();
-
- Rect movementBounds = mMovementBoundsSupplier.apply(currentPipBounds);
- mDisplayBounds.set(movementBounds.left,
- movementBounds.top,
- movementBounds.right + currentPipBounds.width(),
- movementBounds.bottom + currentPipBounds.height());
-
- if (mTmpTopLeftCorner.contains(x, y) && currentPipBounds.top != mDisplayBounds.top
- && currentPipBounds.left != mDisplayBounds.left) {
- mCtrlType |= CTRL_LEFT;
- mCtrlType |= CTRL_TOP;
- }
- if (mTmpTopRightCorner.contains(x, y) && currentPipBounds.top != mDisplayBounds.top
- && currentPipBounds.right != mDisplayBounds.right) {
- mCtrlType |= CTRL_RIGHT;
- mCtrlType |= CTRL_TOP;
- }
- if (mTmpBottomRightCorner.contains(x, y)
- && currentPipBounds.bottom != mDisplayBounds.bottom
- && currentPipBounds.right != mDisplayBounds.right) {
- mCtrlType |= CTRL_RIGHT;
- mCtrlType |= CTRL_BOTTOM;
- }
- if (mTmpBottomLeftCorner.contains(x, y)
- && currentPipBounds.bottom != mDisplayBounds.bottom
- && currentPipBounds.left != mDisplayBounds.left) {
- mCtrlType |= CTRL_LEFT;
- mCtrlType |= CTRL_BOTTOM;
- }
- }
-
- private boolean isInValidSysUiState() {
- return (mSysUiState.getFlags() & INVALID_SYSUI_STATE_MASK) == 0;
- }
-
- private void onDragCornerResize(MotionEvent ev) {
- int action = ev.getActionMasked();
- float x = ev.getX();
- float y = ev.getY();
- if (action == MotionEvent.ACTION_DOWN) {
- final Rect currentPipBounds = mMotionHelper.getBounds();
- mLastResizeBounds.setEmpty();
- mAllowGesture = isInValidSysUiState() && isWithinTouchRegion((int) x, (int) y);
- if (mAllowGesture) {
- setCtrlType((int) x, (int) y);
- mDownPoint.set(x, y);
- mLastDownBounds.set(mMotionHelper.getBounds());
- }
- if (!currentPipBounds.contains((int) ev.getX(), (int) ev.getY())
- && mPipMenuActivityController.isMenuVisible()) {
- mPipMenuActivityController.hideMenu();
- }
-
- } else if (mAllowGesture) {
- switch (action) {
- case MotionEvent.ACTION_POINTER_DOWN:
- // We do not support multi touch for resizing via drag
- mAllowGesture = false;
- break;
- case MotionEvent.ACTION_MOVE:
- // Capture inputs
- if (!mThresholdCrossed
- && Math.hypot(x - mDownPoint.x, y - mDownPoint.y) > mTouchSlop) {
- mThresholdCrossed = true;
- // Reset the down to begin resizing from this point
- mDownPoint.set(x, y);
- mInputMonitor.pilferPointers();
- }
- if (mThresholdCrossed) {
- if (mPipMenuActivityController.isMenuVisible()) {
- mPipMenuActivityController.hideMenuWithoutResize();
- mPipMenuActivityController.hideMenu();
- }
- final Rect currentPipBounds = mMotionHelper.getBounds();
- mLastResizeBounds.set(TaskResizingAlgorithm.resizeDrag(x, y,
- mDownPoint.x, mDownPoint.y, currentPipBounds, mCtrlType, mMinSize.x,
- mMinSize.y, mMaxSize, true,
- mLastDownBounds.width() > mLastDownBounds.height()));
- mPipBoundsHandler.transformBoundsToAspectRatio(mLastResizeBounds);
- mPipTaskOrganizer.scheduleUserResizePip(mLastDownBounds, mLastResizeBounds,
- null);
- }
- break;
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- finishResize();
- break;
- }
- }
- }
-
- private void finishResize() {
- if (!mLastResizeBounds.isEmpty()) {
- mUserResizeBounds.set(mLastResizeBounds);
- mPipTaskOrganizer.scheduleFinishResizePip(mLastResizeBounds,
- (Rect bounds) -> {
- new Handler(Looper.getMainLooper()).post(() -> {
- mMotionHelper.synchronizePinnedStackBounds();
- mUpdateMovementBoundsRunnable.run();
- resetState();
- });
- });
- mPipUiEventLogger.log(
- PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_RESIZE);
- } else {
- resetState();
- }
- }
-
- private void resetState() {
- mCtrlType = CTRL_NONE;
- mUsingPinchToZoom = false;
- mAllowGesture = false;
- mThresholdCrossed = false;
- }
-
- void setUserResizeBounds(Rect bounds) {
- mUserResizeBounds.set(bounds);
- }
-
- void invalidateUserResizeBounds() {
- mUserResizeBounds.setEmpty();
- }
-
- Rect getUserResizeBounds() {
- return mUserResizeBounds;
- }
-
- void updateMaxSize(int maxX, int maxY) {
- mMaxSize.set(maxX, maxY);
- }
-
- void updateMinSize(int minX, int minY) {
- mMinSize.set(minX, minY);
- }
-
- public void dump(PrintWriter pw, String prefix) {
- final String innerPrefix = prefix + " ";
- pw.println(prefix + TAG);
- pw.println(innerPrefix + "mAllowGesture=" + mAllowGesture);
- pw.println(innerPrefix + "mIsAttached=" + mIsAttached);
- pw.println(innerPrefix + "mIsEnabled=" + mIsEnabled);
- pw.println(innerPrefix + "mEnablePinchResize=" + mEnablePinchResize);
- pw.println(innerPrefix + "mThresholdCrossed=" + mThresholdCrossed);
- }
-
- class SysUiInputEventReceiver extends BatchedInputEventReceiver {
- SysUiInputEventReceiver(InputChannel channel, Looper looper) {
- super(channel, looper, Choreographer.getSfInstance());
- }
-
- public void onInputEvent(InputEvent event) {
- PipResizeGestureHandler.this.onInputEvent(event);
- finishInputEvent(event, true);
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchGesture.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchGesture.java
deleted file mode 100644
index 72335dbed115..000000000000
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchGesture.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.pip.phone;
-
-/**
- * A generic interface for a touch gesture.
- */
-public abstract class PipTouchGesture {
-
- /**
- * Handle the touch down.
- */
- public void onDown(PipTouchState touchState) {}
-
- /**
- * Handle the touch move, and return whether the event was consumed.
- */
- public boolean onMove(PipTouchState touchState) {
- return false;
- }
-
- /**
- * Handle the touch up, and return whether the gesture was consumed.
- */
- public boolean onUp(PipTouchState touchState) {
- return false;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
deleted file mode 100644
index 858683c4e2d4..000000000000
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ /dev/null
@@ -1,1115 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.pip.phone;
-
-import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASHING;
-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;
-
-import android.annotation.SuppressLint;
-import android.app.IActivityManager;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.PixelFormat;
-import android.graphics.Point;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.graphics.drawable.TransitionDrawable;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.provider.DeviceConfig;
-import android.util.Log;
-import android.util.Size;
-import android.view.Gravity;
-import android.view.IPinnedStackController;
-import android.view.InputEvent;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityWindowInfo;
-import android.widget.FrameLayout;
-
-import androidx.annotation.NonNull;
-import androidx.dynamicanimation.animation.DynamicAnimation;
-import androidx.dynamicanimation.animation.SpringForce;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.R;
-import com.android.systemui.model.SysUiState;
-import com.android.systemui.pip.PipAnimationController;
-import com.android.systemui.pip.PipBoundsHandler;
-import com.android.systemui.pip.PipTaskOrganizer;
-import com.android.systemui.pip.PipUiEventLogger;
-import com.android.systemui.shared.system.InputConsumerController;
-import com.android.systemui.util.DeviceConfigProxy;
-import com.android.systemui.util.DismissCircleView;
-import com.android.systemui.util.FloatingContentCoordinator;
-import com.android.systemui.util.animation.PhysicsAnimator;
-import com.android.systemui.util.magnetictarget.MagnetizedObject;
-
-import java.io.PrintWriter;
-
-import kotlin.Unit;
-
-/**
- * Manages all the touch handling for PIP on the Phone, including moving, dismissing and expanding
- * the PIP.
- */
-public class PipTouchHandler {
- private static final String TAG = "PipTouchHandler";
-
- /** Duration of the dismiss scrim fading in/out. */
- private static final int DISMISS_TRANSITION_DURATION_MS = 200;
-
- /* The multiplier to apply scale the target size by when applying the magnetic field radius */
- private static final float MAGNETIC_FIELD_RADIUS_MULTIPLIER = 1.25f;
-
- // Allow dragging the PIP to a location to close it
- private final boolean mEnableDismissDragToEdge;
- // Allow PIP to resize to a slightly bigger state upon touch
- private final boolean mEnableResize;
- private final Context mContext;
- private final WindowManager mWindowManager;
- private final IActivityManager mActivityManager;
- private final PipBoundsHandler mPipBoundsHandler;
- private final PipUiEventLogger mPipUiEventLogger;
-
- private PipResizeGestureHandler mPipResizeGestureHandler;
- private IPinnedStackController mPinnedStackController;
-
- private final PipMenuActivityController mMenuController;
- private final AccessibilityManager mAccessibilityManager;
- private boolean mShowPipMenuOnAnimationEnd = false;
-
- /**
- * Whether PIP stash is enabled or not. When enabled, if at the time of fling-release the
- * PIP bounds is outside the left/right edge of the screen, it will be shown in "stashed" mode,
- * where PIP will only show partially.
- */
- private boolean mEnableStash = false;
-
- /**
- * MagnetizedObject wrapper for PIP. This allows the magnetic target library to locate and move
- * PIP.
- */
- private MagnetizedObject<Rect> mMagnetizedPip;
-
- /**
- * Container for the dismiss circle, so that it can be animated within the container via
- * translation rather than within the WindowManager via slow layout animations.
- */
- private ViewGroup mTargetViewContainer;
-
- /** Circle view used to render the dismiss target. */
- private DismissCircleView mTargetView;
-
- /**
- * MagneticTarget instance wrapping the target view and allowing us to set its magnetic radius.
- */
- private MagnetizedObject.MagneticTarget mMagneticTarget;
-
- /** PhysicsAnimator instance for animating the dismiss target in/out. */
- private PhysicsAnimator<View> mMagneticTargetAnimator;
-
- /** Default configuration to use for springing the dismiss target in/out. */
- private final PhysicsAnimator.SpringConfig mTargetSpringConfig =
- new PhysicsAnimator.SpringConfig(
- SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_LOW_BOUNCY);
-
- // The current movement bounds
- private Rect mMovementBounds = new Rect();
-
- // The reference inset bounds, used to determine the dismiss fraction
- private Rect mInsetBounds = new Rect();
- // The reference bounds used to calculate the normal/expanded target bounds
- private Rect mNormalBounds = new Rect();
- @VisibleForTesting Rect mNormalMovementBounds = new Rect();
- private Rect mExpandedBounds = new Rect();
- @VisibleForTesting Rect mExpandedMovementBounds = new Rect();
- private int mExpandedShortestEdgeSize;
-
- // Used to workaround an issue where the WM rotation happens before we are notified, allowing
- // us to send stale bounds
- private int mDeferResizeToNormalBoundsUntilRotation = -1;
- private int mDisplayRotation;
-
- /**
- * Runnable that can be posted delayed to show the target. This needs to be saved as a member
- * variable so we can pass it to removeCallbacks.
- */
- private Runnable mShowTargetAction = this::showDismissTargetMaybe;
-
- private Handler mHandler = new Handler();
-
- // Behaviour states
- private int mMenuState = MENU_STATE_NONE;
- private boolean mIsImeShowing;
- private int mImeHeight;
- private int mImeOffset;
- private int mDismissAreaHeight;
- private boolean mIsShelfShowing;
- private int mShelfHeight;
- private int mMovementBoundsExtraOffsets;
- private int mBottomOffsetBufferPx;
- private float mSavedSnapFraction = -1f;
- private boolean mSendingHoverAccessibilityEvents;
- private boolean mMovementWithinDismiss;
- private PipAccessibilityInteractionConnection mConnection;
-
- // Touch state
- private final PipTouchState mTouchState;
- private final FloatingContentCoordinator mFloatingContentCoordinator;
- private PipMotionHelper mMotionHelper;
- private PipTouchGesture mGesture;
-
- // Temp vars
- private final Rect mTmpBounds = new Rect();
-
- /**
- * A listener for the PIP menu activity.
- */
- private class PipMenuListener implements PipMenuActivityController.Listener {
- @Override
- public void onPipMenuStateChanged(int menuState, boolean resize, Runnable callback) {
- setMenuState(menuState, resize, callback);
- }
-
- @Override
- public void onPipExpand() {
- mMotionHelper.expandLeavePip();
- }
-
- @Override
- public void onPipDismiss() {
- mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_TAP_TO_REMOVE);
- mTouchState.removeDoubleTapTimeoutCallback();
- mMotionHelper.dismissPip();
- }
-
- @Override
- public void onPipShowMenu() {
- mMenuController.showMenu(MENU_STATE_FULL, mMotionHelper.getBounds(),
- true /* allowMenuTimeout */, willResizeMenu(), shouldShowResizeHandle());
- }
- }
-
- @SuppressLint("InflateParams")
- public PipTouchHandler(Context context, IActivityManager activityManager,
- PipMenuActivityController menuController,
- InputConsumerController inputConsumerController,
- PipBoundsHandler pipBoundsHandler,
- PipTaskOrganizer pipTaskOrganizer,
- FloatingContentCoordinator floatingContentCoordinator,
- DeviceConfigProxy deviceConfig,
- SysUiState sysUiState,
- PipUiEventLogger pipUiEventLogger) {
- // Initialize the Pip input consumer
- mContext = context;
- mActivityManager = activityManager;
- mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
- mPipBoundsHandler = pipBoundsHandler;
- mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
- mMenuController = menuController;
- mMenuController.addListener(new PipMenuListener());
- mGesture = new DefaultPipTouchGesture();
- mMotionHelper = new PipMotionHelper(mContext, pipTaskOrganizer, mMenuController,
- mPipBoundsHandler.getSnapAlgorithm(), floatingContentCoordinator);
- mPipResizeGestureHandler =
- new PipResizeGestureHandler(context, pipBoundsHandler, mMotionHelper,
- deviceConfig, pipTaskOrganizer, this::getMovementBounds,
- this::updateMovementBounds, sysUiState, pipUiEventLogger, menuController);
- mTouchState = new PipTouchState(ViewConfiguration.get(context), mHandler,
- () -> mMenuController.showMenuWithDelay(MENU_STATE_FULL, mMotionHelper.getBounds(),
- true /* allowMenuTimeout */, willResizeMenu(), shouldShowResizeHandle()),
- menuController::hideMenu);
-
- Resources res = context.getResources();
- mEnableDismissDragToEdge = res.getBoolean(R.bool.config_pipEnableDismissDragToEdge);
- mEnableResize = res.getBoolean(R.bool.config_pipEnableResizeForMenu);
- reloadResources();
-
- // Register the listener for input consumer touch events
- inputConsumerController.setInputListener(this::handleTouchEvent);
- inputConsumerController.setRegistrationListener(this::onRegistrationChanged);
-
- mFloatingContentCoordinator = floatingContentCoordinator;
- mConnection = new PipAccessibilityInteractionConnection(mContext, mMotionHelper,
- pipTaskOrganizer, mPipBoundsHandler.getSnapAlgorithm(),
- this::onAccessibilityShowMenu, this::updateMovementBounds, mHandler);
-
- mPipUiEventLogger = pipUiEventLogger;
-
- mTargetView = new DismissCircleView(context);
- mTargetViewContainer = new FrameLayout(context);
- mTargetViewContainer.setBackgroundDrawable(
- context.getDrawable(R.drawable.floating_dismiss_gradient_transition));
- mTargetViewContainer.setClipChildren(false);
- mTargetViewContainer.addView(mTargetView);
-
- mMagnetizedPip = mMotionHelper.getMagnetizedPip();
- mMagneticTarget = mMagnetizedPip.addTarget(mTargetView, 0);
- updateMagneticTargetSize();
-
- mMagnetizedPip.setAnimateStuckToTarget(
- (target, velX, velY, flung, after) -> {
- if (mEnableDismissDragToEdge) {
- mMotionHelper.animateIntoDismissTarget(target, velX, velY, flung, after);
- }
- return Unit.INSTANCE;
- });
- mMagnetizedPip.setMagnetListener(new MagnetizedObject.MagnetListener() {
- @Override
- public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
- // Show the dismiss target, in case the initial touch event occurred within the
- // magnetic field radius.
- if (mEnableDismissDragToEdge) {
- showDismissTargetMaybe();
- }
- }
-
- @Override
- public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
- float velX, float velY, boolean wasFlungOut) {
- if (wasFlungOut) {
- mMotionHelper.flingToSnapTarget(velX, velY, null, null);
- hideDismissTarget();
- } else {
- mMotionHelper.setSpringingToTouch(true);
- }
- }
-
- @Override
- public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
- mMotionHelper.notifyDismissalPending();
-
- mHandler.post(() -> {
- mMotionHelper.animateDismiss();
- hideDismissTarget();
- });
-
- mPipUiEventLogger.log(
- PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_DRAG_TO_REMOVE);
- }
- });
-
- mMagneticTargetAnimator = PhysicsAnimator.getInstance(mTargetView);
-
- mEnableStash = DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_SYSTEMUI,
- PIP_STASHING,
- /* defaultValue = */ false);
- deviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
- context.getMainExecutor(),
- new DeviceConfig.OnPropertiesChangedListener() {
- @Override
- public void onPropertiesChanged(DeviceConfig.Properties properties) {
- if (properties.getKeyset().contains(PIP_STASHING)) {
- mEnableStash = properties.getBoolean(
- PIP_STASHING, /* defaultValue = */ false);
- }
- }
- });
- }
-
- private void reloadResources() {
- final Resources res = mContext.getResources();
- mBottomOffsetBufferPx = res.getDimensionPixelSize(R.dimen.pip_bottom_offset_buffer);
- mExpandedShortestEdgeSize = res.getDimensionPixelSize(
- R.dimen.pip_expanded_shortest_edge_size);
- mImeOffset = res.getDimensionPixelSize(R.dimen.pip_ime_offset);
- mDismissAreaHeight = res.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height);
- updateMagneticTargetSize();
- }
-
- private void updateMagneticTargetSize() {
- if (mTargetView == null) {
- return;
- }
-
- final Resources res = mContext.getResources();
- final int targetSize = res.getDimensionPixelSize(R.dimen.dismiss_circle_size);
- final FrameLayout.LayoutParams newParams =
- new FrameLayout.LayoutParams(targetSize, targetSize);
- newParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
- newParams.bottomMargin = mContext.getResources().getDimensionPixelSize(
- R.dimen.floating_dismiss_bottom_margin);
- mTargetView.setLayoutParams(newParams);
-
- // Set the magnetic field radius equal to the target size from the center of the target
- mMagneticTarget.setMagneticFieldRadiusPx(
- (int) (targetSize * MAGNETIC_FIELD_RADIUS_MULTIPLIER));
- }
-
- private boolean shouldShowResizeHandle() {
- return false;
- }
-
- public void setTouchGesture(PipTouchGesture gesture) {
- mGesture = gesture;
- }
-
- public void setTouchEnabled(boolean enabled) {
- mTouchState.setAllowTouches(enabled);
- }
-
- public void showPictureInPictureMenu() {
- // Only show the menu if the user isn't currently interacting with the PiP
- if (!mTouchState.isUserInteracting()) {
- mMenuController.showMenu(MENU_STATE_FULL, mMotionHelper.getBounds(),
- false /* allowMenuTimeout */, willResizeMenu(),
- shouldShowResizeHandle());
- }
- }
-
- public void onActivityPinned() {
- createOrUpdateDismissTarget();
-
- mShowPipMenuOnAnimationEnd = true;
- mPipResizeGestureHandler.onActivityPinned();
- mFloatingContentCoordinator.onContentAdded(mMotionHelper);
- }
-
- public void onActivityUnpinned(ComponentName topPipActivity) {
- if (topPipActivity == null) {
- // Clean up state after the last PiP activity is removed
- cleanUpDismissTarget();
-
- mFloatingContentCoordinator.onContentRemoved(mMotionHelper);
- }
- mPipResizeGestureHandler.onActivityUnpinned();
- }
-
- public void onPinnedStackAnimationEnded(
- @PipAnimationController.TransitionDirection int direction) {
- // Always synchronize the motion helper bounds once PiP animations finish
- mMotionHelper.synchronizePinnedStackBounds();
- updateMovementBounds();
- if (direction == TRANSITION_DIRECTION_TO_PIP) {
- // Set the initial bounds as the user resize bounds.
- mPipResizeGestureHandler.setUserResizeBounds(mMotionHelper.getBounds());
- }
-
- if (mShowPipMenuOnAnimationEnd) {
- mMenuController.showMenu(MENU_STATE_CLOSE, mMotionHelper.getBounds(),
- true /* allowMenuTimeout */, false /* willResizeMenu */,
- shouldShowResizeHandle());
- mShowPipMenuOnAnimationEnd = false;
- }
- }
-
- public void onConfigurationChanged() {
- mPipResizeGestureHandler.onConfigurationChanged();
- mMotionHelper.synchronizePinnedStackBounds();
- reloadResources();
-
- // Recreate the dismiss target for the new orientation.
- createOrUpdateDismissTarget();
- }
-
- public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
- mIsImeShowing = imeVisible;
- mImeHeight = imeHeight;
- }
-
- public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) {
- mIsShelfShowing = shelfVisible;
- mShelfHeight = shelfHeight;
- }
-
- public void adjustBoundsForRotation(Rect outBounds, Rect curBounds, Rect insetBounds) {
- final Rect toMovementBounds = new Rect();
- mPipBoundsHandler.getSnapAlgorithm().getMovementBounds(outBounds, insetBounds,
- toMovementBounds, 0);
- final int prevBottom = mMovementBounds.bottom - mMovementBoundsExtraOffsets;
- if ((prevBottom - mBottomOffsetBufferPx) <= curBounds.top) {
- outBounds.offsetTo(outBounds.left, toMovementBounds.bottom);
- }
- }
-
- /**
- * Responds to IPinnedStackListener on resetting aspect ratio for the pinned window.
- */
- public void onAspectRatioChanged() {
- mPipResizeGestureHandler.invalidateUserResizeBounds();
- }
-
- public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds, Rect curBounds,
- boolean fromImeAdjustment, boolean fromShelfAdjustment, int displayRotation) {
- // Set the user resized bounds equal to the new normal bounds in case they were
- // invalidated (e.g. by an aspect ratio change).
- if (mPipResizeGestureHandler.getUserResizeBounds().isEmpty()) {
- mPipResizeGestureHandler.setUserResizeBounds(normalBounds);
- }
-
- final int bottomOffset = mIsImeShowing ? mImeHeight : 0;
- final boolean fromDisplayRotationChanged = (mDisplayRotation != displayRotation);
- if (fromDisplayRotationChanged) {
- mTouchState.reset();
- }
-
- // Re-calculate the expanded bounds
- mNormalBounds.set(normalBounds);
- Rect normalMovementBounds = new Rect();
- mPipBoundsHandler.getSnapAlgorithm().getMovementBounds(mNormalBounds, insetBounds,
- normalMovementBounds, bottomOffset);
-
- if (mMovementBounds.isEmpty()) {
- // mMovementBounds is not initialized yet and a clean movement bounds without
- // bottom offset shall be used later in this function.
- mPipBoundsHandler.getSnapAlgorithm().getMovementBounds(curBounds, insetBounds,
- mMovementBounds, 0 /* bottomOffset */);
- }
-
- // Calculate the expanded size
- float aspectRatio = (float) normalBounds.width() / normalBounds.height();
- Point displaySize = new Point();
- mContext.getDisplay().getRealSize(displaySize);
- Size expandedSize = mPipBoundsHandler.getSnapAlgorithm().getSizeForAspectRatio(aspectRatio,
- mExpandedShortestEdgeSize, displaySize.x, displaySize.y);
- mExpandedBounds.set(0, 0, expandedSize.getWidth(), expandedSize.getHeight());
- Rect expandedMovementBounds = new Rect();
- mPipBoundsHandler.getSnapAlgorithm().getMovementBounds(mExpandedBounds, insetBounds,
- expandedMovementBounds, bottomOffset);
-
- mPipResizeGestureHandler.updateMinSize(mNormalBounds.width(), mNormalBounds.height());
- mPipResizeGestureHandler.updateMaxSize(mExpandedBounds.width(), mExpandedBounds.height());
-
- // The extra offset does not really affect the movement bounds, but are applied based on the
- // current state (ime showing, or shelf offset) when we need to actually shift
- int extraOffset = Math.max(
- mIsImeShowing ? mImeOffset : 0,
- !mIsImeShowing && mIsShelfShowing ? mShelfHeight : 0);
-
- // If this is from an IME or shelf adjustment, then we should move the PiP so that it is not
- // occluded by the IME or shelf.
- if (fromImeAdjustment || fromShelfAdjustment) {
- if (mTouchState.isUserInteracting()) {
- // Defer the update of the current movement bounds until after the user finishes
- // touching the screen
- } else {
- final boolean isExpanded = mMenuState == MENU_STATE_FULL && willResizeMenu();
- final Rect toMovementBounds = new Rect();
- mPipBoundsHandler.getSnapAlgorithm().getMovementBounds(curBounds, insetBounds,
- toMovementBounds, mIsImeShowing ? mImeHeight : 0);
- final int prevBottom = mMovementBounds.bottom - mMovementBoundsExtraOffsets;
- // This is to handle landscape fullscreen IMEs, don't apply the extra offset in this
- // case
- final int toBottom = toMovementBounds.bottom < toMovementBounds.top
- ? toMovementBounds.bottom
- : toMovementBounds.bottom - extraOffset;
-
- if (isExpanded) {
- curBounds.set(mExpandedBounds);
- mPipBoundsHandler.getSnapAlgorithm().applySnapFraction(curBounds,
- toMovementBounds, mSavedSnapFraction);
- }
-
- if (prevBottom < toBottom) {
- // The movement bounds are expanding
- if (curBounds.top > prevBottom - mBottomOffsetBufferPx) {
- mMotionHelper.animateToOffset(curBounds, toBottom - curBounds.top);
- }
- } else if (prevBottom > toBottom) {
- // The movement bounds are shrinking
- if (curBounds.top > toBottom - mBottomOffsetBufferPx) {
- mMotionHelper.animateToOffset(curBounds, toBottom - curBounds.top);
- }
- }
- }
- }
-
- // Update the movement bounds after doing the calculations based on the old movement bounds
- // above
- mNormalMovementBounds.set(normalMovementBounds);
- mExpandedMovementBounds.set(expandedMovementBounds);
- mDisplayRotation = displayRotation;
- mInsetBounds.set(insetBounds);
- updateMovementBounds();
- mMovementBoundsExtraOffsets = extraOffset;
- mConnection.onMovementBoundsChanged(mNormalBounds, mExpandedBounds, mNormalMovementBounds,
- mExpandedMovementBounds);
-
- // If we have a deferred resize, apply it now
- if (mDeferResizeToNormalBoundsUntilRotation == displayRotation) {
- mMotionHelper.animateToUnexpandedState(normalBounds, mSavedSnapFraction,
- mNormalMovementBounds, mMovementBounds, true /* immediate */);
- mSavedSnapFraction = -1f;
- mDeferResizeToNormalBoundsUntilRotation = -1;
- }
- }
-
- /** Adds the magnetic target view to the WindowManager so it's ready to be animated in. */
- private void createOrUpdateDismissTarget() {
- if (!mTargetViewContainer.isAttachedToWindow()) {
- mHandler.removeCallbacks(mShowTargetAction);
- mMagneticTargetAnimator.cancel();
-
- mTargetViewContainer.setVisibility(View.INVISIBLE);
-
- 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() {
- createOrUpdateDismissTarget();
-
- if (mTargetViewContainer.getVisibility() != View.VISIBLE) {
-
- mTargetView.setTranslationY(mTargetViewContainer.getHeight());
- mTargetViewContainer.setVisibility(View.VISIBLE);
-
- // Cancel in case we were in the middle of animating it out.
- mMagneticTargetAnimator.cancel();
- mMagneticTargetAnimator
- .spring(DynamicAnimation.TRANSLATION_Y, 0f, mTargetSpringConfig)
- .start();
-
- ((TransitionDrawable) mTargetViewContainer.getBackground()).startTransition(
- DISMISS_TRANSITION_DURATION_MS);
- }
- }
-
- /** Animates the magnetic dismiss target out and then sets it to GONE. */
- private void hideDismissTarget() {
- mHandler.removeCallbacks(mShowTargetAction);
- mMagneticTargetAnimator
- .spring(DynamicAnimation.TRANSLATION_Y,
- mTargetViewContainer.getHeight(),
- mTargetSpringConfig)
- .withEndActions(() -> mTargetViewContainer.setVisibility(View.GONE))
- .start();
-
- ((TransitionDrawable) mTargetViewContainer.getBackground()).reverseTransition(
- DISMISS_TRANSITION_DURATION_MS);
- }
-
- /**
- * Removes the dismiss target and cancels any pending callbacks to show it.
- */
- private void cleanUpDismissTarget() {
- mHandler.removeCallbacks(mShowTargetAction);
-
- if (mTargetViewContainer.isAttachedToWindow()) {
- mWindowManager.removeViewImmediate(mTargetViewContainer);
- }
- }
-
- private void onRegistrationChanged(boolean isRegistered) {
- mAccessibilityManager.setPictureInPictureActionReplacingConnection(isRegistered
- ? mConnection : null);
- if (!isRegistered && mTouchState.isUserInteracting()) {
- // If the input consumer is unregistered while the user is interacting, then we may not
- // get the final TOUCH_UP event, so clean up the dismiss target as well
- cleanUpDismissTarget();
- }
- }
-
- private void onAccessibilityShowMenu() {
- mMenuController.showMenu(MENU_STATE_FULL, mMotionHelper.getBounds(),
- true /* allowMenuTimeout */, willResizeMenu(),
- shouldShowResizeHandle());
- }
-
- private boolean handleTouchEvent(InputEvent inputEvent) {
- // Skip any non motion events
- if (!(inputEvent instanceof MotionEvent)) {
- return true;
- }
- // Skip touch handling until we are bound to the controller
- if (mPinnedStackController == null) {
- return true;
- }
-
- MotionEvent ev = (MotionEvent) inputEvent;
- if (mPipResizeGestureHandler.willStartResizeGesture(ev)) {
- // Initialize the touch state for the gesture, but immediately reset to invalidate the
- // gesture
- mTouchState.onTouchEvent(ev);
- mTouchState.reset();
- return true;
- }
-
- if ((ev.getAction() == MotionEvent.ACTION_DOWN || mTouchState.isUserInteracting())
- && mMagnetizedPip.maybeConsumeMotionEvent(ev)) {
- // If the first touch event occurs within the magnetic field, pass the ACTION_DOWN event
- // to the touch state. Touch state needs a DOWN event in order to later process MOVE
- // events it'll receive if the object is dragged out of the magnetic field.
- if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- mTouchState.onTouchEvent(ev);
- }
-
- // Continue tracking velocity when the object is in the magnetic field, since we want to
- // respect touch input velocity if the object is dragged out and then flung.
- mTouchState.addMovementToVelocityTracker(ev);
-
- return true;
- }
-
- // Update the touch state
- mTouchState.onTouchEvent(ev);
-
- boolean shouldDeliverToMenu = mMenuState != MENU_STATE_NONE;
-
- switch (ev.getAction()) {
- case MotionEvent.ACTION_DOWN: {
- mGesture.onDown(mTouchState);
- break;
- }
- case MotionEvent.ACTION_MOVE: {
- if (mGesture.onMove(mTouchState)) {
- break;
- }
-
- shouldDeliverToMenu = !mTouchState.isDragging();
- break;
- }
- case MotionEvent.ACTION_UP: {
- // Update the movement bounds again if the state has changed since the user started
- // dragging (ie. when the IME shows)
- updateMovementBounds();
-
- if (mGesture.onUp(mTouchState)) {
- break;
- }
-
- // Fall through to clean up
- }
- case MotionEvent.ACTION_CANCEL: {
- shouldDeliverToMenu = !mTouchState.startedDragging() && !mTouchState.isDragging();
- mTouchState.reset();
- break;
- }
- case MotionEvent.ACTION_HOVER_ENTER:
- // If Touch Exploration is enabled, some a11y services (e.g. Talkback) is probably
- // on and changing MotionEvents into HoverEvents.
- // Let's not enable menu show/hide for a11y services.
- if (!mAccessibilityManager.isTouchExplorationEnabled()) {
- mTouchState.removeHoverExitTimeoutCallback();
- mMenuController.showMenu(MENU_STATE_FULL, mMotionHelper.getBounds(),
- false /* allowMenuTimeout */, false /* willResizeMenu */,
- shouldShowResizeHandle());
- }
- case MotionEvent.ACTION_HOVER_MOVE: {
- if (!shouldDeliverToMenu && !mSendingHoverAccessibilityEvents) {
- sendAccessibilityHoverEvent(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
- mSendingHoverAccessibilityEvents = true;
- }
- break;
- }
- case MotionEvent.ACTION_HOVER_EXIT: {
- // If Touch Exploration is enabled, some a11y services (e.g. Talkback) is probably
- // on and changing MotionEvents into HoverEvents.
- // Let's not enable menu show/hide for a11y services.
- if (!mAccessibilityManager.isTouchExplorationEnabled()) {
- mTouchState.scheduleHoverExitTimeoutCallback();
- }
- if (!shouldDeliverToMenu && mSendingHoverAccessibilityEvents) {
- sendAccessibilityHoverEvent(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
- mSendingHoverAccessibilityEvents = false;
- }
- break;
- }
- }
-
- // Deliver the event to PipMenuActivity to handle button click if the menu has shown.
- if (shouldDeliverToMenu) {
- final MotionEvent cloneEvent = MotionEvent.obtain(ev);
- // Send the cancel event and cancel menu timeout if it starts to drag.
- if (mTouchState.startedDragging()) {
- cloneEvent.setAction(MotionEvent.ACTION_CANCEL);
- mMenuController.pokeMenu();
- }
-
- mMenuController.handlePointerEvent(cloneEvent);
- }
-
- return true;
- }
-
- private void sendAccessibilityHoverEvent(int type) {
- if (!mAccessibilityManager.isEnabled()) {
- return;
- }
-
- AccessibilityEvent event = AccessibilityEvent.obtain(type);
- event.setImportantForAccessibility(true);
- event.setSourceNodeId(AccessibilityNodeInfo.ROOT_NODE_ID);
- event.setWindowId(
- AccessibilityWindowInfo.PICTURE_IN_PICTURE_ACTION_REPLACER_WINDOW_ID);
- mAccessibilityManager.sendAccessibilityEvent(event);
- }
-
- /**
- * Updates the appearance of the menu and scrim on top of the PiP while dismissing.
- */
- private void updateDismissFraction() {
- if (mMenuController != null) {
- Rect bounds = mMotionHelper.getBounds();
- final float target = mInsetBounds.bottom;
- float fraction = 0f;
- if (bounds.bottom > target) {
- final float distance = bounds.bottom - target;
- fraction = Math.min(distance / bounds.height(), 1f);
- }
- if (Float.compare(fraction, 0f) != 0 || mMenuController.isMenuVisible()) {
- // Update if the fraction > 0, or if fraction == 0 and the menu was already visible
- mMenuController.setDismissFraction(fraction);
- }
- }
- }
-
- /**
- * Sets the controller to update the system of changes from user interaction.
- */
- void setPinnedStackController(IPinnedStackController controller) {
- mPinnedStackController = controller;
- }
-
- /**
- * Sets the menu visibility.
- */
- private void setMenuState(int menuState, boolean resize, Runnable callback) {
- if (mMenuState == menuState && !resize) {
- return;
- }
-
- if (menuState == MENU_STATE_FULL && mMenuState != MENU_STATE_FULL) {
- // Save the current snap fraction and if we do not drag or move the PiP, then
- // we store back to this snap fraction. Otherwise, we'll reset the snap
- // fraction and snap to the closest edge.
- if (resize) {
- Rect expandedBounds = new Rect(mExpandedBounds);
- mSavedSnapFraction = mMotionHelper.animateToExpandedState(expandedBounds,
- mMovementBounds, mExpandedMovementBounds, callback);
- }
- } else if (menuState == MENU_STATE_NONE && mMenuState == MENU_STATE_FULL) {
- // Try and restore the PiP to the closest edge, using the saved snap fraction
- // if possible
- if (resize) {
- if (mDeferResizeToNormalBoundsUntilRotation == -1) {
- // This is a very special case: when the menu is expanded and visible,
- // navigating to another activity can trigger auto-enter PiP, and if the
- // revealed activity has a forced rotation set, then the controller will get
- // updated with the new rotation of the display. However, at the same time,
- // SystemUI will try to hide the menu by creating an animation to the normal
- // bounds which are now stale. In such a case we defer the animation to the
- // normal bounds until after the next onMovementBoundsChanged() call to get the
- // bounds in the new orientation
- try {
- int displayRotation = mPinnedStackController.getDisplayRotation();
- if (mDisplayRotation != displayRotation) {
- mDeferResizeToNormalBoundsUntilRotation = displayRotation;
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Could not get display rotation from controller");
- }
- }
-
- if (mDeferResizeToNormalBoundsUntilRotation == -1) {
- Rect restoreBounds = new Rect(getUserResizeBounds());
- Rect restoredMovementBounds = new Rect();
- mPipBoundsHandler.getSnapAlgorithm().getMovementBounds(restoreBounds,
- mInsetBounds, restoredMovementBounds, mIsImeShowing ? mImeHeight : 0);
- mMotionHelper.animateToUnexpandedState(restoreBounds, mSavedSnapFraction,
- restoredMovementBounds, mMovementBounds, false /* immediate */);
- mSavedSnapFraction = -1f;
- }
- } else {
- mSavedSnapFraction = -1f;
- }
- }
- mMenuState = menuState;
- updateMovementBounds();
- // If pip menu has dismissed, we should register the A11y ActionReplacingConnection for pip
- // as well, or it can't handle a11y focus and pip menu can't perform any action.
- onRegistrationChanged(menuState == MENU_STATE_NONE);
- if (menuState == MENU_STATE_NONE) {
- mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_HIDE_MENU);
- } else if (menuState == MENU_STATE_FULL) {
- mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_SHOW_MENU);
- }
- }
-
- /**
- * @return the motion helper.
- */
- public PipMotionHelper getMotionHelper() {
- return mMotionHelper;
- }
-
- @VisibleForTesting
- PipResizeGestureHandler getPipResizeGestureHandler() {
- return mPipResizeGestureHandler;
- }
-
- @VisibleForTesting
- void setPipResizeGestureHandler(PipResizeGestureHandler pipResizeGestureHandler) {
- mPipResizeGestureHandler = pipResizeGestureHandler;
- }
-
- @VisibleForTesting
- void setPipMotionHelper(PipMotionHelper pipMotionHelper) {
- mMotionHelper = pipMotionHelper;
- }
-
- /**
- * @return the unexpanded bounds.
- */
- public Rect getNormalBounds() {
- return mNormalBounds;
- }
-
- Rect getUserResizeBounds() {
- return mPipResizeGestureHandler.getUserResizeBounds();
- }
-
- /**
- * Gesture controlling normal movement of the PIP.
- */
- private class DefaultPipTouchGesture extends PipTouchGesture {
- private final Point mStartPosition = new Point();
- private final PointF mDelta = new PointF();
- private boolean mShouldHideMenuAfterFling;
-
- @Override
- public void onDown(PipTouchState touchState) {
- if (!touchState.isUserInteracting()) {
- return;
- }
-
- Rect bounds = mMotionHelper.getPossiblyAnimatingBounds();
- mDelta.set(0f, 0f);
- mStartPosition.set(bounds.left, bounds.top);
- mMovementWithinDismiss = touchState.getDownTouchPosition().y >= mMovementBounds.bottom;
- mMotionHelper.setSpringingToTouch(false);
-
- // If the menu is still visible then just poke the menu
- // so that it will timeout after the user stops touching it
- if (mMenuState != MENU_STATE_NONE) {
- mMenuController.pokeMenu();
- }
- }
-
- @Override
- public boolean onMove(PipTouchState touchState) {
- if (!touchState.isUserInteracting()) {
- return false;
- }
-
- if (touchState.startedDragging()) {
- mSavedSnapFraction = -1f;
-
- if (mEnableDismissDragToEdge) {
- if (mTargetViewContainer.getVisibility() != View.VISIBLE) {
- mHandler.removeCallbacks(mShowTargetAction);
- showDismissTargetMaybe();
- }
- }
- }
-
- if (touchState.isDragging()) {
- // Move the pinned stack freely
- final PointF lastDelta = touchState.getLastTouchDelta();
- float lastX = mStartPosition.x + mDelta.x;
- float lastY = mStartPosition.y + mDelta.y;
- float left = lastX + lastDelta.x;
- float top = lastY + lastDelta.y;
-
- // Add to the cumulative delta after bounding the position
- mDelta.x += left - lastX;
- mDelta.y += top - lastY;
-
- mTmpBounds.set(mMotionHelper.getPossiblyAnimatingBounds());
- mTmpBounds.offsetTo((int) left, (int) top);
- mMotionHelper.movePip(mTmpBounds, true /* isDragging */);
-
- final PointF curPos = touchState.getLastTouchPosition();
- if (mMovementWithinDismiss) {
- // Track if movement remains near the bottom edge to identify swipe to dismiss
- mMovementWithinDismiss = curPos.y >= mMovementBounds.bottom;
- }
- return true;
- }
- return false;
- }
-
- @Override
- public boolean onUp(PipTouchState touchState) {
- if (mEnableDismissDragToEdge) {
- hideDismissTarget();
- }
-
- if (!touchState.isUserInteracting()) {
- return false;
- }
-
- final PointF vel = touchState.getVelocity();
-
- if (touchState.isDragging()) {
- if (mMenuState != MENU_STATE_NONE) {
- // If the menu is still visible, then just poke the menu so that
- // it will timeout after the user stops touching it
- mMenuController.showMenu(mMenuState, mMotionHelper.getBounds(),
- true /* allowMenuTimeout */, willResizeMenu(),
- shouldShowResizeHandle());
- }
- mShouldHideMenuAfterFling = mMenuState == MENU_STATE_NONE;
-
- // Reset the touch state on up before the fling settles
- mTouchState.reset();
- final Rect animatingBounds = mMotionHelper.getPossiblyAnimatingBounds();
- // If User releases the PIP window while it's out of the display bounds, put
- // PIP into stashed mode.
- if (mEnableStash
- && (animatingBounds.right > mPipBoundsHandler.getDisplayBounds().right
- || animatingBounds.left < mPipBoundsHandler.getDisplayBounds().left)) {
- mMotionHelper.stashToEdge(vel.x, vel.y,
- PipTouchHandler.this::updateDismissFraction /* updateAction */,
- this::flingEndAction /* endAction */);
- } else {
- mMotionHelper.flingToSnapTarget(vel.x, vel.y,
- PipTouchHandler.this::updateDismissFraction /* updateAction */,
- this::flingEndAction /* endAction */);
- }
- } else if (mTouchState.isDoubleTap()) {
- // Expand to fullscreen if this is a double tap
- // the PiP should be frozen until the transition ends
- setTouchEnabled(false);
- mMotionHelper.expandLeavePip();
- } else if (mMenuState != MENU_STATE_FULL) {
- if (!mTouchState.isWaitingForDoubleTap()) {
- // User has stalled long enough for this not to be a drag or a double tap, just
- // expand the menu
- mMenuController.showMenu(MENU_STATE_FULL, mMotionHelper.getBounds(),
- true /* allowMenuTimeout */, willResizeMenu(),
- shouldShowResizeHandle());
- } else {
- // Next touch event _may_ be the second tap for the double-tap, schedule a
- // fallback runnable to trigger the menu if no touch event occurs before the
- // next tap
- mTouchState.scheduleDoubleTapTimeoutCallback();
- }
- }
- return true;
- }
-
- private void flingEndAction() {
- if (mShouldHideMenuAfterFling) {
- // If the menu is not visible, then we can still be showing the activity for the
- // dismiss overlay, so just finish it after the animation completes
- mMenuController.hideMenu();
- }
- }
- }
-
- /**
- * Updates the current movement bounds based on whether the menu is currently visible and
- * resized.
- */
- private void updateMovementBounds() {
- mPipBoundsHandler.getSnapAlgorithm().getMovementBounds(mMotionHelper.getBounds(),
- mInsetBounds, mMovementBounds, mIsImeShowing ? mImeHeight : 0);
- mMotionHelper.setCurrentMovementBounds(mMovementBounds);
-
- boolean isMenuExpanded = mMenuState == MENU_STATE_FULL;
- mPipBoundsHandler.setMinEdgeSize(
- isMenuExpanded && willResizeMenu() ? mExpandedShortestEdgeSize : 0);
- }
-
- private Rect getMovementBounds(Rect curBounds) {
- Rect movementBounds = new Rect();
- mPipBoundsHandler.getSnapAlgorithm().getMovementBounds(curBounds, mInsetBounds,
- movementBounds, mIsImeShowing ? mImeHeight : 0);
- return movementBounds;
- }
-
- /**
- * @return whether the menu will resize as a part of showing the full menu.
- */
- private boolean willResizeMenu() {
- if (!mEnableResize) {
- return false;
- }
- return mExpandedBounds.width() != mNormalBounds.width()
- || mExpandedBounds.height() != mNormalBounds.height();
- }
-
- public void dump(PrintWriter pw, String prefix) {
- final String innerPrefix = prefix + " ";
- pw.println(prefix + TAG);
- pw.println(innerPrefix + "mMovementBounds=" + mMovementBounds);
- pw.println(innerPrefix + "mNormalBounds=" + mNormalBounds);
- pw.println(innerPrefix + "mNormalMovementBounds=" + mNormalMovementBounds);
- pw.println(innerPrefix + "mExpandedBounds=" + mExpandedBounds);
- pw.println(innerPrefix + "mExpandedMovementBounds=" + mExpandedMovementBounds);
- pw.println(innerPrefix + "mMenuState=" + mMenuState);
- pw.println(innerPrefix + "mIsImeShowing=" + mIsImeShowing);
- pw.println(innerPrefix + "mImeHeight=" + mImeHeight);
- pw.println(innerPrefix + "mIsShelfShowing=" + mIsShelfShowing);
- pw.println(innerPrefix + "mShelfHeight=" + mShelfHeight);
- pw.println(innerPrefix + "mSavedSnapFraction=" + mSavedSnapFraction);
- pw.println(innerPrefix + "mEnableDragToEdgeDismiss=" + mEnableDismissDragToEdge);
- pw.println(innerPrefix + "mMovementBoundsExtraOffsets=" + mMovementBoundsExtraOffsets);
- mPipBoundsHandler.dump(pw, innerPrefix);
- mTouchState.dump(pw, innerPrefix);
- mMotionHelper.dump(pw, innerPrefix);
- if (mPipResizeGestureHandler != null) {
- mPipResizeGestureHandler.dump(pw, innerPrefix);
- }
- }
-
-}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
deleted file mode 100644
index ecd1128a5680..000000000000
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
+++ /dev/null
@@ -1,389 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.pip.phone;
-
-import android.graphics.PointF;
-import android.os.Handler;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.ViewConfiguration;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.io.PrintWriter;
-
-/**
- * This keeps track of the touch state throughout the current touch gesture.
- */
-public class PipTouchState {
- private static final String TAG = "PipTouchState";
- private static final boolean DEBUG = false;
-
- @VisibleForTesting
- static final long DOUBLE_TAP_TIMEOUT = 200;
- static final long HOVER_EXIT_TIMEOUT = 50;
-
- private final Handler mHandler;
- private final ViewConfiguration mViewConfig;
- private final Runnable mDoubleTapTimeoutCallback;
- private final Runnable mHoverExitTimeoutCallback;
-
- private VelocityTracker mVelocityTracker;
- private long mDownTouchTime = 0;
- private long mLastDownTouchTime = 0;
- private long mUpTouchTime = 0;
- private final PointF mDownTouch = new PointF();
- private final PointF mDownDelta = new PointF();
- private final PointF mLastTouch = new PointF();
- private final PointF mLastDelta = new PointF();
- private final PointF mVelocity = new PointF();
- private boolean mAllowTouches = true;
- private boolean mIsUserInteracting = false;
- // Set to true only if the multiple taps occur within the double tap timeout
- private boolean mIsDoubleTap = false;
- // Set to true only if a gesture
- private boolean mIsWaitingForDoubleTap = false;
- private boolean mIsDragging = false;
- // The previous gesture was a drag
- private boolean mPreviouslyDragging = false;
- private boolean mStartedDragging = false;
- private boolean mAllowDraggingOffscreen = false;
- private int mActivePointerId;
-
- public PipTouchState(ViewConfiguration viewConfig, Handler handler,
- Runnable doubleTapTimeoutCallback, Runnable hoverExitTimeoutCallback) {
- mViewConfig = viewConfig;
- mHandler = handler;
- mDoubleTapTimeoutCallback = doubleTapTimeoutCallback;
- mHoverExitTimeoutCallback = hoverExitTimeoutCallback;
- }
-
- /**
- * Resets this state.
- */
- public void reset() {
- mAllowDraggingOffscreen = false;
- mIsDragging = false;
- mStartedDragging = false;
- mIsUserInteracting = false;
- }
-
- /**
- * Processes a given touch event and updates the state.
- */
- public void onTouchEvent(MotionEvent ev) {
- switch (ev.getActionMasked()) {
- case MotionEvent.ACTION_DOWN: {
- if (!mAllowTouches) {
- return;
- }
-
- // Initialize the velocity tracker
- initOrResetVelocityTracker();
- addMovementToVelocityTracker(ev);
-
- mActivePointerId = ev.getPointerId(0);
- if (DEBUG) {
- Log.e(TAG, "Setting active pointer id on DOWN: " + mActivePointerId);
- }
- mLastTouch.set(ev.getRawX(), ev.getRawY());
- mDownTouch.set(mLastTouch);
- mAllowDraggingOffscreen = true;
- mIsUserInteracting = true;
- mDownTouchTime = ev.getEventTime();
- mIsDoubleTap = !mPreviouslyDragging &&
- (mDownTouchTime - mLastDownTouchTime) < DOUBLE_TAP_TIMEOUT;
- mIsWaitingForDoubleTap = false;
- mIsDragging = false;
- mLastDownTouchTime = mDownTouchTime;
- if (mDoubleTapTimeoutCallback != null) {
- mHandler.removeCallbacks(mDoubleTapTimeoutCallback);
- }
- break;
- }
- case MotionEvent.ACTION_MOVE: {
- // Skip event if we did not start processing this touch gesture
- if (!mIsUserInteracting) {
- break;
- }
-
- // Update the velocity tracker
- addMovementToVelocityTracker(ev);
- int pointerIndex = ev.findPointerIndex(mActivePointerId);
- if (pointerIndex == -1) {
- Log.e(TAG, "Invalid active pointer id on MOVE: " + mActivePointerId);
- break;
- }
-
- float x = ev.getRawX(pointerIndex);
- float y = ev.getRawY(pointerIndex);
- mLastDelta.set(x - mLastTouch.x, y - mLastTouch.y);
- mDownDelta.set(x - mDownTouch.x, y - mDownTouch.y);
-
- boolean hasMovedBeyondTap = mDownDelta.length() > mViewConfig.getScaledTouchSlop();
- if (!mIsDragging) {
- if (hasMovedBeyondTap) {
- mIsDragging = true;
- mStartedDragging = true;
- }
- } else {
- mStartedDragging = false;
- }
- mLastTouch.set(x, y);
- break;
- }
- case MotionEvent.ACTION_POINTER_UP: {
- // Skip event if we did not start processing this touch gesture
- if (!mIsUserInteracting) {
- break;
- }
-
- // Update the velocity tracker
- addMovementToVelocityTracker(ev);
-
- int pointerIndex = ev.getActionIndex();
- int pointerId = ev.getPointerId(pointerIndex);
- if (pointerId == mActivePointerId) {
- // Select a new active pointer id and reset the movement state
- final int newPointerIndex = (pointerIndex == 0) ? 1 : 0;
- mActivePointerId = ev.getPointerId(newPointerIndex);
- if (DEBUG) {
- Log.e(TAG, "Relinquish active pointer id on POINTER_UP: " +
- mActivePointerId);
- }
- mLastTouch.set(ev.getRawX(newPointerIndex), ev.getRawY(newPointerIndex));
- }
- break;
- }
- case MotionEvent.ACTION_UP: {
- // Skip event if we did not start processing this touch gesture
- if (!mIsUserInteracting) {
- break;
- }
-
- // Update the velocity tracker
- addMovementToVelocityTracker(ev);
- mVelocityTracker.computeCurrentVelocity(1000,
- mViewConfig.getScaledMaximumFlingVelocity());
- mVelocity.set(mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
-
- int pointerIndex = ev.findPointerIndex(mActivePointerId);
- if (pointerIndex == -1) {
- Log.e(TAG, "Invalid active pointer id on UP: " + mActivePointerId);
- break;
- }
-
- mUpTouchTime = ev.getEventTime();
- mLastTouch.set(ev.getRawX(pointerIndex), ev.getRawY(pointerIndex));
- mPreviouslyDragging = mIsDragging;
- mIsWaitingForDoubleTap = !mIsDoubleTap && !mIsDragging &&
- (mUpTouchTime - mDownTouchTime) < DOUBLE_TAP_TIMEOUT;
-
- // Fall through to clean up
- }
- case MotionEvent.ACTION_CANCEL: {
- recycleVelocityTracker();
- break;
- }
- case MotionEvent.ACTION_BUTTON_PRESS: {
- removeHoverExitTimeoutCallback();
- break;
- }
- }
- }
-
- /**
- * @return the velocity of the active touch pointer at the point it is lifted off the screen.
- */
- public PointF getVelocity() {
- return mVelocity;
- }
-
- /**
- * @return the last touch position of the active pointer.
- */
- public PointF getLastTouchPosition() {
- return mLastTouch;
- }
-
- /**
- * @return the movement delta between the last handled touch event and the previous touch
- * position.
- */
- public PointF getLastTouchDelta() {
- return mLastDelta;
- }
-
- /**
- * @return the down touch position.
- */
- public PointF getDownTouchPosition() {
- return mDownTouch;
- }
-
- /**
- * @return the movement delta between the last handled touch event and the down touch
- * position.
- */
- public PointF getDownTouchDelta() {
- return mDownDelta;
- }
-
- /**
- * @return whether the user has started dragging.
- */
- public boolean isDragging() {
- return mIsDragging;
- }
-
- /**
- * @return whether the user is currently interacting with the PiP.
- */
- public boolean isUserInteracting() {
- return mIsUserInteracting;
- }
-
- /**
- * @return whether the user has started dragging just in the last handled touch event.
- */
- public boolean startedDragging() {
- return mStartedDragging;
- }
-
- /**
- * Sets whether touching is currently allowed.
- */
- public void setAllowTouches(boolean allowTouches) {
- mAllowTouches = allowTouches;
-
- // If the user happens to touch down before this is sent from the system during a transition
- // then block any additional handling by resetting the state now
- if (mIsUserInteracting) {
- reset();
- }
- }
-
- /**
- * Disallows dragging offscreen for the duration of the current gesture.
- */
- public void setDisallowDraggingOffscreen() {
- mAllowDraggingOffscreen = false;
- }
-
- /**
- * @return whether dragging offscreen is allowed during this gesture.
- */
- public boolean allowDraggingOffscreen() {
- return mAllowDraggingOffscreen;
- }
-
- /**
- * @return whether this gesture is a double-tap.
- */
- public boolean isDoubleTap() {
- return mIsDoubleTap;
- }
-
- /**
- * @return whether this gesture will potentially lead to a following double-tap.
- */
- public boolean isWaitingForDoubleTap() {
- return mIsWaitingForDoubleTap;
- }
-
- /**
- * Schedules the callback to run if the next double tap does not occur. Only runs if
- * isWaitingForDoubleTap() is true.
- */
- public void scheduleDoubleTapTimeoutCallback() {
- if (mIsWaitingForDoubleTap) {
- long delay = getDoubleTapTimeoutCallbackDelay();
- mHandler.removeCallbacks(mDoubleTapTimeoutCallback);
- mHandler.postDelayed(mDoubleTapTimeoutCallback, delay);
- }
- }
-
- @VisibleForTesting long getDoubleTapTimeoutCallbackDelay() {
- if (mIsWaitingForDoubleTap) {
- return Math.max(0, DOUBLE_TAP_TIMEOUT - (mUpTouchTime - mDownTouchTime));
- }
- return -1;
- }
-
- /**
- * Removes the timeout callback if it's in queue.
- */
- public void removeDoubleTapTimeoutCallback() {
- mIsWaitingForDoubleTap = false;
- mHandler.removeCallbacks(mDoubleTapTimeoutCallback);
- }
-
- void scheduleHoverExitTimeoutCallback() {
- mHandler.removeCallbacks(mHoverExitTimeoutCallback);
- mHandler.postDelayed(mHoverExitTimeoutCallback, HOVER_EXIT_TIMEOUT);
- }
-
- void removeHoverExitTimeoutCallback() {
- mHandler.removeCallbacks(mHoverExitTimeoutCallback);
- }
-
- void addMovementToVelocityTracker(MotionEvent event) {
- if (mVelocityTracker == null) {
- return;
- }
-
- // Add movement to velocity tracker using raw screen X and Y coordinates instead
- // of window coordinates because the window frame may be moving at the same time.
- float deltaX = event.getRawX() - event.getX();
- float deltaY = event.getRawY() - event.getY();
- event.offsetLocation(deltaX, deltaY);
- mVelocityTracker.addMovement(event);
- event.offsetLocation(-deltaX, -deltaY);
- }
-
- private void initOrResetVelocityTracker() {
- if (mVelocityTracker == null) {
- mVelocityTracker = VelocityTracker.obtain();
- } else {
- mVelocityTracker.clear();
- }
- }
-
- private void recycleVelocityTracker() {
- if (mVelocityTracker != null) {
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- }
- }
-
- public void dump(PrintWriter pw, String prefix) {
- final String innerPrefix = prefix + " ";
- pw.println(prefix + TAG);
- pw.println(innerPrefix + "mAllowTouches=" + mAllowTouches);
- pw.println(innerPrefix + "mActivePointerId=" + mActivePointerId);
- pw.println(innerPrefix + "mDownTouch=" + mDownTouch);
- pw.println(innerPrefix + "mDownDelta=" + mDownDelta);
- pw.println(innerPrefix + "mLastTouch=" + mLastTouch);
- pw.println(innerPrefix + "mLastDelta=" + mLastDelta);
- pw.println(innerPrefix + "mVelocity=" + mVelocity);
- pw.println(innerPrefix + "mIsUserInteracting=" + mIsUserInteracting);
- pw.println(innerPrefix + "mIsDragging=" + mIsDragging);
- pw.println(innerPrefix + "mStartedDragging=" + mStartedDragging);
- pw.println(innerPrefix + "mAllowDraggingOffscreen=" + mAllowDraggingOffscreen);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipUpdateThread.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipUpdateThread.java
deleted file mode 100644
index 6c5d84645e92..000000000000
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipUpdateThread.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.pip.phone;
-
-import android.os.Handler;
-import android.os.HandlerThread;
-
-/**
- * Similar to {@link com.android.internal.os.BackgroundThread}, this is a shared singleton
- * foreground thread for each process for updating PIP.
- */
-public final class PipUpdateThread extends HandlerThread {
- private static PipUpdateThread sInstance;
- private static Handler sHandler;
-
- private PipUpdateThread() {
- super("pip");
- }
-
- private static void ensureThreadLocked() {
- if (sInstance == null) {
- sInstance = new PipUpdateThread();
- sInstance.start();
- sHandler = new Handler(sInstance.getLooper());
- }
- }
-
- /**
- * @return the static update thread instance
- */
- public static PipUpdateThread get() {
- synchronized (PipUpdateThread.class) {
- ensureThreadLocked();
- return sInstance;
- }
- }
- /**
- * @return the static update thread handler instance
- */
- public static Handler getHandler() {
- synchronized (PipUpdateThread.class) {
- ensureThreadLocked();
- return sHandler;
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipUtils.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipUtils.java
deleted file mode 100644
index baa8f118f362..000000000000
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipUtils.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.pip.phone;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-
-import android.app.ActivityTaskManager;
-import android.app.ActivityTaskManager.RootTaskInfo;
-import android.app.IActivityManager;
-import android.content.ComponentName;
-import android.content.Context;
-import android.os.RemoteException;
-import android.util.Log;
-import android.util.Pair;
-
-public class PipUtils {
-
- private static final String TAG = "PipUtils";
-
- /**
- * @return the ComponentName and user id of the top non-SystemUI activity in the pinned stack.
- * The component name may be null if no such activity exists.
- */
- public static Pair<ComponentName, Integer> getTopPipActivity(Context context,
- IActivityManager activityManager) {
- try {
- final String sysUiPackageName = context.getPackageName();
- final RootTaskInfo pinnedTaskInfo = ActivityTaskManager.getService().getRootTaskInfo(
- WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
- if (pinnedTaskInfo != null && pinnedTaskInfo.childTaskIds != null
- && pinnedTaskInfo.childTaskIds.length > 0) {
- for (int i = pinnedTaskInfo.childTaskNames.length - 1; i >= 0; i--) {
- ComponentName cn = ComponentName.unflattenFromString(
- pinnedTaskInfo.childTaskNames[i]);
- if (cn != null && !cn.getPackageName().equals(sysUiPackageName)) {
- return new Pair<>(cn, pinnedTaskInfo.childTaskUserIds[i]);
- }
- }
- }
- } catch (RemoteException e) {
- Log.w(TAG, "Unable to get pinned stack.");
- }
- return new Pair<>(null, 0);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlButtonView.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlButtonView.java
deleted file mode 100644
index db9bedd2e620..000000000000
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlButtonView.java
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.pip.tv;
-
-import android.animation.Animator;
-import android.animation.AnimatorInflater;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.RelativeLayout;
-import android.widget.TextView;
-
-import com.android.wm.shell.R;
-
-/**
- * A view containing PIP controls including fullscreen, close, and media controls.
- */
-public class PipControlButtonView extends RelativeLayout {
-
- private OnFocusChangeListener mFocusChangeListener;
- private ImageView mIconImageView;
- ImageView mButtonImageView;
- private TextView mDescriptionTextView;
- private Animator mTextFocusGainAnimator;
- private Animator mButtonFocusGainAnimator;
- private Animator mTextFocusLossAnimator;
- private Animator mButtonFocusLossAnimator;
-
- private final OnFocusChangeListener mInternalFocusChangeListener =
- new OnFocusChangeListener() {
- @Override
- public void onFocusChange(View v, boolean hasFocus) {
- if (hasFocus) {
- startFocusGainAnimation();
- } else {
- startFocusLossAnimation();
- }
-
- if (mFocusChangeListener != null) {
- mFocusChangeListener.onFocusChange(PipControlButtonView.this, hasFocus);
- }
- }
- };
-
- public PipControlButtonView(Context context) {
- this(context, null, 0, 0);
- }
-
- public PipControlButtonView(Context context, AttributeSet attrs) {
- this(context, attrs, 0, 0);
- }
-
- public PipControlButtonView(Context context, AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
- }
-
- public PipControlButtonView(
- Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- LayoutInflater inflater = (LayoutInflater) getContext()
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- inflater.inflate(R.layout.tv_pip_control_button, this);
-
- mIconImageView = findViewById(R.id.icon);
- mButtonImageView = findViewById(R.id.button);
- mDescriptionTextView = findViewById(R.id.desc);
-
- int[] values = new int[] {android.R.attr.src, android.R.attr.text};
- TypedArray typedArray =
- context.obtainStyledAttributes(attrs, values, defStyleAttr, defStyleRes);
-
- setImageResource(typedArray.getResourceId(0, 0));
- setText(typedArray.getResourceId(1, 0));
-
- typedArray.recycle();
- }
-
- @Override
- public void onFinishInflate() {
- super.onFinishInflate();
- mButtonImageView.setOnFocusChangeListener(mInternalFocusChangeListener);
-
- mTextFocusGainAnimator = AnimatorInflater.loadAnimator(getContext(),
- R.anim.tv_pip_controls_focus_gain_animation);
- mTextFocusGainAnimator.setTarget(mDescriptionTextView);
- mButtonFocusGainAnimator = AnimatorInflater.loadAnimator(getContext(),
- R.anim.tv_pip_controls_focus_gain_animation);
- mButtonFocusGainAnimator.setTarget(mButtonImageView);
-
- mTextFocusLossAnimator = AnimatorInflater.loadAnimator(getContext(),
- R.anim.tv_pip_controls_focus_loss_animation);
- mTextFocusLossAnimator.setTarget(mDescriptionTextView);
- mButtonFocusLossAnimator = AnimatorInflater.loadAnimator(getContext(),
- R.anim.tv_pip_controls_focus_loss_animation);
- mButtonFocusLossAnimator.setTarget(mButtonImageView);
- }
-
- @Override
- public void setOnClickListener(OnClickListener listener) {
- mButtonImageView.setOnClickListener(listener);
- }
-
- @Override
- public void setOnFocusChangeListener(OnFocusChangeListener listener) {
- mFocusChangeListener = listener;
- }
-
- /**
- * Sets the drawable for the button with the given drawable.
- */
- public void setImageDrawable(Drawable d) {
- mIconImageView.setImageDrawable(d);
- }
-
- /**
- * Sets the drawable for the button with the given resource id.
- */
- public void setImageResource(int resId) {
- if (resId != 0) {
- mIconImageView.setImageResource(resId);
- }
- }
-
- /**
- * Sets the text for description the with the given string.
- */
- public void setText(CharSequence text) {
- mButtonImageView.setContentDescription(text);
- mDescriptionTextView.setText(text);
- }
-
- /**
- * Sets the text for description the with the given resource id.
- */
- public void setText(int resId) {
- if (resId != 0) {
- mButtonImageView.setContentDescription(getContext().getString(resId));
- mDescriptionTextView.setText(resId);
- }
- }
-
- private static void cancelAnimator(Animator animator) {
- if (animator.isStarted()) {
- animator.cancel();
- }
- }
-
- /**
- * Starts the focus gain animation.
- */
- public void startFocusGainAnimation() {
- cancelAnimator(mButtonFocusLossAnimator);
- cancelAnimator(mTextFocusLossAnimator);
- mTextFocusGainAnimator.start();
- if (mButtonImageView.getAlpha() < 1f) {
- // If we had faded out the ripple drawable, run our manual focus change animation.
- // See the comment at {@link #startFocusLossAnimation()} for the reason of manual
- // animator.
- mButtonFocusGainAnimator.start();
- }
- }
-
- /**
- * Starts the focus loss animation.
- */
- public void startFocusLossAnimation() {
- cancelAnimator(mButtonFocusGainAnimator);
- cancelAnimator(mTextFocusGainAnimator);
- mTextFocusLossAnimator.start();
- if (mButtonImageView.hasFocus()) {
- // Button uses ripple that has the default animation for the focus changes.
- // Howevever, it doesn't expose the API to fade out while it is focused,
- // so we should manually run the fade out animation when PIP controls row loses focus.
- mButtonFocusLossAnimator.start();
- }
- }
-
- /**
- * Resets to initial state.
- */
- public void reset() {
- cancelAnimator(mButtonFocusGainAnimator);
- cancelAnimator(mTextFocusGainAnimator);
- cancelAnimator(mButtonFocusLossAnimator);
- cancelAnimator(mTextFocusLossAnimator);
- mButtonImageView.setAlpha(1f);
- mDescriptionTextView.setAlpha(mButtonImageView.hasFocus() ? 1f : 0f);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipController.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipController.java
deleted file mode 100644
index 12a545aa4b02..000000000000
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipController.java
+++ /dev/null
@@ -1,762 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.pip.tv;
-
-import static android.app.ActivityTaskManager.INVALID_STACK_ID;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-
-import android.app.ActivityManager.RunningTaskInfo;
-import android.app.ActivityTaskManager;
-import android.app.ActivityTaskManager.RootTaskInfo;
-import android.app.IActivityTaskManager;
-import android.app.RemoteAction;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.ParceledListSlice;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.Rect;
-import android.media.session.MediaController;
-import android.media.session.MediaSessionManager;
-import android.media.session.PlaybackState;
-import android.os.Debug;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.DisplayInfo;
-
-import com.android.systemui.Dependency;
-import com.android.systemui.R;
-import com.android.systemui.UiOffloadThread;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.pip.Pip;
-import com.android.systemui.pip.PipBoundsHandler;
-import com.android.systemui.pip.PipSurfaceTransactionHelper;
-import com.android.systemui.pip.PipTaskOrganizer;
-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 java.util.ArrayList;
-import java.util.List;
-import java.util.Optional;
-
-/**
- * Manages the picture-in-picture (PIP) UI and states.
- */
-@SysUISingleton
-public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallback {
- private static final String TAG = "PipController";
- static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
- /**
- * Unknown or invalid state
- */
- public static final int STATE_UNKNOWN = -1;
- /**
- * State when there's no PIP.
- */
- public static final int STATE_NO_PIP = 0;
- /**
- * State when PIP is shown. This is used as default PIP state.
- */
- public static final int STATE_PIP = 1;
- /**
- * State when PIP menu dialog is shown.
- */
- public static final int STATE_PIP_MENU = 2;
-
- private static final int TASK_ID_NO_PIP = -1;
- private static final int INVALID_RESOURCE_TYPE = -1;
-
- public static final int SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_MENU_ACTIVITY_FINISH = 0x1;
-
- /**
- * PIPed activity is playing a media and it can be paused.
- */
- static final int PLAYBACK_STATE_PLAYING = 0;
- /**
- * PIPed activity has a paused media and it can be played.
- */
- static final int PLAYBACK_STATE_PAUSED = 1;
- /**
- * Users are unable to control PIPed activity's media playback.
- */
- static final int PLAYBACK_STATE_UNAVAILABLE = 2;
-
- private static final int CLOSE_PIP_WHEN_MEDIA_SESSION_GONE_TIMEOUT_MS = 3000;
-
- private int mSuspendPipResizingReason;
-
- private Context mContext;
- private PipBoundsHandler mPipBoundsHandler;
- private PipTaskOrganizer mPipTaskOrganizer;
- private PipSurfaceTransactionHelper mPipSurfaceTransactionHelper;
- private IActivityTaskManager mActivityTaskManager;
- private MediaSessionManager mMediaSessionManager;
- private int mState = STATE_NO_PIP;
- private int mResumeResizePinnedStackRunnableState = STATE_NO_PIP;
- private final Handler mHandler = new Handler();
- private List<Listener> mListeners = new ArrayList<>();
- private List<MediaListener> mMediaListeners = new ArrayList<>();
- private Rect mPipBounds;
- private Rect mDefaultPipBounds = new Rect();
- private Rect mMenuModePipBounds;
- private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED;
- private boolean mInitialized;
- private int mPipTaskId = TASK_ID_NO_PIP;
- private int mPinnedStackId = INVALID_STACK_ID;
- private ComponentName mPipComponentName;
- private MediaController mPipMediaController;
- private String[] mLastPackagesResourceGranted;
- private PipNotification mPipNotification;
- private ParceledListSlice<RemoteAction> mCustomActions;
- private int mResizeAnimationDuration;
-
- // Used to calculate the movement bounds
- private final DisplayInfo mTmpDisplayInfo = new DisplayInfo();
- private final Rect mTmpInsetBounds = new Rect();
-
- // Keeps track of the IME visibility to adjust the PiP when the IME is visible
- private boolean mImeVisible;
- private int mImeHeightAdjustment;
-
- private final PinnedStackListener mPinnedStackListener = new PipControllerPinnedStackListener();
-
- private final Runnable mResizePinnedStackRunnable = new Runnable() {
- @Override
- public void run() {
- resizePinnedStack(mResumeResizePinnedStackRunnableState);
- }
- };
- private final Runnable mClosePipRunnable = new Runnable() {
- @Override
- public void run() {
- closePip();
- }
- };
-
- private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (Intent.ACTION_MEDIA_RESOURCE_GRANTED.equals(action)) {
- String[] packageNames = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
- int resourceType = intent.getIntExtra(Intent.EXTRA_MEDIA_RESOURCE_TYPE,
- INVALID_RESOURCE_TYPE);
- if (packageNames != null && packageNames.length > 0
- && resourceType == Intent.EXTRA_MEDIA_RESOURCE_TYPE_VIDEO_CODEC) {
- handleMediaResourceGranted(packageNames);
- }
- }
-
- }
- };
- private final MediaSessionManager.OnActiveSessionsChangedListener mActiveMediaSessionListener =
- new MediaSessionManager.OnActiveSessionsChangedListener() {
- @Override
- public void onActiveSessionsChanged(List<MediaController> controllers) {
- updateMediaController(controllers);
- }
- };
-
- /**
- * Handler for messages from the PIP controller.
- */
- private class PipControllerPinnedStackListener extends PinnedStackListener {
- @Override
- public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
- mHandler.post(() -> {
- mPipBoundsHandler.onImeVisibilityChanged(imeVisible, imeHeight);
- if (mState == STATE_PIP) {
- if (mImeVisible != imeVisible) {
- if (imeVisible) {
- // Save the IME height adjustment, and offset to not occlude the IME
- mPipBounds.offset(0, -imeHeight);
- mImeHeightAdjustment = imeHeight;
- } else {
- // Apply the inverse adjustment when the IME is hidden
- mPipBounds.offset(0, mImeHeightAdjustment);
- }
- mImeVisible = imeVisible;
- resizePinnedStack(STATE_PIP);
- }
- }
- });
- }
-
- @Override
- public void onMovementBoundsChanged(boolean fromImeAdjustment) {
- mHandler.post(() -> {
- // Populate the inset / normal bounds and DisplayInfo from mPipBoundsHandler first.
- mPipBoundsHandler.onMovementBoundsChanged(mTmpInsetBounds, mPipBounds,
- mDefaultPipBounds, mTmpDisplayInfo);
- });
- }
-
- @Override
- public void onActionsChanged(ParceledListSlice<RemoteAction> actions) {
- mCustomActions = actions;
- mHandler.post(() -> {
- for (int i = mListeners.size() - 1; i >= 0; --i) {
- mListeners.get(i).onPipMenuActionsChanged(mCustomActions);
- }
- });
- }
- }
-
- public PipController(Context context, BroadcastDispatcher broadcastDispatcher,
- PipBoundsHandler pipBoundsHandler,
- PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
- PipTaskOrganizer pipTaskOrganizer) {
- if (mInitialized) {
- return;
- }
-
- mInitialized = true;
- mContext = context;
- mPipNotification = new PipNotification(context, broadcastDispatcher,
- Optional.of(this).get());
- mPipBoundsHandler = pipBoundsHandler;
- // Ensure that we have the display info in case we get calls to update the bounds before the
- // listener calls back
- final DisplayInfo displayInfo = new DisplayInfo();
- context.getDisplay().getDisplayInfo(displayInfo);
- mPipBoundsHandler.onDisplayInfoChanged(displayInfo);
-
- mResizeAnimationDuration = context.getResources()
- .getInteger(R.integer.config_pipResizeAnimationDuration);
- mPipSurfaceTransactionHelper = pipSurfaceTransactionHelper;
- mPipTaskOrganizer = pipTaskOrganizer;
- mPipTaskOrganizer.registerPipTransitionCallback(this);
- mActivityTaskManager = ActivityTaskManager.getService();
- ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
- IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(Intent.ACTION_MEDIA_RESOURCE_GRANTED);
- broadcastDispatcher.registerReceiver(mBroadcastReceiver, intentFilter,
- null /* handler */, UserHandle.ALL);
-
- // Initialize the last orientation and apply the current configuration
- Configuration initialConfig = mContext.getResources().getConfiguration();
- mLastOrientation = initialConfig.orientation;
- loadConfigurationsAndApply(initialConfig);
-
- mMediaSessionManager =
- (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
-
- try {
- WindowManagerWrapper.getInstance().addPinnedStackListener(mPinnedStackListener);
- } catch (RemoteException | UnsupportedOperationException e) {
- Log.e(TAG, "Failed to register pinned stack listener", e);
- }
- }
-
- private void loadConfigurationsAndApply(Configuration newConfig) {
- if (mLastOrientation != newConfig.orientation) {
- // Don't resize the pinned stack on orientation change. TV does not care about this case
- // and this could clobber the existing animation to the new bounds calculated by WM.
- mLastOrientation = newConfig.orientation;
- return;
- }
-
- Resources res = mContext.getResources();
- mMenuModePipBounds = Rect.unflattenFromString(res.getString(
- R.string.pip_menu_bounds));
-
- // Reset the PIP bounds and apply. PIP bounds can be changed by two reasons.
- // 1. Configuration changed due to the language change (RTL <-> RTL)
- // 2. SystemUI restarts after the crash
- mPipBounds = mDefaultPipBounds;
- resizePinnedStack(getPinnedTaskInfo() == null ? STATE_NO_PIP : STATE_PIP);
- }
-
- /**
- * Updates the PIP per configuration changed.
- */
- public void onConfigurationChanged(Configuration newConfig) {
- loadConfigurationsAndApply(newConfig);
- mPipNotification.onConfigurationChanged(mContext);
- }
-
- /**
- * Shows the picture-in-picture menu if an activity is in picture-in-picture mode.
- */
- public void showPictureInPictureMenu() {
- if (DEBUG) Log.d(TAG, "showPictureInPictureMenu(), current state=" + getStateDescription());
-
- if (getState() == STATE_PIP) {
- resizePinnedStack(STATE_PIP_MENU);
- }
- }
-
- /**
- * Closes PIP (PIPed activity and PIP system UI).
- */
- public void closePip() {
- if (DEBUG) Log.d(TAG, "closePip(), current state=" + getStateDescription());
-
- closePipInternal(true);
- }
-
- private void closePipInternal(boolean removePipStack) {
- if (DEBUG) {
- Log.d(TAG,
- "closePipInternal() removePipStack=" + removePipStack + ", current state="
- + getStateDescription());
- }
-
- mState = STATE_NO_PIP;
- mPipTaskId = TASK_ID_NO_PIP;
- mPipMediaController = null;
- mMediaSessionManager.removeOnActiveSessionsChangedListener(mActiveMediaSessionListener);
- if (removePipStack) {
- try {
- mActivityTaskManager.removeStack(mPinnedStackId);
- } catch (RemoteException e) {
- Log.e(TAG, "removeStack failed", e);
- } finally {
- mPinnedStackId = INVALID_STACK_ID;
- }
- }
- for (int i = mListeners.size() - 1; i >= 0; --i) {
- mListeners.get(i).onPipActivityClosed();
- }
- mHandler.removeCallbacks(mClosePipRunnable);
- updatePipVisibility(false);
- }
-
- /**
- * Moves the PIPed activity to the fullscreen and closes PIP system UI.
- */
- public void movePipToFullscreen() {
- if (DEBUG) Log.d(TAG, "movePipToFullscreen(), current state=" + getStateDescription());
-
- mPipTaskId = TASK_ID_NO_PIP;
- for (int i = mListeners.size() - 1; i >= 0; --i) {
- mListeners.get(i).onMoveToFullscreen();
- }
- resizePinnedStack(STATE_NO_PIP);
- updatePipVisibility(false);
- }
-
- /**
- * Suspends resizing operation on the Pip until {@link #resumePipResizing} is called
- *
- * @param reason The reason for suspending resizing operations on the Pip.
- */
- public void suspendPipResizing(int reason) {
- if (DEBUG) {
- Log.d(TAG,
- "suspendPipResizing() reason=" + reason + " callers=" + Debug.getCallers(2));
- }
-
- mSuspendPipResizingReason |= reason;
- }
-
- /**
- * Resumes resizing operation on the Pip that was previously suspended.
- *
- * @param reason The reason resizing operations on the Pip was suspended.
- */
- public void resumePipResizing(int reason) {
- if ((mSuspendPipResizingReason & reason) == 0) {
- return;
- }
- if (DEBUG) {
- Log.d(TAG,
- "resumePipResizing() reason=" + reason + " callers=" + Debug.getCallers(2));
- }
- mSuspendPipResizingReason &= ~reason;
- mHandler.post(mResizePinnedStackRunnable);
- }
-
- /**
- * Resize the Pip to the appropriate size for the input state.
- *
- * @param state In Pip state also used to determine the new size for the Pip.
- */
- public void resizePinnedStack(int state) {
- if (DEBUG) {
- Log.d(TAG, "resizePinnedStack() state=" + stateToName(state) + ", current state="
- + getStateDescription(), new Exception());
- }
-
- boolean wasStateNoPip = (mState == STATE_NO_PIP);
- for (int i = mListeners.size() - 1; i >= 0; --i) {
- mListeners.get(i).onPipResizeAboutToStart();
- }
- if (mSuspendPipResizingReason != 0) {
- mResumeResizePinnedStackRunnableState = state;
- if (DEBUG) {
- Log.d(TAG, "resizePinnedStack() deferring"
- + " mSuspendPipResizingReason=" + mSuspendPipResizingReason
- + " mResumeResizePinnedStackRunnableState="
- + stateToName(mResumeResizePinnedStackRunnableState));
- }
- return;
- }
- mState = state;
- final Rect newBounds;
- switch (mState) {
- case STATE_NO_PIP:
- newBounds = null;
- // If the state was already STATE_NO_PIP, then do not resize the stack below as it
- // will not exist
- if (wasStateNoPip) {
- return;
- }
- break;
- case STATE_PIP_MENU:
- newBounds = mMenuModePipBounds;
- break;
- case STATE_PIP: // fallthrough
- default:
- newBounds = mPipBounds;
- break;
- }
- if (newBounds != null) {
- mPipTaskOrganizer.scheduleAnimateResizePip(newBounds, mResizeAnimationDuration, null);
- } else {
- mPipTaskOrganizer.exitPip(mResizeAnimationDuration);
- }
- }
-
- /**
- * @return the current state, or the pending state if the state change was previously suspended.
- */
- private int getState() {
- if (mSuspendPipResizingReason != 0) {
- return mResumeResizePinnedStackRunnableState;
- }
- return mState;
- }
-
- /**
- * Shows PIP menu UI by launching {@link PipMenuActivity}. It also locates the pinned
- * stack to the centered PIP bound {@link R.config_centeredPictureInPictureBounds}.
- */
- private void showPipMenu() {
- if (DEBUG) Log.d(TAG, "showPipMenu(), current state=" + getStateDescription());
-
- mState = STATE_PIP_MENU;
- for (int i = mListeners.size() - 1; i >= 0; --i) {
- mListeners.get(i).onShowPipMenu();
- }
- Intent intent = new Intent(mContext, PipMenuActivity.class);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.putExtra(PipMenuActivity.EXTRA_CUSTOM_ACTIONS, mCustomActions);
- mContext.startActivity(intent);
- }
-
- /**
- * Adds a {@link Listener} to PipController.
- */
- public void addListener(Listener listener) {
- mListeners.add(listener);
- }
-
- /**
- * Removes a {@link Listener} from PipController.
- */
- public void removeListener(Listener listener) {
- mListeners.remove(listener);
- }
-
- /**
- * Adds a {@link MediaListener} to PipController.
- */
- public void addMediaListener(MediaListener listener) {
- mMediaListeners.add(listener);
- }
-
- /**
- * Removes a {@link MediaListener} from PipController.
- */
- public void removeMediaListener(MediaListener listener) {
- mMediaListeners.remove(listener);
- }
-
- /**
- * Returns {@code true} if PIP is shown.
- */
- public boolean isPipShown() {
- return mState != STATE_NO_PIP;
- }
-
- private RootTaskInfo getPinnedTaskInfo() {
- RootTaskInfo taskInfo = null;
- try {
- taskInfo = ActivityTaskManager.getService().getRootTaskInfo(
- WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
- } catch (RemoteException e) {
- Log.e(TAG, "getRootTaskInfo failed", e);
- }
- return taskInfo;
- }
-
- private void handleMediaResourceGranted(String[] packageNames) {
- if (getState() == STATE_NO_PIP) {
- mLastPackagesResourceGranted = packageNames;
- } else {
- boolean requestedFromLastPackages = false;
- if (mLastPackagesResourceGranted != null) {
- for (String packageName : mLastPackagesResourceGranted) {
- for (String newPackageName : packageNames) {
- if (TextUtils.equals(newPackageName, packageName)) {
- requestedFromLastPackages = true;
- break;
- }
- }
- }
- }
- mLastPackagesResourceGranted = packageNames;
- if (!requestedFromLastPackages) {
- closePip();
- }
- }
- }
-
- private void updateMediaController(List<MediaController> controllers) {
- MediaController mediaController = null;
- if (controllers != null && getState() != STATE_NO_PIP && mPipComponentName != null) {
- for (int i = controllers.size() - 1; i >= 0; i--) {
- MediaController controller = controllers.get(i);
- // We assumes that an app with PIPable activity
- // keeps the single instance of media controller especially when PIP is on.
- if (controller.getPackageName().equals(mPipComponentName.getPackageName())) {
- mediaController = controller;
- break;
- }
- }
- }
- if (mPipMediaController != mediaController) {
- mPipMediaController = mediaController;
- for (int i = mMediaListeners.size() - 1; i >= 0; i--) {
- mMediaListeners.get(i).onMediaControllerChanged();
- }
- if (mPipMediaController == null) {
- mHandler.postDelayed(mClosePipRunnable,
- CLOSE_PIP_WHEN_MEDIA_SESSION_GONE_TIMEOUT_MS);
- } else {
- mHandler.removeCallbacks(mClosePipRunnable);
- }
- }
- }
-
- /**
- * Gets the {@link android.media.session.MediaController} for the PIPed activity.
- */
- public MediaController getMediaController() {
- return mPipMediaController;
- }
-
- @Override
- public void hidePipMenu(Runnable onStartCallback, Runnable onEndCallback) {
-
- }
-
- /**
- * Returns the PIPed activity's playback state.
- * This returns one of {@link #PLAYBACK_STATE_PLAYING}, {@link #PLAYBACK_STATE_PAUSED},
- * or {@link #PLAYBACK_STATE_UNAVAILABLE}.
- */
- public int getPlaybackState() {
- if (mPipMediaController == null || mPipMediaController.getPlaybackState() == null) {
- return PLAYBACK_STATE_UNAVAILABLE;
- }
- int state = mPipMediaController.getPlaybackState().getState();
- boolean isPlaying = (state == PlaybackState.STATE_BUFFERING
- || state == PlaybackState.STATE_CONNECTING
- || state == PlaybackState.STATE_PLAYING
- || state == PlaybackState.STATE_FAST_FORWARDING
- || state == PlaybackState.STATE_REWINDING
- || state == PlaybackState.STATE_SKIPPING_TO_PREVIOUS
- || state == PlaybackState.STATE_SKIPPING_TO_NEXT);
- long actions = mPipMediaController.getPlaybackState().getActions();
- if (!isPlaying && ((actions & PlaybackState.ACTION_PLAY) != 0)) {
- return PLAYBACK_STATE_PAUSED;
- } else if (isPlaying && ((actions & PlaybackState.ACTION_PAUSE) != 0)) {
- return PLAYBACK_STATE_PLAYING;
- }
- return PLAYBACK_STATE_UNAVAILABLE;
- }
-
- private TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
- @Override
- public void onTaskStackChanged() {
- if (DEBUG) Log.d(TAG, "onTaskStackChanged()");
-
- if (getState() != STATE_NO_PIP) {
- boolean hasPip = false;
-
- RootTaskInfo taskInfo = getPinnedTaskInfo();
- if (taskInfo == null || taskInfo.childTaskIds == null) {
- Log.w(TAG, "There is nothing in pinned stack");
- closePipInternal(false);
- return;
- }
- for (int i = taskInfo.childTaskIds.length - 1; i >= 0; --i) {
- if (taskInfo.childTaskIds[i] == mPipTaskId) {
- // PIP task is still alive.
- hasPip = true;
- break;
- }
- }
- if (!hasPip) {
- // PIP task doesn't exist anymore in PINNED_STACK.
- closePipInternal(true);
- return;
- }
- }
- if (getState() == STATE_PIP) {
- if (mPipBounds != mDefaultPipBounds) {
- mPipBounds = mDefaultPipBounds;
- resizePinnedStack(STATE_PIP);
- }
- }
- }
-
- @Override
- public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
- if (DEBUG) Log.d(TAG, "onActivityPinned()");
-
- RootTaskInfo taskInfo = getPinnedTaskInfo();
- if (taskInfo == null) {
- Log.w(TAG, "Cannot find pinned stack");
- return;
- }
- if (DEBUG) Log.d(TAG, "PINNED_STACK:" + taskInfo);
- mPinnedStackId = taskInfo.taskId;
- mPipTaskId = taskInfo.childTaskIds[taskInfo.childTaskIds.length - 1];
- mPipComponentName = ComponentName.unflattenFromString(
- taskInfo.childTaskNames[taskInfo.childTaskNames.length - 1]);
- // Set state to STATE_PIP so we show it when the pinned stack animation ends.
- mState = STATE_PIP;
- mMediaSessionManager.addOnActiveSessionsChangedListener(
- mActiveMediaSessionListener, null);
- updateMediaController(mMediaSessionManager.getActiveSessions(null));
- for (int i = mListeners.size() - 1; i >= 0; i--) {
- mListeners.get(i).onPipEntered(packageName);
- }
- updatePipVisibility(true);
- }
-
- @Override
- public void onActivityRestartAttempt(RunningTaskInfo task, boolean homeTaskVisible,
- boolean clearedTask, boolean wasVisible) {
- if (task.configuration.windowConfiguration.getWindowingMode()
- != WINDOWING_MODE_PINNED) {
- return;
- }
- if (DEBUG) Log.d(TAG, "onPinnedActivityRestartAttempt()");
-
- // If PIPed activity is launched again by Launcher or intent, make it fullscreen.
- movePipToFullscreen();
- }
- };
-
- @Override
- public void onPipTransitionStarted(ComponentName activity, int direction, Rect pipBounds) {
- }
-
- @Override
- public void onPipTransitionFinished(ComponentName activity, int direction) {
- onPipTransitionFinishedOrCanceled();
- }
-
- @Override
- public void onPipTransitionCanceled(ComponentName activity, int direction) {
- onPipTransitionFinishedOrCanceled();
- }
-
- private void onPipTransitionFinishedOrCanceled() {
- if (DEBUG) Log.d(TAG, "onPipTransitionFinishedOrCanceled()");
-
- if (getState() == STATE_PIP_MENU) {
- showPipMenu();
- }
- }
-
- /**
- * A listener interface to receive notification on changes in PIP.
- */
- public interface Listener {
- /**
- * Invoked when an activity is pinned and PIP manager is set corresponding information.
- * Classes must use this instead of {@link android.app.ITaskStackListener.onActivityPinned}
- * because there's no guarantee for the PIP manager be return relavent information
- * correctly. (e.g. {@link Pip.isPipShown}).
- */
- void onPipEntered(String packageName);
- /** Invoked when a PIPed activity is closed. */
- void onPipActivityClosed();
- /** Invoked when the PIP menu gets shown. */
- void onShowPipMenu();
- /** Invoked when the PIP menu actions change. */
- void onPipMenuActionsChanged(ParceledListSlice<RemoteAction> actions);
- /** Invoked when the PIPed activity is about to return back to the fullscreen. */
- void onMoveToFullscreen();
- /** Invoked when we are above to start resizing the Pip. */
- void onPipResizeAboutToStart();
- }
-
- /**
- * A listener interface to receive change in PIP's media controller
- */
- public interface MediaListener {
- /** Invoked when the MediaController on PIPed activity is changed. */
- void onMediaControllerChanged();
- }
-
- private void updatePipVisibility(final boolean visible) {
- Dependency.get(UiOffloadThread.class).execute(() -> {
- WindowManagerWrapper.getInstance().setPipVisibility(visible);
- });
- }
-
- private String getStateDescription() {
- if (mSuspendPipResizingReason == 0) {
- return stateToName(mState);
- }
- return stateToName(mResumeResizePinnedStackRunnableState) + " (while " + stateToName(mState)
- + " is suspended)";
- }
-
- private static String stateToName(int state) {
- switch (state) {
- case STATE_NO_PIP:
- return "NO_PIP";
-
- case STATE_PIP:
- return "PIP";
-
- case STATE_PIP_MENU:
- return "PIP_MENU";
-
- default:
- return "UNKNOWN(" + state + ")";
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java
deleted file mode 100644
index 125444d2dfb5..000000000000
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.pip.tv;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.widget.LinearLayout;
-
-import com.android.wm.shell.R;
-
-
-/**
- * A view containing PIP controls including fullscreen, close, and media controls.
- */
-public class PipControlsView extends LinearLayout {
-
- public PipControlsView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public PipControlsView(Context context, AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
- }
-
- public PipControlsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- LayoutInflater layoutInflater = (LayoutInflater) getContext().getSystemService(
- Context.LAYOUT_INFLATER_SERVICE);
- layoutInflater.inflate(R.layout.tv_pip_controls, this);
- setOrientation(LinearLayout.HORIZONTAL);
- setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL);
- }
-
- PipControlButtonView getFullButtonView() {
- return findViewById(R.id.full_button);
- }
-
- PipControlButtonView getCloseButtonView() {
- return findViewById(R.id.close_button);
- }
-
- PipControlButtonView getPlayPauseButtonView() {
- return findViewById(R.id.play_pause_button);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsViewController.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsViewController.java
deleted file mode 100644
index 4ecd52f8adf5..000000000000
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsViewController.java
+++ /dev/null
@@ -1,263 +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.pip.tv;
-
-import android.app.PendingIntent;
-import android.app.RemoteAction;
-import android.graphics.Color;
-import android.media.session.MediaController;
-import android.media.session.PlaybackState;
-import android.os.Handler;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.pip.Pip;
-import com.android.wm.shell.R;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Optional;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Controller for {@link PipControlsView}.
- */
-public class PipControlsViewController {
- private static final String TAG = PipControlsViewController.class.getSimpleName();
-
- private static final float DISABLED_ACTION_ALPHA = 0.54f;
-
- private final PipControlsView mView;
- private final LayoutInflater mLayoutInflater;
- private final Handler mHandler;
- private final Optional<Pip> mPipOptional;
- private final PipControlButtonView mPlayPauseButtonView;
- private MediaController mMediaController;
- private PipControlButtonView mFocusedChild;
- private Listener mListener;
- private ArrayList<PipControlButtonView> mCustomButtonViews = new ArrayList<>();
- private List<RemoteAction> mCustomActions = new ArrayList<>();
-
- public PipControlsView getView() {
- return mView;
- }
-
- /**
- * An interface to listen user action.
- */
- public interface Listener {
- /**
- * Called when a user clicks close PIP button.
- */
- void onClosed();
- }
-
- private View.OnAttachStateChangeListener
- mOnAttachStateChangeListener =
- new View.OnAttachStateChangeListener() {
- @Override
- public void onViewAttachedToWindow(View v) {
- updateMediaController();
- mPipOptional.ifPresent(
- pip -> pip.addMediaListener(mPipMediaListener));
- }
-
- @Override
- public void onViewDetachedFromWindow(View v) {
- mPipOptional.ifPresent(
- pip -> pip.removeMediaListener(mPipMediaListener));
- }
- };
-
- private MediaController.Callback mMediaControllerCallback = new MediaController.Callback() {
- @Override
- public void onPlaybackStateChanged(PlaybackState state) {
- updateUserActions();
- }
- };
-
- private final PipController.MediaListener mPipMediaListener = this::updateMediaController;
-
- private final View.OnFocusChangeListener
- mFocusChangeListener =
- new View.OnFocusChangeListener() {
- @Override
- public void onFocusChange(View view, boolean hasFocus) {
- if (hasFocus) {
- mFocusedChild = (PipControlButtonView) view;
- } else if (mFocusedChild == view) {
- mFocusedChild = null;
- }
- }
- };
-
-
- public PipControlsViewController(PipControlsView view, Optional<Pip> pipOptional,
- LayoutInflater layoutInflater, @Main Handler handler) {
- super();
- mView = view;
- mPipOptional = pipOptional;
- mLayoutInflater = layoutInflater;
- mHandler = handler;
-
- mView.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
- if (mView.isAttachedToWindow()) {
- mOnAttachStateChangeListener.onViewAttachedToWindow(mView);
- }
-
- View fullButtonView = mView.getFullButtonView();
- fullButtonView.setOnFocusChangeListener(mFocusChangeListener);
- fullButtonView.setOnClickListener(
- v -> mPipOptional.ifPresent(pip -> pip.movePipToFullscreen())
- );
-
- View closeButtonView = mView.getCloseButtonView();
- closeButtonView.setOnFocusChangeListener(mFocusChangeListener);
- closeButtonView.setOnClickListener(v -> {
- mPipOptional.ifPresent(pip -> pip.closePip());
- if (mListener != null) {
- mListener.onClosed();
- }
- });
-
-
- mPlayPauseButtonView = mView.getPlayPauseButtonView();
- mPlayPauseButtonView.setOnFocusChangeListener(mFocusChangeListener);
- mPlayPauseButtonView.setOnClickListener(v -> {
- if (mMediaController == null || mMediaController.getPlaybackState() == null) {
- return;
- }
- mPipOptional.ifPresent(pip -> {
- if (pip.getPlaybackState() == PipController.PLAYBACK_STATE_PAUSED) {
- mMediaController.getTransportControls().play();
- } else if (pip.getPlaybackState() == PipController.PLAYBACK_STATE_PLAYING) {
- mMediaController.getTransportControls().pause();
- }
- });
-
- // View will be updated later in {@link mMediaControllerCallback}
- });
- }
-
- private void updateMediaController() {
- AtomicReference<MediaController> newController = new AtomicReference<>();
- mPipOptional.ifPresent(pip -> newController.set(pip.getMediaController()));
-
- if (newController.get() == null || mMediaController == newController.get()) {
- return;
- }
- if (mMediaController != null) {
- mMediaController.unregisterCallback(mMediaControllerCallback);
- }
- mMediaController = newController.get();
- if (mMediaController != null) {
- mMediaController.registerCallback(mMediaControllerCallback);
- }
- updateUserActions();
- }
-
- /**
- * Updates the actions for the PIP. If there are no custom actions, then the media session
- * actions are shown.
- */
- private void updateUserActions() {
- if (!mCustomActions.isEmpty()) {
- // Ensure we have as many buttons as actions
- while (mCustomButtonViews.size() < mCustomActions.size()) {
- PipControlButtonView buttonView = (PipControlButtonView) mLayoutInflater.inflate(
- R.layout.tv_pip_custom_control, mView, false);
- mView.addView(buttonView);
- mCustomButtonViews.add(buttonView);
- }
-
- // Update the visibility of all views
- for (int i = 0; i < mCustomButtonViews.size(); i++) {
- mCustomButtonViews.get(i).setVisibility(
- i < mCustomActions.size() ? View.VISIBLE : View.GONE);
- }
-
- // Update the state and visibility of the action buttons, and hide the rest
- for (int i = 0; i < mCustomActions.size(); i++) {
- final RemoteAction action = mCustomActions.get(i);
- PipControlButtonView actionView = mCustomButtonViews.get(i);
-
- // TODO: Check if the action drawable has changed before we reload it
- action.getIcon().loadDrawableAsync(mView.getContext(), d -> {
- d.setTint(Color.WHITE);
- actionView.setImageDrawable(d);
- }, mHandler);
- actionView.setText(action.getContentDescription());
- if (action.isEnabled()) {
- actionView.setOnClickListener(v -> {
- try {
- action.getActionIntent().send();
- } catch (PendingIntent.CanceledException e) {
- Log.w(TAG, "Failed to send action", e);
- }
- });
- }
- actionView.setEnabled(action.isEnabled());
- actionView.setAlpha(action.isEnabled() ? 1f : DISABLED_ACTION_ALPHA);
- }
-
- // Hide the media session buttons
- mPlayPauseButtonView.setVisibility(View.GONE);
- } else {
- AtomicInteger state = new AtomicInteger(PipController.STATE_UNKNOWN);
- mPipOptional.ifPresent(pip -> state.set(pip.getPlaybackState()));
- if (state.get() == PipController.STATE_UNKNOWN
- || state.get() == PipController.PLAYBACK_STATE_UNAVAILABLE) {
- mPlayPauseButtonView.setVisibility(View.GONE);
- } else {
- mPlayPauseButtonView.setVisibility(View.VISIBLE);
- if (state.get() == PipController.PLAYBACK_STATE_PLAYING) {
- mPlayPauseButtonView.setImageResource(R.drawable.pip_ic_pause_white);
- mPlayPauseButtonView.setText(R.string.pip_pause);
- } else {
- mPlayPauseButtonView.setImageResource(R.drawable.pip_ic_play_arrow_white);
- mPlayPauseButtonView.setText(R.string.pip_play);
- }
- }
-
- // Hide all the custom action buttons
- for (int i = 0; i < mCustomButtonViews.size(); i++) {
- mCustomButtonViews.get(i).setVisibility(View.GONE);
- }
- }
- }
-
-
- /**
- * Sets the {@link Listener} to listen user actions.
- */
- public void setListener(Listener listener) {
- mListener = listener;
- }
-
-
- /**
- * Updates the set of activity-defined actions.
- */
- public void setActions(List<? extends RemoteAction> actions) {
- mCustomActions.clear();
- mCustomActions.addAll(actions);
- updateUserActions();
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java
deleted file mode 100644
index 7e812d9ca8a1..000000000000
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.pip.tv;
-
-import android.animation.Animator;
-import android.animation.AnimatorInflater;
-import android.app.Activity;
-import android.app.RemoteAction;
-import android.content.Intent;
-import android.content.pm.ParceledListSlice;
-import android.os.Bundle;
-import android.util.Log;
-
-import com.android.systemui.pip.Pip;
-import com.android.systemui.pip.tv.dagger.TvPipComponent;
-import com.android.wm.shell.R;
-
-import java.util.Collections;
-import java.util.Optional;
-
-import javax.inject.Inject;
-
-/**
- * Activity to show the PIP menu to control PIP.
- */
-
-public class PipMenuActivity extends Activity implements PipController.Listener {
- private static final String TAG = "PipMenuActivity";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
- static final String EXTRA_CUSTOM_ACTIONS = "custom_actions";
-
- private final TvPipComponent.Builder mPipComponentBuilder;
- private TvPipComponent mTvPipComponent;
- private final Optional<Pip> mPipOptional;
-
- private Animator mFadeInAnimation;
- private Animator mFadeOutAnimation;
- private boolean mRestorePipSizeWhenClose;
- private PipControlsViewController mPipControlsViewController;
-
- @Inject
- public PipMenuActivity(TvPipComponent.Builder pipComponentBuilder,
- Optional<Pip> pipOptional) {
- super();
- mPipComponentBuilder = pipComponentBuilder;
- mPipOptional = pipOptional;
- }
-
- @Override
- protected void onCreate(Bundle bundle) {
- if (DEBUG) Log.d(TAG, "onCreate()");
-
- super.onCreate(bundle);
- mPipOptional.ifPresent(pip -> {
- if (!pip.isPipShown()) {
- finish();
- }
- });
- setContentView(R.layout.tv_pip_menu);
- mTvPipComponent = mPipComponentBuilder.pipControlsView(
- findViewById(R.id.pip_controls)).build();
- mPipControlsViewController = mTvPipComponent.getPipControlsViewController();
-
- mPipOptional.ifPresent(pip -> pip.addListener(this));
-
- mRestorePipSizeWhenClose = true;
- mFadeInAnimation = AnimatorInflater.loadAnimator(
- this, R.anim.tv_pip_menu_fade_in_animation);
- mFadeInAnimation.setTarget(mPipControlsViewController.getView());
- mFadeOutAnimation = AnimatorInflater.loadAnimator(
- this, R.anim.tv_pip_menu_fade_out_animation);
- mFadeOutAnimation.setTarget(mPipControlsViewController.getView());
-
- onPipMenuActionsChanged(getIntent().getParcelableExtra(EXTRA_CUSTOM_ACTIONS));
- }
-
- @Override
- protected void onNewIntent(Intent intent) {
- if (DEBUG) Log.d(TAG, "onNewIntent(), intent=" + intent);
- super.onNewIntent(intent);
-
- onPipMenuActionsChanged(getIntent().getParcelableExtra(EXTRA_CUSTOM_ACTIONS));
- }
-
- private void restorePipAndFinish() {
- if (DEBUG) Log.d(TAG, "restorePipAndFinish()");
-
- if (mRestorePipSizeWhenClose) {
- if (DEBUG) Log.d(TAG, " > restoring to the default position");
-
- // When PIP menu activity is closed, restore to the default position.
- mPipOptional.ifPresent(pip -> pip.resizePinnedStack(PipController.STATE_PIP));
- }
- finish();
- }
-
- @Override
- public void onResume() {
- if (DEBUG) Log.d(TAG, "onResume()");
-
- super.onResume();
- mFadeInAnimation.start();
- }
-
- @Override
- public void onPause() {
- if (DEBUG) Log.d(TAG, "onPause()");
-
- super.onPause();
- mFadeOutAnimation.start();
- restorePipAndFinish();
- }
-
- @Override
- protected void onDestroy() {
- if (DEBUG) Log.d(TAG, "onDestroy()");
-
- super.onDestroy();
- mPipOptional.ifPresent(pip -> pip.removeListener(this));
- mPipOptional.ifPresent(pip -> pip.resumePipResizing(
- PipController.SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_MENU_ACTIVITY_FINISH));
- }
-
- @Override
- public void onBackPressed() {
- if (DEBUG) Log.d(TAG, "onBackPressed()");
-
- restorePipAndFinish();
- }
-
- @Override
- public void onPipEntered(String packageName) {
- if (DEBUG) Log.d(TAG, "onPipEntered(), packageName=" + packageName);
- }
-
- @Override
- public void onPipActivityClosed() {
- if (DEBUG) Log.d(TAG, "onPipActivityClosed()");
-
- finish();
- }
-
- @Override
- public void onPipMenuActionsChanged(ParceledListSlice<RemoteAction> actions) {
- if (DEBUG) Log.d(TAG, "onPipMenuActionsChanged()");
-
- boolean hasCustomActions = actions != null && !actions.getList().isEmpty();
- mPipControlsViewController.setActions(
- hasCustomActions ? actions.getList() : Collections.emptyList());
- }
-
- @Override
- public void onShowPipMenu() {
- if (DEBUG) Log.d(TAG, "onShowPipMenu()");
- }
-
- @Override
- public void onMoveToFullscreen() {
- if (DEBUG) Log.d(TAG, "onMoveToFullscreen()");
-
- // Moving PIP to fullscreen is implemented by resizing PINNED_STACK with null bounds.
- // This conflicts with restoring PIP position, so disable it.
- mRestorePipSizeWhenClose = false;
- finish();
- }
-
- @Override
- public void onPipResizeAboutToStart() {
- if (DEBUG) Log.d(TAG, "onPipResizeAboutToStart()");
-
- finish();
- mPipOptional.ifPresent(pip -> pip.suspendPipResizing(
- PipController.SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_MENU_ACTIVITY_FINISH));
- }
-
- @Override
- public void finish() {
- if (DEBUG) Log.d(TAG, "finish()", new RuntimeException());
-
- super.finish();
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java
deleted file mode 100644
index 78569edf009d..000000000000
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java
+++ /dev/null
@@ -1,279 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.pip.tv;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.RemoteAction;
-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;
-import android.media.MediaMetadata;
-import android.media.session.MediaController;
-import android.media.session.PlaybackState;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.util.NotificationChannels;
-import com.android.wm.shell.R;
-
-/**
- * A notification that informs users that PIP is running and also provides PIP controls.
- * <p>Once it's created, it will manage the PIP notification UI by itself except for handling
- * configuration changes.
- */
-public class PipNotification {
- private static final String TAG = "PipNotification";
- private static final String NOTIFICATION_TAG = PipNotification.class.getSimpleName();
- private static final boolean DEBUG = PipController.DEBUG;
-
- private static final String ACTION_MENU = "PipNotification.menu";
- private static final String ACTION_CLOSE = "PipNotification.close";
-
- private final PackageManager mPackageManager;
-
- private final PipController mPipController;
-
- private final NotificationManager mNotificationManager;
- private final Notification.Builder mNotificationBuilder;
-
- private MediaController mMediaController;
- private String mDefaultTitle;
- private int mDefaultIconResId;
-
- /** Package name for the application that owns PiP window. */
- private String mPackageName;
- private boolean mNotified;
- private String mMediaTitle;
- private Bitmap mArt;
-
- private PipController.Listener mPipListener = new PipController.Listener() {
- @Override
- public void onPipEntered(String packageName) {
- mPackageName = packageName;
- updateMediaControllerMetadata();
- notifyPipNotification();
- }
-
- @Override
- public void onPipActivityClosed() {
- dismissPipNotification();
- mPackageName = null;
- }
-
- @Override
- public void onShowPipMenu() {
- // no-op.
- }
-
- @Override
- public void onPipMenuActionsChanged(ParceledListSlice<RemoteAction> actions) {
- // no-op.
- }
-
- @Override
- public void onMoveToFullscreen() {
- dismissPipNotification();
- mPackageName = null;
- }
-
- @Override
- public void onPipResizeAboutToStart() {
- // no-op.
- }
- };
-
- private MediaController.Callback mMediaControllerCallback = new MediaController.Callback() {
- @Override
- public void onPlaybackStateChanged(PlaybackState state) {
- if (updateMediaControllerMetadata() && mNotified) {
- // update notification
- notifyPipNotification();
- }
- }
-
- @Override
- public void onMetadataChanged(MediaMetadata metadata) {
- if (updateMediaControllerMetadata() && mNotified) {
- // update notification
- notifyPipNotification();
- }
- }
- };
-
- private final PipController.MediaListener mPipMediaListener =
- new PipController.MediaListener() {
- @Override
- public void onMediaControllerChanged() {
- MediaController newController = mPipController.getMediaController();
- if (newController == null || mMediaController == newController) {
- return;
- }
- if (mMediaController != null) {
- mMediaController.unregisterCallback(mMediaControllerCallback);
- }
- mMediaController = newController;
- if (mMediaController != null) {
- mMediaController.registerCallback(mMediaControllerCallback);
- }
- if (updateMediaControllerMetadata() && mNotified) {
- // update notification
- notifyPipNotification();
- }
- }
- };
-
- private final BroadcastReceiver mEventReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (DEBUG) {
- Log.d(TAG, "Received " + intent.getAction() + " from the notification UI");
- }
- switch (intent.getAction()) {
- case ACTION_MENU:
- mPipController.showPictureInPictureMenu();
- break;
- case ACTION_CLOSE:
- mPipController.closePip();
- break;
- }
- }
- };
-
- public PipNotification(Context context, BroadcastDispatcher broadcastDispatcher,
- PipController pipController) {
- mPackageManager = context.getPackageManager();
-
- mNotificationManager = (NotificationManager) context.getSystemService(
- Context.NOTIFICATION_SERVICE);
-
- mNotificationBuilder = new Notification.Builder(context, NotificationChannels.TVPIP)
- .setLocalOnly(true)
- .setOngoing(false)
- .setCategory(Notification.CATEGORY_SYSTEM)
- .extend(new Notification.TvExtender()
- .setContentIntent(createPendingIntent(context, ACTION_MENU))
- .setDeleteIntent(createPendingIntent(context, ACTION_CLOSE)));
-
- mPipController = pipController;
- pipController.addListener(mPipListener);
- pipController.addMediaListener(mPipMediaListener);
-
- IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(ACTION_MENU);
- intentFilter.addAction(ACTION_CLOSE);
- broadcastDispatcher.registerReceiver(mEventReceiver, intentFilter);
-
- onConfigurationChanged(context);
- }
-
- /**
- * Called by {@link PipController} when the configuration is changed.
- */
- void onConfigurationChanged(Context context) {
- Resources res = context.getResources();
- mDefaultTitle = res.getString(R.string.pip_notification_unknown_title);
- mDefaultIconResId = R.drawable.pip_icon;
- if (mNotified) {
- // update notification
- notifyPipNotification();
- }
- }
-
- private void notifyPipNotification() {
- mNotified = true;
- mNotificationBuilder
- .setShowWhen(true)
- .setWhen(System.currentTimeMillis())
- .setSmallIcon(mDefaultIconResId)
- .setContentTitle(getNotificationTitle());
- if (mArt != null) {
- mNotificationBuilder.setStyle(new Notification.BigPictureStyle()
- .bigPicture(mArt));
- } else {
- mNotificationBuilder.setStyle(null);
- }
- mNotificationManager.notify(NOTIFICATION_TAG, SystemMessage.NOTE_TV_PIP,
- mNotificationBuilder.build());
- }
-
- private void dismissPipNotification() {
- mNotified = false;
- mNotificationManager.cancel(NOTIFICATION_TAG, SystemMessage.NOTE_TV_PIP);
- }
-
- private boolean updateMediaControllerMetadata() {
- String title = null;
- Bitmap art = null;
- if (mPipController.getMediaController() != null) {
- MediaMetadata metadata = mPipController.getMediaController().getMetadata();
- if (metadata != null) {
- title = metadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE);
- if (TextUtils.isEmpty(title)) {
- title = metadata.getString(MediaMetadata.METADATA_KEY_TITLE);
- }
- art = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
- if (art == null) {
- art = metadata.getBitmap(MediaMetadata.METADATA_KEY_ART);
- }
- }
- }
- 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/pip/tv/dagger/TvPipComponent.java b/packages/SystemUI/src/com/android/systemui/pip/tv/dagger/TvPipComponent.java
deleted file mode 100644
index 8e8b7f37b8d6..000000000000
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/dagger/TvPipComponent.java
+++ /dev/null
@@ -1,61 +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.pip.tv.dagger;
-
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-
-import com.android.systemui.pip.tv.PipControlsView;
-import com.android.systemui.pip.tv.PipControlsViewController;
-import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.Retention;
-
-import javax.inject.Scope;
-
-import dagger.BindsInstance;
-import dagger.Subcomponent;
-
-/**
- * Component for injecting into Pip related classes.
- */
-@Subcomponent
-public interface TvPipComponent {
- /**
- * Builder for {@link StatusBarComponent}.
- */
- @Subcomponent.Builder
- interface Builder {
- @BindsInstance
- TvPipComponent.Builder pipControlsView(PipControlsView pipControlsView);
- TvPipComponent build();
- }
-
- /**
- * Scope annotation for singleton items within the PipComponent.
- */
- @Documented
- @Retention(RUNTIME)
- @Scope
- @interface PipScope {}
-
- /**
- * Creates a StatusBarWindowViewController.
- */
- @TvPipComponent.PipScope
- PipControlsViewController getPipControlsViewController();
-}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
index 0fbd73b615ce..f56e6cdf5cb7 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
@@ -16,25 +16,23 @@
package com.android.systemui.privacy
-import android.app.ActivityManager
import android.app.AppOpsManager
-import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
+import android.content.pm.UserInfo
import android.os.UserHandle
-import android.os.UserManager
import android.provider.DeviceConfig
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags
import com.android.systemui.Dumpable
import com.android.systemui.appops.AppOpItem
import com.android.systemui.appops.AppOpsController
-import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.DeviceConfigProxy
import com.android.systemui.util.concurrency.DelayableExecutor
import java.io.FileDescriptor
@@ -48,9 +46,8 @@ class PrivacyItemController @Inject constructor(
private val appOpsController: AppOpsController,
@Main uiExecutor: DelayableExecutor,
@Background private val bgExecutor: Executor,
- private val broadcastDispatcher: BroadcastDispatcher,
private val deviceConfigProxy: DeviceConfigProxy,
- private val userManager: UserManager,
+ private val userTracker: UserTracker,
dumpManager: DumpManager
) : Dumpable {
@@ -153,13 +150,16 @@ class PrivacyItemController @Inject constructor(
}
@VisibleForTesting
- internal var userSwitcherReceiver = Receiver()
- set(value) {
- unregisterReceiver()
- field = value
- if (listening) registerReceiver()
+ internal var userTrackerCallback = object : UserTracker.Callback {
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ update(true)
}
+ override fun onProfilesChanged(profiles: List<UserInfo>) {
+ update(true)
+ }
+ }
+
init {
deviceConfigProxy.addOnPropertiesChangedListener(
DeviceConfig.NAMESPACE_PRIVACY,
@@ -168,20 +168,18 @@ class PrivacyItemController @Inject constructor(
dumpManager.registerDumpable(TAG, this)
}
- private fun unregisterReceiver() {
- broadcastDispatcher.unregisterReceiver(userSwitcherReceiver)
+ private fun unregisterListener() {
+ userTracker.removeCallback(userTrackerCallback)
}
private fun registerReceiver() {
- broadcastDispatcher.registerReceiver(userSwitcherReceiver, intentFilter,
- null /* handler */, UserHandle.ALL)
+ userTracker.addCallback(userTrackerCallback, bgExecutor)
}
private fun update(updateUsers: Boolean) {
bgExecutor.execute {
if (updateUsers) {
- val currentUser = ActivityManager.getCurrentUser()
- currentUserIds = userManager.getProfiles(currentUser).map { it.id }
+ currentUserIds = userTracker.userProfiles.map { it.id }
}
updateListAndNotifyChanges.run()
}
@@ -206,7 +204,7 @@ class PrivacyItemController @Inject constructor(
update(true)
} else {
appOpsController.removeCallback(OPS, cb)
- unregisterReceiver()
+ unregisterListener()
// Make sure that we remove all indicators and notify listeners if we are not
// listening anymore due to indicators being disabled
update(false)
@@ -275,14 +273,6 @@ class PrivacyItemController @Inject constructor(
fun onFlagMicCameraChanged(flag: Boolean) {}
}
- internal inner class Receiver : BroadcastReceiver() {
- override fun onReceive(context: Context, intent: Intent) {
- if (intentFilter.hasAction(intent.action)) {
- update(true)
- }
- }
- }
-
private class NotifyChangesToCallback(
private val callback: Callback?,
private val list: List<PrivacyItem>
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
index 52a2cecec6b1..0053fea35262 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
@@ -2,6 +2,7 @@ package com.android.systemui.qs;
import android.content.Context;
import android.content.res.ColorStateList;
+import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.drawable.Animatable2;
import android.graphics.drawable.AnimatedVectorDrawable;
@@ -31,9 +32,6 @@ public class PageIndicator extends ViewGroup {
private static final long ANIMATION_DURATION = 250;
- // The size of a single dot in relation to the whole animation.
- private static final float SINGLE_SCALE = .4f;
-
private static final float MINOR_ALPHA = .42f;
private final ArrayList<Integer> mQueuedPositions = new ArrayList<>();
@@ -75,11 +73,10 @@ public class PageIndicator extends ViewGroup {
}
array.recycle();
- mPageIndicatorWidth =
- (int) mContext.getResources().getDimension(R.dimen.qs_page_indicator_width);
- mPageIndicatorHeight =
- (int) mContext.getResources().getDimension(R.dimen.qs_page_indicator_height);
- mPageDotWidth = (int) (mPageIndicatorWidth * SINGLE_SCALE);
+ Resources res = context.getResources();
+ mPageIndicatorWidth = res.getDimensionPixelSize(R.dimen.qs_page_indicator_width);
+ mPageIndicatorHeight = res.getDimensionPixelSize(R.dimen.qs_page_indicator_height);
+ mPageDotWidth = res.getDimensionPixelSize(R.dimen.qs_page_indicator_dot_width);
}
public void setNumPages(int numPages) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index 22c735d5fa11..04f379ef35ea 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -177,6 +177,16 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout {
}
@Override
+ public void endFakeDrag() {
+ try {
+ super.endFakeDrag();
+ } catch (NullPointerException e) {
+ // Not sure what's going on. Let's log it
+ Log.e(TAG, "endFakeDrag called without velocityTracker", e);
+ }
+ }
+
+ @Override
public void computeScroll() {
if (!mScroller.isFinished() && mScroller.computeScrollOffset()) {
if (!isFakeDragging()) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index eba4465018ab..7e2433a1fd33 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -23,6 +23,7 @@ import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Point;
import android.util.AttributeSet;
+import android.util.Pair;
import android.view.View;
import android.widget.FrameLayout;
@@ -31,7 +32,7 @@ import androidx.dynamicanimation.animation.SpringForce;
import com.android.systemui.R;
import com.android.systemui.qs.customize.QSCustomizer;
-import com.android.systemui.util.animation.PhysicsAnimator;
+import com.android.wm.shell.animation.PhysicsAnimator;
/**
* Wrapper view with background which contains {@link QSPanel} and {@link BaseStatusBarHeader}
@@ -282,7 +283,7 @@ public class QSContainerImpl extends FrameLayout {
View view = getChildAt(i);
if (view == mStatusBarBackground || view == mBackgroundGradient
|| view == mQSCustomizer) {
- // Some views are always full width
+ // Some views are always full width or have dependent padding
continue;
}
LayoutParams lp = (LayoutParams) view.getLayoutParams();
@@ -291,6 +292,9 @@ public class QSContainerImpl extends FrameLayout {
if (view == mQSPanelContainer) {
// QS panel lays out some of its content full width
mQSPanel.setContentMargins(mContentPaddingStart, mContentPaddingEnd);
+ Pair<Integer, Integer> margins = mQSPanel.getVisualSideMargins();
+ // Apply paddings based on QSPanel
+ mQSCustomizer.setContentPaddings(margins.first, margins.second);
} else if (view == mHeader) {
// The header contains the QQS panel which needs to have special padding, to
// visually align them.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
index fa3328417bd6..1e239b1e9ec9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
@@ -17,30 +17,44 @@
package com.android.systemui.qs;
import com.android.systemui.R;
+import com.android.systemui.util.ViewController;
import javax.inject.Inject;
-public class QSContainerImplController {
- private final QSContainerImpl mView;
+class QSContainerImplController extends ViewController<QSContainerImpl> {
private final QuickStatusBarHeaderController mQuickStatusBarHeaderController;
private QSContainerImplController(QSContainerImpl view,
QuickStatusBarHeaderController.Builder quickStatusBarHeaderControllerBuilder) {
- mView = view;
+ super(view);
mQuickStatusBarHeaderController = quickStatusBarHeaderControllerBuilder
.setQuickStatusBarHeader(mView.findViewById(R.id.header)).build();
}
+ @Override
+ public void init() {
+ super.init();
+ mQuickStatusBarHeaderController.init();
+ }
+
public void setListening(boolean listening) {
mQuickStatusBarHeaderController.setListening(listening);
}
- public static class Builder {
+ @Override
+ protected void onViewAttached() {
+ }
+
+ @Override
+ protected void onViewDetached() {
+ }
+
+ static class Builder {
private final QuickStatusBarHeaderController.Builder mQuickStatusBarHeaderControllerBuilder;
private QSContainerImpl mView;
@Inject
- public Builder(
+ Builder(
QuickStatusBarHeaderController.Builder quickStatusBarHeaderControllerBuilder) {
mQuickStatusBarHeaderControllerBuilder = quickStatusBarHeaderControllerBuilder;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
index 6e4ab9a3323a..84563a078447 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
@@ -20,6 +20,8 @@ import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
+import android.content.ClipData;
+import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
@@ -34,6 +36,7 @@ import android.os.Handler;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.view.View.OnClickListener;
@@ -57,6 +60,7 @@ import com.android.systemui.R;
import com.android.systemui.R.dimen;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.qs.TouchAnimator.Builder;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.MultiUserSwitch;
import com.android.systemui.statusbar.phone.SettingsButton;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -75,6 +79,7 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
private final ActivityStarter mActivityStarter;
private final UserInfoController mUserInfoController;
private final DeviceProvisionedController mDeviceProvisionedController;
+ private final UserTracker mUserTracker;
private SettingsButton mSettingsButton;
protected View mSettingsContainer;
private PageIndicator mPageIndicator;
@@ -115,11 +120,12 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
@Inject
public QSFooterImpl(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
ActivityStarter activityStarter, UserInfoController userInfoController,
- DeviceProvisionedController deviceProvisionedController) {
+ DeviceProvisionedController deviceProvisionedController, UserTracker userTracker) {
super(context, attrs);
mActivityStarter = activityStarter;
mUserInfoController = userInfoController;
mDeviceProvisionedController = deviceProvisionedController;
+ mUserTracker = userTracker;
}
@VisibleForTesting
@@ -127,7 +133,8 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
this(context, attrs,
Dependency.get(ActivityStarter.class),
Dependency.get(UserInfoController.class),
- Dependency.get(DeviceProvisionedController.class));
+ Dependency.get(DeviceProvisionedController.class),
+ Dependency.get(UserTracker.class));
}
@Override
@@ -150,6 +157,19 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
mActionsContainer = findViewById(R.id.qs_footer_actions_container);
mEditContainer = findViewById(R.id.qs_footer_actions_edit_container);
mBuildText = findViewById(R.id.build);
+ mBuildText.setOnLongClickListener(view -> {
+ CharSequence buildText = mBuildText.getText();
+ if (!TextUtils.isEmpty(buildText)) {
+ ClipboardManager service =
+ mUserTracker.getUserContext().getSystemService(ClipboardManager.class);
+ String label = mContext.getString(R.string.build_number_clip_data_label);
+ service.setPrimaryClip(ClipData.newPlainText(label, buildText));
+ Toast.makeText(mContext, R.string.build_number_copy_toast, Toast.LENGTH_SHORT)
+ .show();
+ return true;
+ }
+ return false;
+ });
// RenderThread is doing more harm than good when touching the header (to expand quick
// settings), so disable it for this view
@@ -176,6 +196,7 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
mBuildText.setSelected(true);
mShouldShowBuildText = true;
} else {
+ mBuildText.setText(null);
mShouldShowBuildText = false;
mBuildText.setSelected(false);
}
@@ -317,12 +338,14 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
mMultiUserSwitch.setClickable(mMultiUserSwitch.getVisibility() == View.VISIBLE);
mEdit.setClickable(mEdit.getVisibility() == View.VISIBLE);
mSettingsButton.setClickable(mSettingsButton.getVisibility() == View.VISIBLE);
+ mBuildText.setLongClickable(mBuildText.getVisibility() == View.VISIBLE);
}
private void updateVisibilities() {
mSettingsContainer.setVisibility(mQsDisabled ? View.GONE : View.VISIBLE);
mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility(
- TunerService.isTunerEnabled(mContext) ? View.VISIBLE : View.INVISIBLE);
+ TunerService.isTunerEnabled(mContext, mUserTracker.getUserHandle()) ? View.VISIBLE
+ : View.INVISIBLE);
final boolean isDemo = UserManager.isDeviceInDemoMode(mContext);
mMultiUserSwitch.setVisibility(showUserSwitcher() ? View.VISIBLE : View.INVISIBLE);
mEditContainer.setVisibility(isDemo || !mExpanded ? View.INVISIBLE : View.VISIBLE);
@@ -376,15 +399,16 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
: MetricsProto.MetricsEvent.ACTION_QS_COLLAPSED_SETTINGS_LAUNCH);
if (mSettingsButton.isTunerClick()) {
mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
- if (TunerService.isTunerEnabled(mContext)) {
- TunerService.showResetRequest(mContext, () -> {
- // Relaunch settings so that the tuner disappears.
- startSettingsActivity();
- });
+ if (TunerService.isTunerEnabled(mContext, mUserTracker.getUserHandle())) {
+ TunerService.showResetRequest(mContext, mUserTracker.getUserHandle(),
+ () -> {
+ // Relaunch settings so that the tuner disappears.
+ startSettingsActivity();
+ });
} else {
Toast.makeText(getContext(), R.string.tuner_toast,
Toast.LENGTH_LONG).show();
- TunerService.setTunerEnabled(mContext, true);
+ TunerService.setTunerEnabled(mContext, mUserTracker.getUserHandle(), true);
}
startSettingsActivity();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index f1bb8996e181..3a783653a2d8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -142,7 +142,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
mQSContainerImplController = mQSContainerImplControllerBuilder
.setQSContainerImpl((QSContainerImpl) view)
.build();
-
+ mQSContainerImplController.init();
mQSDetail.setQsPanel(mQSPanel, mHeader, (View) mFooter);
mQSAnimator = new QSAnimator(this, mHeader.findViewById(R.id.quick_qs_panel), mQSPanel);
@@ -367,14 +367,13 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
if (DEBUG) Log.d(TAG, "setListening " + listening);
mListening = listening;
mQSContainerImplController.setListening(listening);
- mHeader.setListening(listening);
mFooter.setListening(listening);
mQSPanel.setListening(mListening, mQsExpanded);
}
@Override
public void setHeaderListening(boolean listening) {
- mHeader.setListening(listening);
+ mQSContainerImplController.setListening(listening);
mFooter.setListening(listening);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
index 290ab8594fc0..000fd1c4bd2e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
@@ -30,6 +30,7 @@ public interface QSHost {
void openPanels();
Context getContext();
Context getUserContext();
+ int getUserId();
UiEventLogger getUiEventLogger();
Collection<QSTile> getTiles();
void addCallback(Callback callback);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 1eea4337eac1..61c6d3a629ab 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -25,12 +25,12 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.graphics.PointF;
import android.metrics.LogMaker;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
+import android.util.Pair;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
@@ -57,6 +57,7 @@ import com.android.systemui.qs.external.CustomTile;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.settings.BrightnessController;
import com.android.systemui.settings.ToggleSliderView;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.statusbar.policy.BrightnessMirrorController.BrightnessMirrorListener;
import com.android.systemui.tuner.TunerService;
@@ -114,6 +115,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
private final QSLogger mQSLogger;
protected final UiEventLogger mUiEventLogger;
protected QSTileHost mHost;
+ private final UserTracker mUserTracker;
@Nullable
protected QSSecurityFooter mSecurityFooter;
@@ -157,7 +159,8 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
BroadcastDispatcher broadcastDispatcher,
QSLogger qsLogger,
MediaHost mediaHost,
- UiEventLogger uiEventLogger
+ UiEventLogger uiEventLogger,
+ UserTracker userTracker
) {
super(context, attrs);
mUsingMediaPlayer = useQsMediaPlayer(context);
@@ -173,6 +176,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
mDumpManager = dumpManager;
mBroadcastDispatcher = broadcastDispatcher;
mUiEventLogger = uiEventLogger;
+ mUserTracker = userTracker;
setOrientation(VERTICAL);
@@ -221,7 +225,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
}
protected void addSecurityFooter() {
- mSecurityFooter = new QSSecurityFooter(this, mContext);
+ mSecurityFooter = new QSSecurityFooter(this, mContext, mUserTracker);
}
protected void addViewsAboveTiles() {
@@ -1080,6 +1084,10 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
updateTileLayoutMargins();
}
+ public Pair<Integer, Integer> getVisualSideMargins() {
+ return new Pair(mVisualMarginStart, mUsingHorizontalLayout ? 0 : mVisualMarginEnd);
+ }
+
private void updateTileLayoutMargins() {
int marginEnd = mVisualMarginEnd;
if (mUsingHorizontalLayout) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
index afc5be4e6c2f..0891972c11d2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
@@ -15,7 +15,6 @@
*/
package com.android.systemui.qs;
-import android.app.ActivityManager;
import android.app.AlertDialog;
import android.app.admin.DevicePolicyEventLogger;
import android.content.Context;
@@ -45,6 +44,7 @@ import com.android.systemui.Dependency;
import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.SecurityController;
@@ -61,8 +61,7 @@ public class QSSecurityFooter implements OnClickListener, DialogInterface.OnClic
private final SecurityController mSecurityController;
private final ActivityStarter mActivityStarter;
private final Handler mMainHandler;
-
- private final UserManager mUm;
+ private final UserTracker mUserTracker;
private AlertDialog mDialog;
private QSTileHost mHost;
@@ -73,7 +72,7 @@ public class QSSecurityFooter implements OnClickListener, DialogInterface.OnClic
private int mFooterTextId;
private int mFooterIconId;
- public QSSecurityFooter(QSPanel qsPanel, Context context) {
+ public QSSecurityFooter(QSPanel qsPanel, Context context, UserTracker userTracker) {
mRootView = LayoutInflater.from(context)
.inflate(R.layout.quick_settings_footer, qsPanel, false);
mRootView.setOnClickListener(this);
@@ -85,7 +84,7 @@ public class QSSecurityFooter implements OnClickListener, DialogInterface.OnClic
mActivityStarter = Dependency.get(ActivityStarter.class);
mSecurityController = Dependency.get(SecurityController.class);
mHandler = new H(Dependency.get(Dependency.BG_LOOPER));
- mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ mUserTracker = userTracker;
}
public void setHostEnvironment(QSTileHost host) {
@@ -138,7 +137,7 @@ public class QSSecurityFooter implements OnClickListener, DialogInterface.OnClic
private void handleRefreshState() {
final boolean isDeviceManaged = mSecurityController.isDeviceManaged();
- final UserInfo currentUser = mUm.getUserInfo(ActivityManager.getCurrentUser());
+ final UserInfo currentUser = mUserTracker.getUserInfo();
final boolean isDemoDevice = UserManager.isDeviceInDemoMode(mContext) && currentUser != null
&& currentUser.isDemo();
final boolean hasWorkProfile = mSecurityController.hasWorkProfile();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 9a63a56b2c8e..0d0d01249c3d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -14,7 +14,6 @@
package com.android.systemui.qs;
-import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -49,6 +48,7 @@ import com.android.systemui.qs.external.CustomTile;
import com.android.systemui.qs.external.TileLifecycleManager;
import com.android.systemui.qs.external.TileServices;
import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.phone.AutoTileManager;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -99,6 +99,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
private int mCurrentUser;
private final Optional<StatusBar> mStatusBarOptional;
private Context mUserContext;
+ private UserTracker mUserTracker;
@Inject
public QSTileHost(Context context,
@@ -113,7 +114,8 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
BroadcastDispatcher broadcastDispatcher,
Optional<StatusBar> statusBarOptional,
QSLogger qsLogger,
- UiEventLogger uiEventLogger) {
+ UiEventLogger uiEventLogger,
+ UserTracker userTracker) {
mIconController = iconController;
mContext = context;
mUserContext = context;
@@ -125,12 +127,13 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
mBroadcastDispatcher = broadcastDispatcher;
mInstanceIdSequence = new InstanceIdSequence(MAX_QS_INSTANCE_ID);
- mServices = new TileServices(this, bgLooper, mBroadcastDispatcher);
+ mServices = new TileServices(this, bgLooper, mBroadcastDispatcher, userTracker);
mStatusBarOptional = statusBarOptional;
mQsFactories.add(defaultFactory);
pluginManager.addPluginListener(this, QSFactory.class, true);
mDumpManager.registerDumpable(TAG, this);
+ mUserTracker = userTracker;
mainHandler.post(() -> {
// This is technically a hack to avoid circular dependency of
@@ -230,6 +233,11 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
}
@Override
+ public int getUserId() {
+ return mCurrentUser;
+ }
+
+ @Override
public TileServices getTileServices() {
return mServices;
}
@@ -248,9 +256,9 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
newValue = mContext.getResources().getString(R.string.quick_settings_tiles_retail_mode);
}
final List<String> tileSpecs = loadTileSpecs(mContext, newValue);
- int currentUser = ActivityManager.getCurrentUser();
+ int currentUser = mUserTracker.getUserId();
if (currentUser != mCurrentUser) {
- mUserContext = mContext.createContextAsUser(UserHandle.of(currentUser), 0);
+ mUserContext = mUserTracker.getUserContext();
if (mAutoTiles != null) {
mAutoTiles.changeUser(UserHandle.of(currentUser));
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index affb7b91b6a5..ea036f6fe0e5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -38,6 +38,7 @@ import com.android.systemui.plugins.qs.QSTile.SignalState;
import com.android.systemui.plugins.qs.QSTile.State;
import com.android.systemui.qs.customize.QSCustomizer;
import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
@@ -70,9 +71,11 @@ public class QuickQSPanel extends QSPanel {
BroadcastDispatcher broadcastDispatcher,
QSLogger qsLogger,
MediaHost mediaHost,
- UiEventLogger uiEventLogger
+ UiEventLogger uiEventLogger,
+ UserTracker userTracker
) {
- super(context, attrs, dumpManager, broadcastDispatcher, qsLogger, mediaHost, uiEventLogger);
+ super(context, attrs, dumpManager, broadcastDispatcher, qsLogger, mediaHost, uiEventLogger,
+ userTracker);
sDefaultMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns);
applyBottomMargin((View) mRegularTileLayout);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 2e258d56ece0..a9fbc744b38e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -17,27 +17,16 @@ package com.android.systemui.qs;
import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
-
import android.annotation.ColorInt;
-import android.app.ActivityManager;
-import android.app.AlarmManager;
+import android.app.AlarmManager.AlarmClockInfo;
import android.content.Context;
-import android.content.Intent;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Rect;
import android.media.AudioManager;
-import android.os.Handler;
-import android.os.Looper;
-import android.provider.AlarmClock;
-import android.provider.Settings;
-import android.service.notification.ZenModeConfig;
-import android.text.format.DateUtils;
import android.util.AttributeSet;
-import android.util.Log;
import android.util.MathUtils;
import android.util.Pair;
import android.view.ContextThemeWrapper;
@@ -53,90 +42,48 @@ import android.widget.Space;
import android.widget.TextView;
import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LifecycleRegistry;
-import com.android.internal.logging.UiEventLogger;
import com.android.settingslib.Utils;
import com.android.systemui.BatteryMeterView;
import com.android.systemui.DualToneHandler;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
import com.android.systemui.privacy.OngoingPrivacyChip;
-import com.android.systemui.privacy.PrivacyChipEvent;
-import com.android.systemui.privacy.PrivacyItem;
-import com.android.systemui.privacy.PrivacyItemController;
import com.android.systemui.qs.QSDetail.Callback;
-import com.android.systemui.qs.carrier.QSCarrierGroup;
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager;
import com.android.systemui.statusbar.phone.StatusBarWindowView;
-import com.android.systemui.statusbar.phone.StatusIconContainer;
import com.android.systemui.statusbar.policy.Clock;
-import com.android.systemui.statusbar.policy.DateView;
-import com.android.systemui.statusbar.policy.NextAlarmController;
-import com.android.systemui.statusbar.policy.ZenModeController;
-import com.android.systemui.util.RingerModeTracker;
-import java.util.ArrayList;
-import java.util.List;
import java.util.Locale;
import java.util.Objects;
-import javax.inject.Inject;
-import javax.inject.Named;
-
/**
* View that contains the top-most bits of the screen (primarily the status bar with date, time, and
* battery) and also contains the {@link QuickQSPanel} along with some of the panel's inner
* contents.
*/
-public class QuickStatusBarHeader extends RelativeLayout implements
- View.OnClickListener, NextAlarmController.NextAlarmChangeCallback,
- ZenModeController.Callback, LifecycleOwner {
- private static final String TAG = "QuickStatusBarHeader";
- private static final boolean DEBUG = false;
-
- /** Delay for auto fading out the long press tooltip after it's fully visible (in ms). */
- private static final long AUTO_FADE_OUT_DELAY_MS = DateUtils.SECOND_IN_MILLIS * 6;
- private static final int FADE_ANIMATION_DURATION_MS = 300;
- private static final int TOOLTIP_NOT_YET_SHOWN_COUNT = 0;
- public static final int MAX_TOOLTIP_SHOWN_COUNT = 2;
+public class QuickStatusBarHeader extends RelativeLayout implements LifecycleOwner {
- private final NextAlarmController mAlarmController;
- private final ZenModeController mZenController;
- private final StatusBarIconController mStatusBarIconController;
- private final ActivityStarter mActivityStarter;
-
- private QSPanel mQsPanel;
+ public static final int MAX_TOOLTIP_SHOWN_COUNT = 2;
private boolean mExpanded;
- private boolean mListening;
private boolean mQsDisabled;
- private QSCarrierGroup mCarrierGroup;
protected QuickQSPanel mHeaderQsPanel;
- protected QSTileHost mHost;
- private TintedIconManager mIconManager;
private TouchAnimator mStatusIconsAlphaAnimator;
private TouchAnimator mHeaderTextContainerAlphaAnimator;
private TouchAnimator mPrivacyChipAlphaAnimator;
private DualToneHandler mDualToneHandler;
- private final CommandQueue mCommandQueue;
private View mSystemIconsView;
private View mQuickQsStatusIcons;
private View mHeaderTextContainerView;
- private int mRingerMode = AudioManager.RINGER_MODE_NORMAL;
- private AlarmManager.AlarmClockInfo mNextAlarm;
-
private ImageView mNextAlarmIcon;
/** {@link TextView} containing the actual text indicating when the next alarm will go off. */
private TextView mNextAlarmTextView;
@@ -146,20 +93,13 @@ public class QuickStatusBarHeader extends RelativeLayout implements
private TextView mRingerModeTextView;
private View mRingerContainer;
private Clock mClockView;
- private DateView mDateView;
private OngoingPrivacyChip mPrivacyChip;
private Space mSpace;
private BatteryMeterView mBatteryRemainingIcon;
- private RingerModeTracker mRingerModeTracker;
- private boolean mAllIndicatorsEnabled;
- private boolean mMicCameraIndicatorsEnabled;
- private PrivacyItemController mPrivacyItemController;
- private final UiEventLogger mUiEventLogger;
// Used for RingerModeTracker
private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
- private boolean mHasTopCutout = false;
private int mStatusBarPaddingTop = 0;
private int mRoundedCornerPadding = 0;
private int mContentMarginStart;
@@ -169,56 +109,11 @@ public class QuickStatusBarHeader extends RelativeLayout implements
private int mCutOutPaddingRight;
private float mExpandedHeaderAlpha = 1.0f;
private float mKeyguardExpansionFraction;
- private boolean mPrivacyChipLogged = false;
-
- private PrivacyItemController.Callback mPICCallback = new PrivacyItemController.Callback() {
- @Override
- public void onPrivacyItemsChanged(List<PrivacyItem> privacyItems) {
- mPrivacyChip.setPrivacyList(privacyItems);
- setChipVisibility(!privacyItems.isEmpty());
- }
- @Override
- public void onFlagAllChanged(boolean flag) {
- if (mAllIndicatorsEnabled != flag) {
- mAllIndicatorsEnabled = flag;
- update();
- }
- }
-
- @Override
- public void onFlagMicCameraChanged(boolean flag) {
- if (mMicCameraIndicatorsEnabled != flag) {
- mMicCameraIndicatorsEnabled = flag;
- update();
- }
- }
-
- private void update() {
- StatusIconContainer iconContainer = requireViewById(R.id.statusIcons);
- iconContainer.setIgnoredSlots(getIgnoredIconSlots());
- setChipVisibility(!mPrivacyChip.getPrivacyList().isEmpty());
- }
- };
-
- @Inject
- public QuickStatusBarHeader(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
- NextAlarmController nextAlarmController, ZenModeController zenModeController,
- StatusBarIconController statusBarIconController,
- ActivityStarter activityStarter, PrivacyItemController privacyItemController,
- CommandQueue commandQueue, RingerModeTracker ringerModeTracker,
- UiEventLogger uiEventLogger) {
+ public QuickStatusBarHeader(Context context, AttributeSet attrs) {
super(context, attrs);
- mAlarmController = nextAlarmController;
- mZenController = zenModeController;
- mStatusBarIconController = statusBarIconController;
- mActivityStarter = activityStarter;
- mPrivacyItemController = privacyItemController;
mDualToneHandler = new DualToneHandler(
new ContextThemeWrapper(context, R.style.QSHeaderTheme));
- mCommandQueue = commandQueue;
- mRingerModeTracker = ringerModeTracker;
- mUiEventLogger = uiEventLogger;
}
@Override
@@ -228,11 +123,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements
mHeaderQsPanel = findViewById(R.id.quick_qs_panel);
mSystemIconsView = findViewById(R.id.quick_status_bar_system_icons);
mQuickQsStatusIcons = findViewById(R.id.quick_qs_status_icons);
- StatusIconContainer iconContainer = findViewById(R.id.statusIcons);
- // Ignore privacy icons because they show in the space above QQS
- iconContainer.addIgnoredSlots(getIgnoredIconSlots());
- iconContainer.setShouldRestrictIcons(false);
- mIconManager = new TintedIconManager(iconContainer, mCommandQueue);
// Views corresponding to the header info section (e.g. ringer and next alarm).
mHeaderTextContainerView = findViewById(R.id.header_text_container);
@@ -240,35 +130,18 @@ public class QuickStatusBarHeader extends RelativeLayout implements
mNextAlarmIcon = findViewById(R.id.next_alarm_icon);
mNextAlarmTextView = findViewById(R.id.next_alarm_text);
mNextAlarmContainer = findViewById(R.id.alarm_container);
- mNextAlarmContainer.setOnClickListener(this::onClick);
mRingerModeIcon = findViewById(R.id.ringer_mode_icon);
mRingerModeTextView = findViewById(R.id.ringer_mode_text);
mRingerContainer = findViewById(R.id.ringer_container);
- mRingerContainer.setOnClickListener(this::onClick);
mPrivacyChip = findViewById(R.id.privacy_chip);
- mPrivacyChip.setOnClickListener(this::onClick);
- mCarrierGroup = findViewById(R.id.carrier_group);
-
updateResources();
Rect tintArea = new Rect(0, 0, 0, 0);
- int colorForeground = Utils.getColorAttrDefaultColor(getContext(),
- android.R.attr.colorForeground);
- float intensity = getColorIntensity(colorForeground);
- int fillColor = mDualToneHandler.getSingleColor(intensity);
-
// Set light text on the header icons because they will always be on a black background
applyDarkness(R.id.clock, tintArea, 0, DarkIconDispatcher.DEFAULT_ICON_TINT);
- // Set the correct tint for the status icons so they contrast
- mIconManager.setTint(fillColor);
- mNextAlarmIcon.setImageTintList(ColorStateList.valueOf(fillColor));
- mRingerModeIcon.setImageTintList(ColorStateList.valueOf(fillColor));
-
mClockView = findViewById(R.id.clock);
- mClockView.setOnClickListener(this);
- mDateView = findViewById(R.id.date);
mSpace = findViewById(R.id.space);
// Tint for the battery icons are handled in setupHost()
@@ -280,33 +153,28 @@ public class QuickStatusBarHeader extends RelativeLayout implements
mBatteryRemainingIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE);
mRingerModeTextView.setSelected(true);
mNextAlarmTextView.setSelected(true);
+ }
- mAllIndicatorsEnabled = mPrivacyItemController.getAllIndicatorsAvailable();
- mMicCameraIndicatorsEnabled = mPrivacyItemController.getMicCameraAvailable();
+ void onAttach(TintedIconManager iconManager) {
+ int colorForeground = Utils.getColorAttrDefaultColor(getContext(),
+ android.R.attr.colorForeground);
+ float intensity = getColorIntensity(colorForeground);
+ int fillColor = mDualToneHandler.getSingleColor(intensity);
+
+ // Set the correct tint for the status icons so they contrast
+ iconManager.setTint(fillColor);
+ mNextAlarmIcon.setImageTintList(ColorStateList.valueOf(fillColor));
+ mRingerModeIcon.setImageTintList(ColorStateList.valueOf(fillColor));
}
public QuickQSPanel getHeaderQsPanel() {
return mHeaderQsPanel;
}
- private List<String> getIgnoredIconSlots() {
- ArrayList<String> ignored = new ArrayList<>();
- if (getChipEnabled()) {
- ignored.add(mContext.getResources().getString(
- com.android.internal.R.string.status_bar_camera));
- ignored.add(mContext.getResources().getString(
- com.android.internal.R.string.status_bar_microphone));
- if (mAllIndicatorsEnabled) {
- ignored.add(mContext.getResources().getString(
- com.android.internal.R.string.status_bar_location));
- }
- }
-
- return ignored;
- }
-
- private void updateStatusText() {
- boolean changed = updateRingerStatus() || updateAlarmStatus();
+ void updateStatusText(int ringerMode, AlarmClockInfo nextAlarm, boolean zenOverridingRinger,
+ boolean use24HourFormat) {
+ boolean changed = updateRingerStatus(ringerMode, zenOverridingRinger)
+ || updateAlarmStatus(nextAlarm, use24HourFormat);
if (changed) {
boolean alarmVisible = mNextAlarmTextView.getVisibility() == View.VISIBLE;
@@ -316,32 +184,17 @@ public class QuickStatusBarHeader extends RelativeLayout implements
}
}
- private void setChipVisibility(boolean chipVisible) {
- if (chipVisible && getChipEnabled()) {
- mPrivacyChip.setVisibility(View.VISIBLE);
- // Makes sure that the chip is logged as viewed at most once each time QS is opened
- // mListening makes sure that the callback didn't return after the user closed QS
- if (!mPrivacyChipLogged && mListening) {
- mPrivacyChipLogged = true;
- mUiEventLogger.log(PrivacyChipEvent.ONGOING_INDICATORS_CHIP_VIEW);
- }
- } else {
- mPrivacyChip.setVisibility(View.GONE);
- }
- }
-
- private boolean updateRingerStatus() {
+ private boolean updateRingerStatus(int ringerMode, boolean zenOverridingRinger) {
boolean isOriginalVisible = mRingerModeTextView.getVisibility() == View.VISIBLE;
CharSequence originalRingerText = mRingerModeTextView.getText();
boolean ringerVisible = false;
- if (!ZenModeConfig.isZenOverridingRinger(mZenController.getZen(),
- mZenController.getConsolidatedPolicy())) {
- if (mRingerMode == AudioManager.RINGER_MODE_VIBRATE) {
+ if (!zenOverridingRinger) {
+ if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) {
mRingerModeIcon.setImageResource(R.drawable.ic_volume_ringer_vibrate);
mRingerModeTextView.setText(R.string.qs_status_phone_vibrate);
ringerVisible = true;
- } else if (mRingerMode == AudioManager.RINGER_MODE_SILENT) {
+ } else if (ringerMode == AudioManager.RINGER_MODE_SILENT) {
mRingerModeIcon.setImageResource(R.drawable.ic_volume_ringer_mute);
mRingerModeTextView.setText(R.string.qs_status_phone_muted);
ringerVisible = true;
@@ -355,14 +208,14 @@ public class QuickStatusBarHeader extends RelativeLayout implements
!Objects.equals(originalRingerText, mRingerModeTextView.getText());
}
- private boolean updateAlarmStatus() {
+ private boolean updateAlarmStatus(AlarmClockInfo nextAlarm, boolean use24HourFormat) {
boolean isOriginalVisible = mNextAlarmTextView.getVisibility() == View.VISIBLE;
CharSequence originalAlarmText = mNextAlarmTextView.getText();
boolean alarmVisible = false;
- if (mNextAlarm != null) {
+ if (nextAlarm != null) {
alarmVisible = true;
- mNextAlarmTextView.setText(formatNextAlarm(mNextAlarm));
+ mNextAlarmTextView.setText(formatNextAlarm(nextAlarm, use24HourFormat));
}
mNextAlarmIcon.setVisibility(alarmVisible ? View.VISIBLE : View.GONE);
mNextAlarmTextView.setVisibility(alarmVisible ? View.VISIBLE : View.GONE);
@@ -409,7 +262,7 @@ public class QuickStatusBarHeader extends RelativeLayout implements
setMinimumHeight(sbHeight + qqsHeight);
}
- private void updateResources() {
+ void updateResources() {
Resources resources = mContext.getResources();
updateMinimumHeight();
@@ -519,17 +372,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements
}
@Override
- public void onAttachedToWindow() {
- super.onAttachedToWindow();
- mRingerModeTracker.getRingerModeInternal().observe(this, ringer -> {
- mRingerMode = ringer;
- updateStatusText();
- });
- mStatusBarIconController.addIconGroup(mIconManager);
- requestApplyInsets();
- }
-
- @Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
// Handle padding of the clock
DisplayCutout cutout = insets.getDisplayCutout();
@@ -552,17 +394,14 @@ public class QuickStatusBarHeader extends RelativeLayout implements
if (cutout != null) {
Rect topCutout = cutout.getBoundingRectTop();
if (topCutout.isEmpty() || cornerCutout) {
- mHasTopCutout = false;
lp.width = 0;
mSpace.setVisibility(View.GONE);
} else {
- mHasTopCutout = true;
lp.width = topCutout.width();
mSpace.setVisibility(View.VISIBLE);
}
}
mSpace.setLayoutParams(lp);
- setChipVisibility(mPrivacyChip.getVisibility() == View.VISIBLE);
mCutOutPaddingLeft = padding.first;
mCutOutPaddingRight = padding.second;
mWaterfallTopInset = cutout == null ? 0 : cutout.getWaterfallInsets().top;
@@ -600,102 +439,14 @@ public class QuickStatusBarHeader extends RelativeLayout implements
0);
}
- @Override
- @VisibleForTesting
- public void onDetachedFromWindow() {
- setListening(false);
- mRingerModeTracker.getRingerModeInternal().removeObservers(this);
- mStatusBarIconController.removeIconGroup(mIconManager);
- super.onDetachedFromWindow();
- }
-
- public void setListening(boolean listening) {
- if (listening == mListening) {
- return;
- }
- mHeaderQsPanel.setListening(listening);
- if (mHeaderQsPanel.switchTileLayout()) {
- updateResources();
- }
- mListening = listening;
-
- if (listening) {
- mZenController.addCallback(this);
- mAlarmController.addCallback(this);
- mLifecycle.setCurrentState(Lifecycle.State.RESUMED);
- // Get the most up to date info
- mAllIndicatorsEnabled = mPrivacyItemController.getAllIndicatorsAvailable();
- mMicCameraIndicatorsEnabled = mPrivacyItemController.getMicCameraAvailable();
- mPrivacyItemController.addCallback(mPICCallback);
- } else {
- mZenController.removeCallback(this);
- mAlarmController.removeCallback(this);
- mLifecycle.setCurrentState(Lifecycle.State.CREATED);
- mPrivacyItemController.removeCallback(mPICCallback);
- mPrivacyChipLogged = false;
- }
- }
-
- @Override
- public void onClick(View v) {
- if (v == mClockView) {
- mActivityStarter.postStartActivityDismissingKeyguard(new Intent(
- AlarmClock.ACTION_SHOW_ALARMS), 0);
- } else if (v == mNextAlarmContainer && mNextAlarmContainer.isVisibleToUser()) {
- if (mNextAlarm.getShowIntent() != null) {
- mActivityStarter.postStartActivityDismissingKeyguard(
- mNextAlarm.getShowIntent());
- } else {
- Log.d(TAG, "No PendingIntent for next alarm. Using default intent");
- mActivityStarter.postStartActivityDismissingKeyguard(new Intent(
- AlarmClock.ACTION_SHOW_ALARMS), 0);
- }
- } else if (v == mPrivacyChip) {
- // If the privacy chip is visible, it means there were some indicators
- Handler mUiHandler = new Handler(Looper.getMainLooper());
- mUiEventLogger.log(PrivacyChipEvent.ONGOING_INDICATORS_CHIP_CLICK);
- mUiHandler.post(() -> {
- mActivityStarter.postStartActivityDismissingKeyguard(
- new Intent(Intent.ACTION_REVIEW_ONGOING_PERMISSION_USAGE), 0);
- mHost.collapsePanels();
- });
- } else if (v == mRingerContainer && mRingerContainer.isVisibleToUser()) {
- mActivityStarter.postStartActivityDismissingKeyguard(new Intent(
- Settings.ACTION_SOUND_SETTINGS), 0);
- }
- }
-
- @Override
- public void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm) {
- mNextAlarm = nextAlarm;
- updateStatusText();
- }
-
- @Override
- public void onZenChanged(int zen) {
- updateStatusText();
- }
-
- @Override
- public void onConfigChanged(ZenModeConfig config) {
- updateStatusText();
- }
-
public void updateEverything() {
post(() -> setClickable(!mExpanded));
}
public void setQSPanel(final QSPanel qsPanel) {
- mQsPanel = qsPanel;
- setupHost(qsPanel.getHost());
- }
-
- public void setupHost(final QSTileHost host) {
- mHost = host;
//host.setHeaderView(mExpandIndicator);
- mHeaderQsPanel.setQSPanelAndHeader(mQsPanel, this);
- mHeaderQsPanel.setHost(host, null /* No customization in header */);
-
+ mHeaderQsPanel.setQSPanelAndHeader(qsPanel, this);
+ mHeaderQsPanel.setHost(qsPanel.getHost(), null /* No customization in header */);
Rect tintArea = new Rect(0, 0, 0, 0);
int colorForeground = Utils.getColorAttrDefaultColor(getContext(),
@@ -709,12 +460,11 @@ public class QuickStatusBarHeader extends RelativeLayout implements
mHeaderQsPanel.setCallback(qsPanelCallback);
}
- private String formatNextAlarm(AlarmManager.AlarmClockInfo info) {
+ private String formatNextAlarm(AlarmClockInfo info, boolean use24HourFormat) {
if (info == null) {
return "";
}
- String skeleton = android.text.format.DateFormat
- .is24HourFormat(mContext, ActivityManager.getCurrentUser()) ? "EHm" : "Ehma";
+ String skeleton = use24HourFormat ? "EHm" : "Ehma";
String pattern = android.text.format.DateFormat
.getBestDateTimePattern(Locale.getDefault(), skeleton);
return android.text.format.DateFormat.format(pattern, info.getTriggerTime()).toString();
@@ -765,8 +515,4 @@ public class QuickStatusBarHeader extends RelativeLayout implements
updateHeaderTextContainerAlphaAnimator();
}
}
-
- private boolean getChipEnabled() {
- return mMicCameraIndicatorsEnabled || mAllIndicatorsEnabled;
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
index d899acbade4a..676a300b0ff2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
@@ -16,36 +16,393 @@
package com.android.systemui.qs;
+import android.app.AlarmManager.AlarmClockInfo;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.AlarmClock;
+import android.provider.Settings;
+import android.service.notification.ZenModeConfig;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LifecycleRegistry;
+
+import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
+import com.android.systemui.demomode.DemoMode;
+import com.android.systemui.demomode.DemoModeController;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.privacy.OngoingPrivacyChip;
+import com.android.systemui.privacy.PrivacyChipEvent;
+import com.android.systemui.privacy.PrivacyItem;
+import com.android.systemui.privacy.PrivacyItemController;
import com.android.systemui.qs.carrier.QSCarrierGroupController;
+import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.phone.StatusBarIconController;
+import com.android.systemui.statusbar.phone.StatusIconContainer;
+import com.android.systemui.statusbar.policy.Clock;
+import com.android.systemui.statusbar.policy.NextAlarmController;
+import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback;
+import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.statusbar.policy.ZenModeController.Callback;
+import com.android.systemui.util.RingerModeTracker;
+import com.android.systemui.util.ViewController;
+
+import java.util.ArrayList;
+import java.util.List;
import javax.inject.Inject;
-public class QuickStatusBarHeaderController {
- private final QuickStatusBarHeader mView;
+/**
+ * Controller for {@link QuickStatusBarHeader}.
+ */
+class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader> {
+ private static final String TAG = "QuickStatusBarHeader";
+
+ private final ZenModeController mZenModeController;
+ private final NextAlarmController mNextAlarmController;
+ private final PrivacyItemController mPrivacyItemController;
+ private final RingerModeTracker mRingerModeTracker;
+ private final ActivityStarter mActivityStarter;
+ private final UiEventLogger mUiEventLogger;
private final QSCarrierGroupController mQSCarrierGroupController;
+ private final QuickQSPanel mHeaderQsPanel;
+ private final LifecycleRegistry mLifecycle;
+ private final OngoingPrivacyChip mPrivacyChip;
+ private final Clock mClockView;
+ private final View mNextAlarmContainer;
+ private final View mRingerContainer;
+ private final QSTileHost mQSTileHost;
+ private final StatusBarIconController mStatusBarIconController;
+ private final CommandQueue mCommandQueue;
+ private final DemoModeController mDemoModeController;
+ private final UserTracker mUserTracker;
+ private final StatusIconContainer mIconContainer;
+ private final StatusBarIconController.TintedIconManager mIconManager;
+ private final DemoMode mDemoModeReceiver;
+
+ private boolean mListening;
+ private AlarmClockInfo mNextAlarm;
+ private boolean mAllIndicatorsEnabled;
+ private boolean mMicCameraIndicatorsEnabled;
+ private boolean mPrivacyChipLogged;
+ private int mRingerMode = AudioManager.RINGER_MODE_NORMAL;
+
+ private final ZenModeController.Callback mZenModeControllerCallback = new Callback() {
+ @Override
+ public void onZenChanged(int zen) {
+ mView.updateStatusText(mRingerMode, mNextAlarm, isZenOverridingRinger(),
+ use24HourFormat());
+ }
+
+ @Override
+ public void onConfigChanged(ZenModeConfig config) {
+ mView.updateStatusText(mRingerMode, mNextAlarm, isZenOverridingRinger(),
+ use24HourFormat());
+ }
+ };
+
+ private boolean use24HourFormat() {
+ return android.text.format.DateFormat.is24HourFormat(
+ mView.getContext(), mUserTracker.getUserId());
+
+ }
+
+ private final NextAlarmChangeCallback mNextAlarmChangeCallback = new NextAlarmChangeCallback() {
+ @Override
+ public void onNextAlarmChanged(AlarmClockInfo nextAlarm) {
+ mNextAlarm = nextAlarm;
+ mView.updateStatusText(mRingerMode, mNextAlarm, isZenOverridingRinger(),
+ use24HourFormat());
+ }
+ };
+
+ private final LifecycleOwner mLifecycleOwner = new LifecycleOwner() {
+ @NonNull
+ @Override
+ public Lifecycle getLifecycle() {
+ return mLifecycle;
+ }
+ };
+
+ private PrivacyItemController.Callback mPICCallback = new PrivacyItemController.Callback() {
+ @Override
+ public void onPrivacyItemsChanged(@NonNull List<PrivacyItem> privacyItems) {
+ mPrivacyChip.setPrivacyList(privacyItems);
+ setChipVisibility(!privacyItems.isEmpty());
+ }
+
+ @Override
+ public void onFlagAllChanged(boolean flag) {
+ if (mAllIndicatorsEnabled != flag) {
+ mAllIndicatorsEnabled = flag;
+ update();
+ }
+ }
+
+ @Override
+ public void onFlagMicCameraChanged(boolean flag) {
+ if (mMicCameraIndicatorsEnabled != flag) {
+ mMicCameraIndicatorsEnabled = flag;
+ update();
+ }
+ }
+
+ private void update() {
+ StatusIconContainer iconContainer = mView.requireViewById(R.id.statusIcons);
+ iconContainer.setIgnoredSlots(getIgnoredIconSlots());
+ setChipVisibility(!mPrivacyChip.getPrivacyList().isEmpty());
+ }
+ };
+
+ private View.OnClickListener mOnClickListener = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (v == mClockView) {
+ mActivityStarter.postStartActivityDismissingKeyguard(new Intent(
+ AlarmClock.ACTION_SHOW_ALARMS), 0);
+ } else if (v == mNextAlarmContainer && mNextAlarmContainer.isVisibleToUser()) {
+ if (mNextAlarm.getShowIntent() != null) {
+ mActivityStarter.postStartActivityDismissingKeyguard(
+ mNextAlarm.getShowIntent());
+ } else {
+ Log.d(TAG, "No PendingIntent for next alarm. Using default intent");
+ mActivityStarter.postStartActivityDismissingKeyguard(new Intent(
+ AlarmClock.ACTION_SHOW_ALARMS), 0);
+ }
+ } else if (v == mPrivacyChip) {
+ // If the privacy chip is visible, it means there were some indicators
+ Handler mUiHandler = new Handler(Looper.getMainLooper());
+ mUiEventLogger.log(PrivacyChipEvent.ONGOING_INDICATORS_CHIP_CLICK);
+ mUiHandler.post(() -> {
+ mActivityStarter.postStartActivityDismissingKeyguard(
+ new Intent(Intent.ACTION_REVIEW_ONGOING_PERMISSION_USAGE), 0);
+ mQSTileHost.collapsePanels();
+ });
+ } else if (v == mRingerContainer && mRingerContainer.isVisibleToUser()) {
+ mActivityStarter.postStartActivityDismissingKeyguard(new Intent(
+ Settings.ACTION_SOUND_SETTINGS), 0);
+ }
+ }
+ };
private QuickStatusBarHeaderController(QuickStatusBarHeader view,
+ ZenModeController zenModeController, NextAlarmController nextAlarmController,
+ PrivacyItemController privacyItemController, RingerModeTracker ringerModeTracker,
+ ActivityStarter activityStarter, UiEventLogger uiEventLogger,
+ QSTileHost qsTileHost, StatusBarIconController statusBarIconController,
+ CommandQueue commandQueue, DemoModeController demoModeController,
+ UserTracker userTracker,
QSCarrierGroupController.Builder qsCarrierGroupControllerBuilder) {
- mView = view;
+ super(view);
+ mZenModeController = zenModeController;
+ mNextAlarmController = nextAlarmController;
+ mPrivacyItemController = privacyItemController;
+ mRingerModeTracker = ringerModeTracker;
+ mActivityStarter = activityStarter;
+ mUiEventLogger = uiEventLogger;
+ mQSTileHost = qsTileHost;
+ mStatusBarIconController = statusBarIconController;
+ mCommandQueue = commandQueue;
+ mDemoModeController = demoModeController;
+ mUserTracker = userTracker;
+ mLifecycle = new LifecycleRegistry(mLifecycleOwner);
+
mQSCarrierGroupController = qsCarrierGroupControllerBuilder
.setQSCarrierGroup(mView.findViewById(R.id.carrier_group))
.build();
+
+
+ mPrivacyChip = mView.findViewById(R.id.privacy_chip);
+ mHeaderQsPanel = mView.findViewById(R.id.quick_qs_panel);
+ mNextAlarmContainer = mView.findViewById(R.id.alarm_container);
+ mClockView = mView.findViewById(R.id.clock);
+ mRingerContainer = mView.findViewById(R.id.ringer_container);
+ mIconContainer = mView.findViewById(R.id.statusIcons);
+
+ mIconManager = new StatusBarIconController.TintedIconManager(mIconContainer, mCommandQueue);
+ mDemoModeReceiver = new ClockDemoModeReceiver(mClockView);
+ }
+
+ @Override
+ protected void onViewAttached() {
+ mRingerModeTracker.getRingerModeInternal().observe(mLifecycleOwner, ringer -> {
+ mRingerMode = ringer;
+ mView.updateStatusText(mRingerMode, mNextAlarm, isZenOverridingRinger(),
+ use24HourFormat());
+ });
+
+ mClockView.setOnClickListener(mOnClickListener);
+ mNextAlarmContainer.setOnClickListener(mOnClickListener);
+ mRingerContainer.setOnClickListener(mOnClickListener);
+ mPrivacyChip.setOnClickListener(mOnClickListener);
+
+ // Ignore privacy icons because they show in the space above QQS
+ mIconContainer.addIgnoredSlots(getIgnoredIconSlots());
+ mIconContainer.setShouldRestrictIcons(false);
+ mStatusBarIconController.addIconGroup(mIconManager);
+
+ mAllIndicatorsEnabled = mPrivacyItemController.getAllIndicatorsAvailable();
+ mMicCameraIndicatorsEnabled = mPrivacyItemController.getMicCameraAvailable();
+
+ setChipVisibility(mPrivacyChip.getVisibility() == View.VISIBLE);
+
+ mView.onAttach(mIconManager);
+
+ mDemoModeController.addCallback(mDemoModeReceiver);
+ }
+
+ @Override
+ protected void onViewDetached() {
+ mRingerModeTracker.getRingerModeInternal().removeObservers(mLifecycleOwner);
+ mClockView.setOnClickListener(null);
+ mNextAlarmContainer.setOnClickListener(null);
+ mRingerContainer.setOnClickListener(null);
+ mPrivacyChip.setOnClickListener(null);
+ mStatusBarIconController.removeIconGroup(mIconManager);
+ mDemoModeController.removeCallback(mDemoModeReceiver);
+ setListening(false);
}
public void setListening(boolean listening) {
mQSCarrierGroupController.setListening(listening);
- // TODO: move mView.setListening logic into here.
- mView.setListening(listening);
+
+ if (listening == mListening) {
+ return;
+ }
+ mListening = listening;
+
+ mHeaderQsPanel.setListening(listening);
+ if (mHeaderQsPanel.switchTileLayout()) {
+ mView.updateResources();
+ }
+
+ if (listening) {
+ mZenModeController.addCallback(mZenModeControllerCallback);
+ mNextAlarmController.addCallback(mNextAlarmChangeCallback);
+ mLifecycle.setCurrentState(Lifecycle.State.RESUMED);
+ // Get the most up to date info
+ mAllIndicatorsEnabled = mPrivacyItemController.getAllIndicatorsAvailable();
+ mMicCameraIndicatorsEnabled = mPrivacyItemController.getMicCameraAvailable();
+ mPrivacyItemController.addCallback(mPICCallback);
+ } else {
+ mZenModeController.removeCallback(mZenModeControllerCallback);
+ mNextAlarmController.removeCallback(mNextAlarmChangeCallback);
+ mLifecycle.setCurrentState(Lifecycle.State.CREATED);
+ mPrivacyItemController.removeCallback(mPICCallback);
+ mPrivacyChipLogged = false;
+ }
}
+ private void setChipVisibility(boolean chipVisible) {
+ if (chipVisible && getChipEnabled()) {
+ mPrivacyChip.setVisibility(View.VISIBLE);
+ // Makes sure that the chip is logged as viewed at most once each time QS is opened
+ // mListening makes sure that the callback didn't return after the user closed QS
+ if (!mPrivacyChipLogged && mListening) {
+ mPrivacyChipLogged = true;
+ mUiEventLogger.log(PrivacyChipEvent.ONGOING_INDICATORS_CHIP_VIEW);
+ }
+ } else {
+ mPrivacyChip.setVisibility(View.GONE);
+ }
+ }
- public static class Builder {
+ private List<String> getIgnoredIconSlots() {
+ ArrayList<String> ignored = new ArrayList<>();
+ if (getChipEnabled()) {
+ ignored.add(mView.getResources().getString(
+ com.android.internal.R.string.status_bar_camera));
+ ignored.add(mView.getResources().getString(
+ com.android.internal.R.string.status_bar_microphone));
+ if (mAllIndicatorsEnabled) {
+ ignored.add(mView.getResources().getString(
+ com.android.internal.R.string.status_bar_location));
+ }
+ }
+
+ return ignored;
+ }
+
+ private boolean getChipEnabled() {
+ return mMicCameraIndicatorsEnabled || mAllIndicatorsEnabled;
+ }
+
+ private boolean isZenOverridingRinger() {
+ return ZenModeConfig.isZenOverridingRinger(mZenModeController.getZen(),
+ mZenModeController.getConsolidatedPolicy());
+ }
+
+
+ private static class ClockDemoModeReceiver implements DemoMode {
+ private Clock mClockView;
+
+ @Override
+ public List<String> demoCommands() {
+ return List.of(COMMAND_CLOCK);
+ }
+
+ ClockDemoModeReceiver(Clock clockView) {
+ mClockView = clockView;
+ }
+
+ @Override
+ public void dispatchDemoCommand(String command, Bundle args) {
+ mClockView.dispatchDemoCommand(command, args);
+ }
+
+ @Override
+ public void onDemoModeStarted() {
+ mClockView.onDemoModeStarted();
+ }
+
+ @Override
+ public void onDemoModeFinished() {
+ mClockView.onDemoModeFinished();
+ }
+ }
+
+ static class Builder {
+ private final ZenModeController mZenModeController;
+ private final NextAlarmController mNextAlarmController;
+ private final PrivacyItemController mPrivacyItemController;
+ private final RingerModeTracker mRingerModeTracker;
+ private final ActivityStarter mActivityStarter;
+ private final UiEventLogger mUiEventLogger;
+ private final QSTileHost mQsTileHost;
+ private final StatusBarIconController mStatusBarIconController;
+ private final CommandQueue mCommandQueue;
+ private final DemoModeController mDemoModeController;
+ private final UserTracker mUserTracker;
private final QSCarrierGroupController.Builder mQSCarrierGroupControllerBuilder;
private QuickStatusBarHeader mView;
@Inject
- public Builder(QSCarrierGroupController.Builder qsCarrierGroupControllerBuilder) {
+ Builder(ZenModeController zenModeController, NextAlarmController nextAlarmController,
+ PrivacyItemController privacyItemController, RingerModeTracker ringerModeTracker,
+ ActivityStarter activityStarter, UiEventLogger uiEventLogger, QSTileHost qsTileHost,
+ StatusBarIconController statusBarIconController, CommandQueue commandQueue,
+ DemoModeController demoModeController, UserTracker userTracker,
+ QSCarrierGroupController.Builder qsCarrierGroupControllerBuilder) {
+ mZenModeController = zenModeController;
+ mNextAlarmController = nextAlarmController;
+ mPrivacyItemController = privacyItemController;
+ mRingerModeTracker = ringerModeTracker;
+ mActivityStarter = activityStarter;
+ mUiEventLogger = uiEventLogger;
+ mQsTileHost = qsTileHost;
+ mStatusBarIconController = statusBarIconController;
+ mCommandQueue = commandQueue;
+ mDemoModeController = demoModeController;
+ mUserTracker = userTracker;
mQSCarrierGroupControllerBuilder = qsCarrierGroupControllerBuilder;
}
@@ -54,8 +411,13 @@ public class QuickStatusBarHeaderController {
return this;
}
- public QuickStatusBarHeaderController build() {
- return new QuickStatusBarHeaderController(mView, mQSCarrierGroupControllerBuilder);
+
+ QuickStatusBarHeaderController build() {
+ return new QuickStatusBarHeaderController(mView, mZenModeController,
+ mNextAlarmController, mPrivacyItemController, mRingerModeTracker,
+ mActivityStarter, mUiEventLogger, mQsTileHost, mStatusBarIconController,
+ mCommandQueue, mDemoModeController, mUserTracker,
+ mQSCarrierGroupControllerBuilder);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java b/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java
index 65d815053e47..3ee3e117fb0f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java
@@ -16,7 +16,6 @@
package com.android.systemui.qs;
-import android.app.ActivityManager;
import android.content.Context;
import android.database.ContentObserver;
import android.os.Handler;
@@ -37,10 +36,6 @@ public abstract class SecureSetting extends ContentObserver implements Listenabl
protected abstract void handleValueChanged(int value, boolean observedChange);
- 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;
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 e5ed88c10a2e..55b67e061c13 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -132,6 +132,7 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene
layout.setSpanSizeLookup(mTileAdapter.getSizeLookup());
mRecyclerView.setLayoutManager(layout);
mRecyclerView.addItemDecoration(mTileAdapter.getItemDecoration());
+ mRecyclerView.addItemDecoration(mTileAdapter.getMarginItemDecoration());
DefaultItemAnimator animator = new DefaultItemAnimator();
animator.setMoveDuration(TileAdapter.MOVE_DURATION);
mRecyclerView.setItemAnimator(animator);
@@ -221,6 +222,22 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene
}
}
+ /**
+ * Sets the padding for the RecyclerView. Also, updates the margin between the tiles in the
+ * {@link TileAdapter}.
+ */
+ public void setContentPaddings(int paddingStart, int paddingEnd) {
+ int halfMargin = mContext.getResources()
+ .getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal) / 2;
+ mTileAdapter.changeHalfMargin(halfMargin);
+ mRecyclerView.setPaddingRelative(
+ paddingStart,
+ mRecyclerView.getPaddingTop(),
+ paddingEnd,
+ mRecyclerView.getPaddingBottom()
+ );
+ }
+
private void queryTiles() {
mTileQueryHelper.queryTiles(mHost);
}
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 bffeb3ec3c70..b471dfae02d1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -18,6 +18,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.view.LayoutInflater;
@@ -75,6 +76,7 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta
private final List<TileInfo> mTiles = new ArrayList<>();
private final ItemTouchHelper mItemTouchHelper;
private final ItemDecoration mDecoration;
+ private final MarginTileDecoration mMarginDecoration;
private final int mMinNumTiles;
private int mEditIndex;
private int mTileDividerIndex;
@@ -97,6 +99,7 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta
mUiEventLogger = uiEventLogger;
mItemTouchHelper = new ItemTouchHelper(mCallbacks);
mDecoration = new TileItemDecoration(context);
+ mMarginDecoration = new MarginTileDecoration();
mMinNumTiles = context.getResources().getInteger(R.integer.quick_settings_min_num_tiles);
mAccessibilityDelegate = new TileAdapterDelegate();
}
@@ -123,6 +126,14 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta
return mDecoration;
}
+ public ItemDecoration getMarginItemDecoration() {
+ return mMarginDecoration;
+ }
+
+ public void changeHalfMargin(int halfMargin) {
+ mMarginDecoration.setHalfMargin(halfMargin);
+ }
+
public void saveSpecs(QSTileHost host) {
List<String> newSpecs = new ArrayList<>();
clearAccessibilityState();
@@ -596,7 +607,6 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta
mDrawable = context.getDrawable(R.drawable.qs_customize_tile_decoration);
}
-
@Override
public void onDraw(Canvas c, RecyclerView parent, State state) {
super.onDraw(c, parent, state);
@@ -607,6 +617,12 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final ViewHolder holder = parent.getChildViewHolder(child);
+ // Do not draw background for the holder that's currently being dragged
+ if (holder == mCurrentDrag) {
+ continue;
+ }
+ // Do not draw background for holders before the edit index (header and current
+ // tiles)
if (holder.getAdapterPosition() == 0 ||
holder.getAdapterPosition() < mEditIndex && !(child instanceof TextView)) {
continue;
@@ -624,6 +640,25 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta
}
}
+ private static class MarginTileDecoration extends ItemDecoration {
+ private int mHalfMargin;
+
+ public void setHalfMargin(int halfMargin) {
+ mHalfMargin = halfMargin;
+ }
+
+ @Override
+ public void getItemOffsets(@NonNull Rect outRect, @NonNull View view,
+ @NonNull RecyclerView parent, @NonNull State state) {
+ if (view instanceof TextView) {
+ super.getItemOffsets(outRect, view, parent, state);
+ } else {
+ outRect.left = mHalfMargin;
+ outRect.right = mHalfMargin;
+ }
+ }
+ }
+
private final ItemTouchHelper.Callback mCallbacks = new ItemTouchHelper.Callback() {
@Override
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 73c6504b9983..b795a5f5ea19 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
@@ -17,7 +17,6 @@
package com.android.systemui.qs.customize;
import android.Manifest.permission;
-import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -40,6 +39,7 @@ import com.android.systemui.plugins.qs.QSTile.State;
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.qs.external.CustomTile;
import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.util.leak.GarbageMonitor;
import java.util.ArrayList;
@@ -58,16 +58,18 @@ public class TileQueryHelper {
private final Executor mMainExecutor;
private final Executor mBgExecutor;
private final Context mContext;
+ private final UserTracker mUserTracker;
private TileStateListener mListener;
private boolean mFinished;
@Inject
- public TileQueryHelper(Context context,
+ public TileQueryHelper(Context context, UserTracker userTracker,
@Main Executor mainExecutor, @Background Executor bgExecutor) {
mContext = context;
mMainExecutor = mainExecutor;
mBgExecutor = bgExecutor;
+ mUserTracker = userTracker;
}
public void setListener(TileStateListener listener) {
@@ -207,7 +209,7 @@ public class TileQueryHelper {
Collection<QSTile> params = host.getTiles();
PackageManager pm = mContext.getPackageManager();
List<ResolveInfo> services = pm.queryIntentServicesAsUser(
- new Intent(TileService.ACTION_QS_TILE), 0, ActivityManager.getCurrentUser());
+ new Intent(TileService.ACTION_QS_TILE), 0, mUserTracker.getUserId());
String stockTiles = mContext.getString(R.string.quick_settings_tiles_stock);
for (ResolveInfo info : services) {
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 19c7b6cefc5d..6e28cd89d43a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -18,7 +18,6 @@ package com.android.systemui.qs.external;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_QS_DIALOG;
-import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -304,8 +303,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener
}
private Intent resolveIntent(Intent i) {
- ResolveInfo result = mContext.getPackageManager().resolveActivityAsUser(i, 0,
- ActivityManager.getCurrentUser());
+ ResolveInfo result = mContext.getPackageManager().resolveActivityAsUser(i, 0, mUser);
return result != null ? new Intent(TileService.ACTION_QS_TILE_PREFERENCES)
.setClassName(result.activityInfo.packageName, result.activityInfo.name) : null;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
index cfa8fb6373a1..7e76e57f4802 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
@@ -15,7 +15,6 @@
*/
package com.android.systemui.qs.external;
-import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -26,7 +25,6 @@ import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
-import android.os.UserHandle;
import android.service.quicksettings.IQSTileService;
import android.service.quicksettings.Tile;
import android.service.quicksettings.TileService;
@@ -36,6 +34,7 @@ import androidx.annotation.VisibleForTesting;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.qs.external.TileLifecycleManager.TileChangeListener;
+import com.android.systemui.settings.UserTracker;
import java.util.List;
import java.util.Objects;
@@ -60,6 +59,7 @@ public class TileServiceManager {
private final TileServices mServices;
private final TileLifecycleManager mStateManager;
private final Handler mHandler;
+ private final UserTracker mUserTracker;
private boolean mBindRequested;
private boolean mBindAllowed;
private boolean mBound;
@@ -73,25 +73,26 @@ public class TileServiceManager {
private boolean mStarted = false;
TileServiceManager(TileServices tileServices, Handler handler, ComponentName component,
- Tile tile, BroadcastDispatcher broadcastDispatcher) {
- this(tileServices, handler, new TileLifecycleManager(handler,
+ Tile tile, BroadcastDispatcher broadcastDispatcher, UserTracker userTracker) {
+ this(tileServices, handler, userTracker, new TileLifecycleManager(handler,
tileServices.getContext(), tileServices, tile, new Intent().setComponent(component),
- new UserHandle(ActivityManager.getCurrentUser()), broadcastDispatcher));
+ userTracker.getUserHandle(), broadcastDispatcher));
}
@VisibleForTesting
- TileServiceManager(TileServices tileServices, Handler handler,
+ TileServiceManager(TileServices tileServices, Handler handler, UserTracker userTracker,
TileLifecycleManager tileLifecycleManager) {
mServices = tileServices;
mHandler = handler;
mStateManager = tileLifecycleManager;
+ mUserTracker = userTracker;
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addDataScheme("package");
Context context = mServices.getContext();
- context.registerReceiverAsUser(mUninstallReceiver,
- new UserHandle(ActivityManager.getCurrentUser()), filter, null, mHandler);
+ context.registerReceiverAsUser(mUninstallReceiver, userTracker.getUserHandle(), filter,
+ null, mHandler);
}
boolean isLifecycleStarted() {
@@ -279,7 +280,7 @@ public class TileServiceManager {
queryIntent.setPackage(pkgName);
PackageManager pm = context.getPackageManager();
List<ResolveInfo> services = pm.queryIntentServicesAsUser(
- queryIntent, 0, ActivityManager.getCurrentUser());
+ queryIntent, 0, mUserTracker.getUserId());
for (ResolveInfo info : services) {
if (Objects.equals(info.serviceInfo.packageName, component.getPackageName())
&& Objects.equals(info.serviceInfo.name, component.getClassName())) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
index 2863d08a75dc..35cf2a12745e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
@@ -39,6 +39,7 @@ import com.android.internal.statusbar.StatusBarIcon;
import com.android.systemui.Dependency;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -62,15 +63,18 @@ public class TileServices extends IQSService.Stub {
private final Handler mMainHandler;
private final QSTileHost mHost;
private final BroadcastDispatcher mBroadcastDispatcher;
+ private final UserTracker mUserTracker;
private int mMaxBound = DEFAULT_MAX_BOUND;
- public TileServices(QSTileHost host, Looper looper, BroadcastDispatcher broadcastDispatcher) {
+ public TileServices(QSTileHost host, Looper looper, BroadcastDispatcher broadcastDispatcher,
+ UserTracker userTracker) {
mHost = host;
mContext = mHost.getContext();
mBroadcastDispatcher = broadcastDispatcher;
mHandler = new Handler(looper);
mMainHandler = new Handler(Looper.getMainLooper());
+ mUserTracker = userTracker;
mBroadcastDispatcher.registerReceiver(
mRequestListeningReceiver,
new IntentFilter(TileService.ACTION_REQUEST_LISTENING),
@@ -104,7 +108,7 @@ public class TileServices extends IQSService.Stub {
protected TileServiceManager onCreateTileService(ComponentName component, Tile tile,
BroadcastDispatcher broadcastDispatcher) {
return new TileServiceManager(this, mHandler, component, tile,
- broadcastDispatcher);
+ broadcastDispatcher, mUserTracker);
}
public void freeService(CustomTile tile, TileServiceManager service) {
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 dfd7e2c8fdb7..5a81676567a7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -31,7 +31,6 @@ import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import android.annotation.CallSuper;
import android.annotation.NonNull;
-import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
@@ -521,9 +520,9 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy
protected void checkIfRestrictionEnforcedByAdminOnly(State state, String userRestriction) {
EnforcedAdmin admin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(mContext,
- userRestriction, ActivityManager.getCurrentUser());
+ userRestriction, mHost.getUserId());
if (admin != null && !RestrictedLockUtilsInternal.hasBaseUserRestriction(mContext,
- userRestriction, ActivityManager.getCurrentUser())) {
+ userRestriction, mHost.getUserId())) {
state.disabledByPolicy = true;
mEnforcedAdmin = admin;
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index 3495d3065fe9..becbfd57c14b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -52,6 +52,7 @@ import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.CastController.CastDevice;
+import com.android.systemui.statusbar.policy.HotspotController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.NetworkController;
@@ -73,6 +74,7 @@ public class CastTile extends QSTileImpl<BooleanState> {
private final Callback mCallback = new Callback();
private Dialog mDialog;
private boolean mWifiConnected;
+ private boolean mHotspotConnected;
private static final String WFD_ENABLE = "persist.debug.wfd.enable";
@Inject
@@ -86,7 +88,8 @@ public class CastTile extends QSTileImpl<BooleanState> {
QSLogger qsLogger,
CastController castController,
KeyguardStateController keyguardStateController,
- NetworkController networkController
+ NetworkController networkController,
+ HotspotController hotspotController
) {
super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController,
activityStarter, qsLogger);
@@ -97,6 +100,7 @@ public class CastTile extends QSTileImpl<BooleanState> {
mController.observe(this, mCallback);
mKeyguard.observe(this, mCallback);
mNetworkController.observe(this, mSignalCallback);
+ hotspotController.observe(this, mHotspotCallback);
}
@Override
@@ -224,7 +228,7 @@ public class CastTile extends QSTileImpl<BooleanState> {
}
state.icon = ResourceIcon.get(state.value ? R.drawable.ic_cast_connected
: R.drawable.ic_cast);
- if (mWifiConnected || state.value) {
+ if (canCastToWifi() || state.value) {
state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
if (!state.value) {
state.secondaryLabel = "";
@@ -260,6 +264,10 @@ public class CastTile extends QSTileImpl<BooleanState> {
: mContext.getString(R.string.quick_settings_cast_device_default_name);
}
+ private boolean canCastToWifi() {
+ return mWifiConnected || mHotspotConnected;
+ }
+
private final NetworkController.SignalCallback mSignalCallback =
new NetworkController.SignalCallback() {
@Override
@@ -277,6 +285,24 @@ public class CastTile extends QSTileImpl<BooleanState> {
boolean enabledAndConnected = enabled && qsIcon.visible;
if (enabledAndConnected != mWifiConnected) {
mWifiConnected = enabledAndConnected;
+ // Hotspot is not connected, so changes here should update
+ if (!mHotspotConnected) {
+ refreshState();
+ }
+ }
+ }
+ }
+ };
+
+ private final HotspotController.Callback mHotspotCallback =
+ new HotspotController.Callback() {
+ @Override
+ public void onHotspotChanged(boolean enabled, int numDevices) {
+ boolean enabledAndConnected = enabled && numDevices > 0;
+ if (enabledAndConnected != mHotspotConnected) {
+ mHotspotConnected = enabledAndConnected;
+ // Wifi is not connected, so changes here should update
+ if (!mWifiConnected) {
refreshState();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
index 347ef45824c9..98782f7c8b55 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
@@ -38,6 +38,7 @@ import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.SecureSetting;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.settings.UserTracker;
import javax.inject.Inject;
@@ -61,13 +62,14 @@ public class ColorInversionTile extends QSTileImpl<BooleanState> {
MetricsLogger metricsLogger,
StatusBarStateController statusBarStateController,
ActivityStarter activityStarter,
- QSLogger qsLogger
+ QSLogger qsLogger,
+ UserTracker userTracker
) {
super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController,
activityStarter, qsLogger);
mSetting = new SecureSetting(mContext, mainHandler,
- Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED) {
+ Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, userTracker.getUserId()) {
@Override
protected void handleValueChanged(int value, boolean observedChange) {
// mHandler is the background handler so calling this is OK
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index ec8b1435e201..2076cbffa425 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -19,7 +19,6 @@ package com.android.systemui.qs.tiles;
import static android.provider.Settings.Global.ZEN_MODE_ALARMS;
import static android.provider.Settings.Global.ZEN_MODE_OFF;
-import android.app.ActivityManager;
import android.app.Dialog;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -203,7 +202,7 @@ public class DndTile extends QSTileImpl<BooleanState> {
break;
default:
Uri conditionId = ZenModeConfig.toTimeCondition(mContext, zenDuration,
- ActivityManager.getCurrentUser(), true).id;
+ mHost.getUserId(), true).id;
mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
conditionId, TAG);
}
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 201ed9c9ebec..8ddd4c9816cd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
@@ -155,7 +155,7 @@ public class UserDetailView extends PseudoGridView {
}
view.setActivated(true);
}
- switchTo(tag);
+ onUserListItemClicked(tag);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java
index 47002683c6b9..bbeff6ece902 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java
@@ -16,30 +16,17 @@
package com.android.systemui.recents;
-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 android.annotation.Nullable;
-import android.app.ActivityManager;
import android.app.trust.TrustManager;
import android.content.Context;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.hardware.display.DisplayManager;
import android.os.Handler;
import android.os.RemoteException;
import android.util.Log;
-import android.view.Display;
-import android.widget.Toast;
import com.android.systemui.Dependency;
-import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.shared.recents.IOverviewProxy;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.wm.shell.splitscreen.SplitScreen;
import java.util.Optional;
@@ -56,7 +43,6 @@ public class OverviewProxyRecentsImpl implements RecentsImplementation {
private final static String TAG = "OverviewProxyRecentsImpl";
@Nullable
private final Lazy<StatusBar> mStatusBarLazy;
- private final Optional<SplitScreen> mSplitScreenOptional;
private Context mContext;
private Handler mHandler;
@@ -65,10 +51,8 @@ public class OverviewProxyRecentsImpl implements RecentsImplementation {
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
@Inject
- public OverviewProxyRecentsImpl(Optional<Lazy<StatusBar>> statusBarLazy,
- Optional<SplitScreen> splitScreenOptional) {
+ public OverviewProxyRecentsImpl(Optional<Lazy<StatusBar>> statusBarLazy) {
mStatusBarLazy = statusBarLazy.orElse(null);
- mSplitScreenOptional = splitScreenOptional;
}
@Override
@@ -140,42 +124,4 @@ public class OverviewProxyRecentsImpl implements RecentsImplementation {
// Do nothing
}
}
-
- @Override
- public boolean splitPrimaryTask(int stackCreateMode, Rect initialBounds,
- int metricsDockAction) {
- Point realSize = new Point();
- if (initialBounds == null) {
- mContext.getSystemService(DisplayManager.class).getDisplay(Display.DEFAULT_DISPLAY)
- .getRealSize(realSize);
- initialBounds = new Rect(0, 0, realSize.x, realSize.y);
- }
-
- ActivityManager.RunningTaskInfo runningTask =
- ActivityManagerWrapper.getInstance().getRunningTask();
- final int activityType = runningTask != null
- ? runningTask.configuration.windowConfiguration.getActivityType()
- : ACTIVITY_TYPE_UNDEFINED;
- boolean screenPinningActive = ActivityManagerWrapper.getInstance().isScreenPinningActive();
- boolean isRunningTaskInHomeOrRecentsStack =
- activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS;
- if (runningTask != null && !isRunningTaskInHomeOrRecentsStack && !screenPinningActive) {
- if (runningTask.supportsSplitScreenMultiWindow) {
- if (ActivityManagerWrapper.getInstance().setTaskWindowingModeSplitScreenPrimary(
- runningTask.id, stackCreateMode, initialBounds)) {
- mSplitScreenOptional.ifPresent(splitScreen -> {
- splitScreen.onDockedTopTask();
- // The overview service is handling split screen, so just skip the wait
- // for the first draw and notify the divider to start animating now
- splitScreen.onRecentsDrawn();
- });
- return true;
- }
- } else {
- Toast.makeText(mContext, R.string.dock_non_resizeble_failed_to_dock_text,
- Toast.LENGTH_SHORT).show();
- }
- }
- return false;
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 2a976f546ba4..5279a20a67a7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -16,7 +16,6 @@
package com.android.systemui.recents;
-import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
@@ -36,12 +35,14 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_T
import android.annotation.FloatRange;
import android.app.ActivityTaskManager;
+import android.app.PictureInPictureParams;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
+import android.content.pm.ActivityInfo;
import android.graphics.Bitmap;
import android.graphics.Insets;
import android.graphics.Rect;
@@ -65,6 +66,7 @@ import android.view.accessibility.AccessibilityManager;
import androidx.annotation.NonNull;
import com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.internal.util.ScreenshotHelper;
import com.android.systemui.Dumpable;
@@ -75,8 +77,6 @@ import com.android.systemui.navigationbar.NavigationBar;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.navigationbar.NavigationModeController;
-import com.android.systemui.pip.Pip;
-import com.android.systemui.pip.PipAnimationController;
import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
import com.android.systemui.settings.CurrentUserTracker;
import com.android.systemui.shared.recents.IOverviewProxy;
@@ -93,6 +93,9 @@ import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
import com.android.systemui.statusbar.policy.CallbackController;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedEvents;
+import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.pip.PipAnimationController;
+import com.android.wm.shell.pip.phone.PipUtils;
import com.android.wm.shell.splitscreen.SplitScreen;
import java.io.FileDescriptor;
@@ -101,11 +104,13 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.BiConsumer;
+import java.util.function.Consumer;
import javax.inject.Inject;
import dagger.Lazy;
+
/**
* Class to send information from overview to launcher with a binder.
*/
@@ -141,6 +146,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
private Region mActiveNavBarRegion;
+ private IPinnedStackAnimationListener mIPinnedStackAnimationListener;
private IOverviewProxy mOverviewProxy;
private int mConnectionBackoffAttempts;
private boolean mBound;
@@ -155,14 +161,14 @@ public class OverviewProxyService extends CurrentUserTracker implements
private boolean mSupportsRoundedCornersOnWindows;
private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
- private ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() {
-
+ @VisibleForTesting
+ public ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() {
@Override
public void startScreenPinning(int taskId) {
if (!verifyCaller("startScreenPinning")) {
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mHandler.post(() -> {
mStatusBarOptionalLazy.ifPresent(
@@ -179,7 +185,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
if (!verifyCaller("stopScreenPinning")) {
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mHandler.post(() -> {
try {
@@ -199,7 +205,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
if (!verifyCaller("onStatusBarMotionEvent")) {
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
// TODO move this logic to message queue
mStatusBarOptionalLazy.ifPresent(statusBarLazy -> {
@@ -230,26 +236,11 @@ public class OverviewProxyService extends CurrentUserTracker implements
}
@Override
- public void onSplitScreenInvoked() {
- if (!verifyCaller("onSplitScreenInvoked")) {
- return;
- }
- long token = Binder.clearCallingIdentity();
- try {
- mSplitScreenOptional.ifPresent(splitScreen -> {
- splitScreen.onDockedFirstAnimationFrame();
- });
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
public void onOverviewShown(boolean fromHome) {
if (!verifyCaller("onOverviewShown")) {
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mHandler.post(() -> {
for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
@@ -266,7 +257,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
if (!verifyCaller("getNonMinimizedSplitScreenSecondaryBounds")) {
return null;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
return mSplitScreenOptional.map(splitScreen ->
splitScreen.getDividerView().getNonMinimizedSplitScreenSecondaryBounds())
@@ -281,7 +272,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
if (!verifyCaller("setNavBarButtonAlpha")) {
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mNavBarButtonAlpha = alpha;
mHandler.post(() -> notifyNavBarButtonAlphaChanged(alpha, animate));
@@ -300,7 +291,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
if (!verifyCaller("onAssistantProgress")) {
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mHandler.post(() -> notifyAssistantProgress(progress));
} finally {
@@ -313,7 +304,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
if (!verifyCaller("onAssistantGestureCompletion")) {
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mHandler.post(() -> notifyAssistantGestureCompletion(velocity));
} finally {
@@ -326,7 +317,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
if (!verifyCaller("startAssistant")) {
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mHandler.post(() -> notifyStartAssistant(bundle));
} finally {
@@ -339,7 +330,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
if (!verifyCaller("monitorGestureInput")) {
return null;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
final InputMonitor monitor =
InputManager.getInstance().monitorGestureInput(name, displayId);
@@ -357,7 +348,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
if (!verifyCaller("notifyAccessibilityButtonClicked")) {
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
AccessibilityManager.getInstance(mContext)
.notifyAccessibilityButtonClicked(displayId);
@@ -371,7 +362,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
if (!verifyCaller("notifyAccessibilityButtonLongClicked")) {
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
final Intent intent =
new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
@@ -391,7 +382,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
"ByPass setShelfHeight, FEATURE_PICTURE_IN_PICTURE:" + mHasPipFeature);
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mPipOptional.ifPresent(
pip -> pip.setShelfHeight(visible, shelfHeight));
@@ -419,7 +410,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
+ mHasPipFeature);
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mPipOptional.ifPresent(
pip -> pip.setPinnedStackAnimationType(
@@ -436,10 +427,11 @@ public class OverviewProxyService extends CurrentUserTracker implements
+ mHasPipFeature);
return;
}
- long token = Binder.clearCallingIdentity();
+ mIPinnedStackAnimationListener = listener;
+ final long token = Binder.clearCallingIdentity();
try {
mPipOptional.ifPresent(
- pip -> pip.setPinnedStackAnimationListener(listener));
+ pip -> pip.setPinnedStackAnimationListener(mPinnedStackAnimationCallback));
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -450,7 +442,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
if (!verifyCaller("onQuickSwitchToNewTask")) {
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mHandler.post(() -> notifyQuickSwitchToNewTask(rotation));
} finally {
@@ -463,7 +455,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
if (!verifyCaller("startOneHandedMode")) {
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mOneHandedOptional.ifPresent(oneHanded -> oneHanded.startOneHanded());
} finally {
@@ -476,7 +468,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
if (!verifyCaller("stopOneHandedMode")) {
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mOneHandedOptional.ifPresent(oneHanded -> oneHanded.stopOneHanded(
OneHandedEvents.EVENT_ONE_HANDED_TRIGGER_GESTURE_OUT));
@@ -505,7 +497,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
if (!verifyCaller("expandNotificationPanel")) {
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mCommandQueue.handleSystemKey(KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN);
} finally {
@@ -513,6 +505,38 @@ public class OverviewProxyService extends CurrentUserTracker implements
}
}
+ @Override
+ public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
+ PictureInPictureParams pictureInPictureParams,
+ int launcherRotation, int shelfHeight) {
+ if (!verifyCaller("startSwipePipToHome") || !mHasPipFeature) {
+ return null;
+ }
+ final long binderToken = Binder.clearCallingIdentity();
+ try {
+ return mPipOptional.map(pip ->
+ pip.startSwipePipToHome(componentName, activityInfo,
+ pictureInPictureParams, launcherRotation, shelfHeight))
+ .orElse(null);
+ } finally {
+ Binder.restoreCallingIdentity(binderToken);
+ }
+ }
+
+ @Override
+ public void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds) {
+ if (!verifyCaller("stopSwipePipToHome") || !mHasPipFeature) {
+ return;
+ }
+ final long binderToken = Binder.clearCallingIdentity();
+ try {
+ mPipOptional.ifPresent(pip -> pip.stopSwipePipToHome(
+ componentName, destinationBounds));
+ } finally {
+ Binder.restoreCallingIdentity(binderToken);
+ }
+ }
+
private boolean verifyCaller(String reason) {
final int callerId = Binder.getCallingUserHandle().getIdentifier();
if (callerId != mCurrentBoundedUserId) {
@@ -605,6 +629,8 @@ public class OverviewProxyService extends CurrentUserTracker implements
private final StatusBarWindowCallback mStatusBarWindowCallback = this::onStatusBarStateChanged;
private final BiConsumer<Rect, Rect> mSplitScreenBoundsChangeListener =
this::notifySplitScreenBoundsChanged;
+ private final Consumer<Boolean> mPinnedStackAnimationCallback =
+ this::notifyPinnedStackAnimationStarted;
// This is the death handler for the binder from the launcher service
private final IBinder.DeathRecipient mOverviewServiceDeathRcpt
@@ -624,7 +650,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
super(broadcastDispatcher);
mContext = context;
mPipOptional = pipOptional;
- mHasPipFeature = mContext.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE);
+ mHasPipFeature = PipUtils.hasSystemFeature(mContext);
mStatusBarOptionalLazy = statusBarOptionalLazy;
mHandler = new Handler();
mNavBarControllerLazy = navBarControllerLazy;
@@ -734,6 +760,17 @@ public class OverviewProxyService extends CurrentUserTracker implements
}
}
+ private void notifyPinnedStackAnimationStarted(Boolean isAnimationStarted) {
+ if (mIPinnedStackAnimationListener == null) {
+ return;
+ }
+ try {
+ mIPinnedStackAnimationListener.onPinnedStackAnimationStarted();
+ } catch (RemoteException e) {
+ Log.e(TAG_OPS, "Failed to call onPinnedStackAnimationStarted()", e);
+ }
+ }
+
private void onStatusBarStateChanged(boolean keyguardShowing, boolean keyguardOccluded,
boolean bouncerShowing) {
mSysUiState.setFlag(SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING,
@@ -764,7 +801,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
public void cleanupAfterDeath() {
if (mInputFocusTransferStarted) {
- mHandler.post(()-> {
+ mHandler.post(() -> {
mStatusBarOptionalLazy.ifPresent(statusBarLazy -> {
mInputFocusTransferStarted = false;
statusBarLazy.get().onInputFocusTransfer(false, true /* cancel */,
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index df61fd19ad45..6f6dd9cb22c7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -19,7 +19,6 @@ package com.android.systemui.recents;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Configuration;
-import android.graphics.Rect;
import android.provider.Settings;
import com.android.systemui.SystemUI;
@@ -120,17 +119,6 @@ public class Recents extends SystemUI implements CommandQueue.Callbacks {
mImpl.cancelPreloadRecentApps();
}
- public boolean splitPrimaryTask(int stackCreateMode, Rect initialBounds,
- int metricsDockAction) {
- // Ensure the device has been provisioned before allowing the user to interact with
- // recents
- if (!isUserSetup()) {
- return false;
- }
-
- return mImpl.splitPrimaryTask(stackCreateMode, initialBounds, metricsDockAction);
- }
-
/**
* @return whether this device is provisioned and the current user is set up.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImplementation.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImplementation.java
index a641730ac64e..8848dbbda5e7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImplementation.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImplementation.java
@@ -17,7 +17,6 @@ package com.android.systemui.recents;
import android.content.Context;
import android.content.res.Configuration;
-import android.graphics.Rect;
import java.io.PrintWriter;
@@ -35,10 +34,6 @@ public interface RecentsImplementation {
default void showRecentApps(boolean triggeredFromAltTab) {}
default void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {}
default void toggleRecentApps() {}
- default boolean splitPrimaryTask(int stackCreateMode, Rect initialBounds,
- int metricsDockAction) {
- return false;
- }
default void dump(PrintWriter pw) {}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
index 9c5a3de4523a..ccf2598e4f18 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
@@ -67,6 +67,7 @@ import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
import java.io.PrintWriter;
import java.util.Collections;
@@ -365,7 +366,7 @@ public class RecentsOnboarding {
mOverviewProxyListenerRegistered = true;
}
if (!mTaskListenerRegistered) {
- ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskListener);
+ TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskListener);
mTaskListenerRegistered = true;
}
}
@@ -377,7 +378,7 @@ public class RecentsOnboarding {
mOverviewProxyListenerRegistered = false;
}
if (mTaskListenerRegistered) {
- ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskListener);
+ TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskListener);
mTaskListenerRegistered = false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenInternalAudioRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenInternalAudioRecorder.java
index df03c3e08f08..0aa9d4d662a5 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenInternalAudioRecorder.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenInternalAudioRecorder.java
@@ -48,6 +48,7 @@ public class ScreenInternalAudioRecorder {
private long mTotalBytes;
private MediaMuxer mMuxer;
private boolean mMic;
+ private boolean mStarted;
private int mTrackId = -1;
@@ -263,10 +264,14 @@ public class ScreenInternalAudioRecorder {
* start recording
* @throws IllegalStateException if recording fails to initialize
*/
- public void start() throws IllegalStateException {
- if (mThread != null) {
- Log.e(TAG, "a recording is being done in parallel or stop is not called");
+ public synchronized void start() throws IllegalStateException {
+ if (mStarted) {
+ if (mThread == null) {
+ throw new IllegalStateException("Recording stopped and can't restart (single use)");
+ }
+ throw new IllegalStateException("Recording already started");
}
+ mStarted = true;
mAudioRecord.startRecording();
if (mMic) mAudioRecordMic.startRecording();
Log.d(TAG, "channel count " + mAudioRecord.getChannelCount());
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 2b4fa2a23a07..aaa335c25d5d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -69,6 +69,7 @@ import android.view.ViewOutlineProvider;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.AnimationUtils;
@@ -104,7 +105,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
public Bitmap image;
public Consumer<Uri> finisher;
public GlobalScreenshot.ActionsReadyListener mActionsReadyListener;
- public int errorMsgResId;
void clearImage() {
image = null;
@@ -184,6 +184,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
private final WindowManager.LayoutParams mWindowLayoutParams;
private final Display mDisplay;
private final DisplayMetrics mDisplayMetrics;
+ private final AccessibilityManager mAccessibilityManager;
private View mScreenshotLayout;
private ScreenshotSelectorView mScreenshotSelectorView;
@@ -242,6 +243,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
mScreenshotSmartActions = screenshotSmartActions;
mNotificationsController = screenshotNotificationsController;
mUiEventLogger = uiEventLogger;
+ mAccessibilityManager = AccessibilityManager.getInstance(mContext);
reloadAssets();
Configuration config = mContext.getResources().getConfiguration();
@@ -319,8 +321,17 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
Insets visibleInsets, int taskId, int userId, ComponentName topComponent,
Consumer<Uri> finisher, Runnable onComplete) {
// TODO: use task Id, userId, topComponent for smart handler
-
mOnCompleteRunnable = onComplete;
+
+ if (screenshot == null) {
+ Log.e(TAG, "Got null bitmap from screenshot message");
+ mNotificationsController.notifyScreenshotError(
+ R.string.screenshot_failed_to_capture_text);
+ finisher.accept(null);
+ mOnCompleteRunnable.run();
+ return;
+ }
+
if (aspectRatiosMatch(screenshot, visibleInsets, screenshotScreenBounds)) {
saveScreenshot(screenshot, finisher, screenshotScreenBounds, visibleInsets, false);
} else {
@@ -567,12 +578,30 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
.build();
final SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =
SurfaceControl.captureDisplay(captureArgs);
- final Bitmap screenshot = screenshotBuffer == null ? null : screenshotBuffer.asBitmap();
+ Bitmap screenshot = screenshotBuffer == null ? null : screenshotBuffer.asBitmap();
+
+ if (screenshot == null) {
+ Log.e(TAG, "Screenshot bitmap was null");
+ mNotificationsController.notifyScreenshotError(
+ R.string.screenshot_failed_to_capture_text);
+ finisher.accept(null);
+ mOnCompleteRunnable.run();
+ return;
+ }
+
saveScreenshot(screenshot, finisher, screenRect, Insets.NONE, true);
}
private void saveScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect,
Insets screenInsets, boolean showFlash) {
+ if (mAccessibilityManager.isEnabled()) {
+ AccessibilityEvent event =
+ new AccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ event.setContentDescription(
+ mContext.getResources().getString(R.string.screenshot_saving_title));
+ mAccessibilityManager.sendAccessibilityEvent(event);
+ }
+
if (mScreenshotLayout.isAttachedToWindow()) {
// if we didn't already dismiss for another reason
if (mDismissAnimation == null || !mDismissAnimation.isRunning()) {
@@ -583,14 +612,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
mScreenBitmap = screenshot;
- if (mScreenBitmap == null) {
- mNotificationsController.notifyScreenshotError(
- R.string.screenshot_failed_to_capture_text);
- finisher.accept(null);
- mOnCompleteRunnable.run();
- return;
- }
-
if (!isUserSetupComplete()) {
// User setup isn't complete, so we don't want to show any UI beyond a toast, as editing
// and sharing shouldn't be exposed to the user.
@@ -632,7 +653,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
if (imageData.uri == null) {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED);
mNotificationsController.notifyScreenshotError(
- R.string.screenshot_failed_to_capture_text);
+ R.string.screenshot_failed_to_save_text);
} else {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED);
@@ -752,7 +773,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
if (imageData.uri == null) {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED);
mNotificationsController.notifyScreenshotError(
- R.string.screenshot_failed_to_capture_text);
+ R.string.screenshot_failed_to_save_text);
} else {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED);
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index df1d78953f46..f0ea597c458d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -217,13 +217,11 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
mParams.mActionsReadyListener.onActionsReady(mImageData);
mParams.finisher.accept(mImageData.uri);
mParams.image = null;
- mParams.errorMsgResId = 0;
} catch (Exception e) {
// IOException/UnsupportedOperationException may be thrown if external storage is
// not mounted
Slog.e(TAG, "unable to save screenshot", e);
mParams.clearImage();
- mParams.errorMsgResId = R.string.screenshot_failed_to_save_text;
mImageData.reset();
mParams.mActionsReadyListener.onActionsReady(mImageData);
mParams.finisher.accept(null);
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
index 26d408fe4ab7..c7a8fa22d1af 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
@@ -40,6 +40,11 @@ interface UserTracker : UserContentResolverProvider, UserContextProvider {
val userHandle: UserHandle
/**
+ * [UserInfo] for current user
+ */
+ val userInfo: UserInfo
+
+ /**
* List of profiles associated with the current user.
*/
val userProfiles: List<UserInfo>
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
index 4cc0eeee712c..049685f3dda4 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
@@ -82,6 +82,12 @@ class UserTrackerImpl internal constructor(
override val userContentResolver: ContentResolver
get() = userContext.contentResolver
+ override val userInfo: UserInfo
+ get() {
+ val user = userId
+ return userProfiles.first { it.id == user }
+ }
+
/**
* Returns a [List<UserInfo>] of all profiles associated with the current user.
*
diff --git a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
index b9b4f42a66e1..6202057b9ef1 100644
--- a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
@@ -16,9 +16,6 @@
package com.android.systemui.shortcut;
-import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
-import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
-
import android.content.Context;
import android.content.res.Configuration;
import android.os.RemoteException;
@@ -29,7 +26,6 @@ import android.view.WindowManagerGlobal;
import com.android.internal.policy.DividerSnapAlgorithm;
import com.android.systemui.SystemUI;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.recents.Recents;
import com.android.wm.shell.splitscreen.DividerView;
import com.android.wm.shell.splitscreen.SplitScreen;
@@ -46,7 +42,6 @@ public class ShortcutKeyDispatcher extends SystemUI
private static final String TAG = "ShortcutKeyDispatcher";
private final Optional<SplitScreen> mSplitScreenOptional;
- private final Recents mRecents;
private ShortcutKeyServiceProxy mShortcutKeyServiceProxy = new ShortcutKeyServiceProxy(this);
private IWindowManager mWindowManagerService = WindowManagerGlobal.getWindowManagerService();
@@ -60,11 +55,9 @@ public class ShortcutKeyDispatcher extends SystemUI
protected final long SC_DOCK_RIGHT = META_MASK | KeyEvent.KEYCODE_RIGHT_BRACKET;
@Inject
- public ShortcutKeyDispatcher(Context context,
- Optional<SplitScreen> splitScreenOptional, Recents recents) {
+ public ShortcutKeyDispatcher(Context context, Optional<SplitScreen> splitScreenOptional) {
super(context);
mSplitScreenOptional = splitScreenOptional;
- mRecents = recents;
}
/**
@@ -96,8 +89,7 @@ public class ShortcutKeyDispatcher extends SystemUI
}
private void handleDockKey(long shortcutCode) {
- if (mSplitScreenOptional.isPresent()) {
- SplitScreen splitScreen = mSplitScreenOptional.get();
+ mSplitScreenOptional.ifPresent(splitScreen -> {
if (splitScreen.isDividerVisible()) {
// If there is already a docked window, we respond by resizing the docking pane.
DividerView dividerView = splitScreen.getDividerView();
@@ -112,12 +104,9 @@ public class ShortcutKeyDispatcher extends SystemUI
dividerView.stopDragging(target.position, 0f, false /* avoidDismissStart */,
true /* logMetrics */);
return;
+ } else {
+ splitScreen.splitPrimaryTask();
}
- }
-
- // Split the screen
- mRecents.splitPrimaryTask((shortcutCode == SC_DOCK_LEFT)
- ? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT
- : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT, null, -1);
+ });
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 37ae791bf172..dcee9fa9e648 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -58,12 +58,14 @@ import com.android.internal.statusbar.IStatusBar;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.view.AppearanceRegion;
import com.android.systemui.statusbar.CommandQueue.Callbacks;
+import com.android.systemui.statusbar.commandline.CommandRegistry;
import com.android.systemui.statusbar.policy.CallbackController;
import com.android.systemui.tracing.ProtoTracer;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Arrays;
/**
* This class takes the functions from IStatusBar that come in on
@@ -138,6 +140,8 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
private static final int MSG_SUPPRESS_AMBIENT_DISPLAY = 56 << MSG_SHIFT;
private static final int MSG_REQUEST_WINDOW_MAGNIFICATION_CONNECTION = 57 << MSG_SHIFT;
private static final int MSG_HANDLE_WINDOW_MANAGER_LOGGING_COMMAND = 58 << MSG_SHIFT;
+ //TODO(b/169175022) Update name and when feature name is locked.
+ private static final int MSG_EMERGENCY_ACTION_LAUNCH_GESTURE = 59 << MSG_SHIFT;
public static final int FLAG_EXCLUDE_NONE = 0;
public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -159,6 +163,7 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
*/
private int mLastUpdatedImeDisplayId = INVALID_DISPLAY;
private ProtoTracer mProtoTracer;
+ private final @Nullable CommandRegistry mRegistry;
/**
* These methods are called back on the main thread.
@@ -255,6 +260,11 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
default void showAssistDisclosure() { }
default void startAssist(Bundle args) { }
default void onCameraLaunchGestureDetected(int source) { }
+
+ /**
+ * Notifies SysUI that the emergency action gesture was detected.
+ */
+ default void onEmergencyActionLaunchGestureDetected() { }
default void showPictureInPictureMenu() { }
default void setTopAppHidesStatusBar(boolean topAppHidesStatusBar) { }
@@ -368,11 +378,12 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
}
public CommandQueue(Context context) {
- this(context, null);
+ this(context, null, null);
}
- public CommandQueue(Context context, ProtoTracer protoTracer) {
+ public CommandQueue(Context context, ProtoTracer protoTracer, CommandRegistry registry) {
mProtoTracer = protoTracer;
+ mRegistry = registry;
context.getSystemService(DisplayManager.class).registerDisplayListener(this, mHandler);
// We always have default display.
setDisabled(DEFAULT_DISPLAY, DISABLE_NONE, DISABLE2_NONE);
@@ -726,6 +737,14 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
}
@Override
+ public void onEmergencyActionLaunchGestureDetected() {
+ synchronized (mLock) {
+ mHandler.removeMessages(MSG_EMERGENCY_ACTION_LAUNCH_GESTURE);
+ mHandler.obtainMessage(MSG_EMERGENCY_ACTION_LAUNCH_GESTURE).sendToTarget();
+ }
+ }
+
+ @Override
public void addQsTile(ComponentName tile) {
synchronized (mLock) {
mHandler.obtainMessage(MSG_ADD_QS_TILE, tile).sendToTarget();
@@ -1013,6 +1032,34 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
}
}
+ @Override
+ public void passThroughShellCommand(String[] args, ParcelFileDescriptor pfd) {
+ final FileOutputStream fos = new FileOutputStream(pfd.getFileDescriptor());
+ final PrintWriter pw = new PrintWriter(fos);
+ // This is mimicking Binder#dumpAsync, but on this side of the binder. Might be possible
+ // to just throw this work onto the handler just like the other messages
+ Thread thr = new Thread("Sysui.passThroughShellCommand") {
+ public void run() {
+ try {
+ if (mRegistry == null) {
+ return;
+ }
+
+ // Registry blocks this thread until finished
+ mRegistry.onShellCommand(pw, args);
+ } finally {
+ pw.flush();
+ try {
+ // Close the file descriptor so the TransferPipe finishes its thread
+ pfd.close();
+ } catch (Exception e) {
+ }
+ }
+ }
+ };
+ thr.start();
+ }
+
private final class H extends Handler {
private H(Looper l) {
super(l);
@@ -1154,6 +1201,10 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
mCallbacks.get(i).onCameraLaunchGestureDetected(msg.arg1);
}
break;
+ case MSG_EMERGENCY_ACTION_LAUNCH_GESTURE:
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).onEmergencyActionLaunchGestureDetected();
+ }
case MSG_SHOW_PICTURE_IN_PICTURE_MENU:
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).showPictureInPictureMenu();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/MediaTransferManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/MediaTransferManager.java
index ac3523b2fffd..1b1a51b8a57b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/MediaTransferManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/MediaTransferManager.java
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar;
import android.content.Context;
-import android.content.Intent;
import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
@@ -36,10 +35,9 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.media.InfoMediaManager;
import com.android.settingslib.media.LocalMediaManager;
import com.android.settingslib.media.MediaDevice;
-import com.android.settingslib.media.MediaOutputSliceConstants;
import com.android.settingslib.widget.AdaptiveIcon;
import com.android.systemui.Dependency;
-import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -51,7 +49,7 @@ import java.util.List;
*/
public class MediaTransferManager {
private final Context mContext;
- private final ActivityStarter mActivityStarter;
+ private final MediaOutputDialogFactory mMediaOutputDialogFactory;
private MediaDevice mDevice;
private List<View> mViews = new ArrayList<>();
private LocalMediaManager mLocalMediaManager;
@@ -74,12 +72,7 @@ public class MediaTransferManager {
ViewParent parent = view.getParent();
StatusBarNotification statusBarNotification =
getRowForParent(parent).getEntry().getSbn();
- final Intent intent = new Intent()
- .setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT)
- .putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME,
- statusBarNotification.getPackageName());
- mActivityStarter.startActivity(intent, false, true /* dismissShade */,
- Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ mMediaOutputDialogFactory.create(statusBarNotification.getPackageName(), true);
return true;
}
};
@@ -107,7 +100,7 @@ public class MediaTransferManager {
public MediaTransferManager(Context context) {
mContext = context;
- mActivityStarter = Dependency.get(ActivityStarter.class);
+ mMediaOutputDialogFactory = Dependency.get(MediaOutputDialogFactory.class);
LocalBluetoothManager lbm = Dependency.get(LocalBluetoothManager.class);
InfoMediaManager imm = new InfoMediaManager(mContext, null, null, lbm);
mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, null);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 8bf134d9c5b9..bb76ac0b26bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -40,6 +40,9 @@ import android.os.Trace;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.provider.DeviceConfig.Properties;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.NotificationStats;
+import android.service.notification.StatusBarNotification;
import android.util.ArraySet;
import android.util.Log;
import android.view.View;
@@ -52,13 +55,18 @@ import com.android.systemui.Dumpable;
import com.android.systemui.Interpolators;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.media.MediaData;
import com.android.systemui.media.MediaDataManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.dagger.StatusBarModule;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.phone.BiometricUnlockController;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.LockscreenWallpaper;
@@ -77,6 +85,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
import dagger.Lazy;
@@ -106,6 +115,9 @@ public class NotificationMediaManager implements Dumpable {
private final NotificationEntryManager mEntryManager;
private final MediaDataManager mMediaDataManager;
+ private final NotifPipeline mNotifPipeline;
+ private final NotifCollection mNotifCollection;
+ private final boolean mUsingNotifPipeline;
@Nullable
private Lazy<NotificationShadeWindowController> mNotificationShadeWindowController;
@@ -189,6 +201,9 @@ public class NotificationMediaManager implements Dumpable {
NotificationEntryManager notificationEntryManager,
MediaArtworkProcessor mediaArtworkProcessor,
KeyguardBypassController keyguardBypassController,
+ NotifPipeline notifPipeline,
+ NotifCollection notifCollection,
+ FeatureFlags featureFlags,
@Main DelayableExecutor mainExecutor,
DeviceConfigProxy deviceConfig,
MediaDataManager mediaDataManager) {
@@ -206,17 +221,87 @@ public class NotificationMediaManager implements Dumpable {
mEntryManager = notificationEntryManager;
mMainExecutor = mainExecutor;
mMediaDataManager = mediaDataManager;
+ mNotifPipeline = notifPipeline;
+ mNotifCollection = notifCollection;
- notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
+ if (!featureFlags.isNewNotifPipelineRenderingEnabled()) {
+ setupNEM();
+ mUsingNotifPipeline = false;
+ } else {
+ setupNotifPipeline();
+ mUsingNotifPipeline = true;
+ }
+
+ mShowCompactMediaSeekbar = "true".equals(
+ DeviceConfig.getProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.COMPACT_MEDIA_SEEKBAR_ENABLED));
+
+ deviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
+ mContext.getMainExecutor(),
+ mPropertiesChangedListener);
+ }
+
+ private void setupNotifPipeline() {
+ mNotifPipeline.addCollectionListener(new NotifCollectionListener() {
+ @Override
+ public void onEntryAdded(@NonNull NotificationEntry entry) {
+ mMediaDataManager.onNotificationAdded(entry.getKey(), entry.getSbn());
+ }
+
+ @Override
+ public void onEntryUpdated(NotificationEntry entry) {
+ mMediaDataManager.onNotificationAdded(entry.getKey(), entry.getSbn());
+ }
+
+ @Override
+ public void onEntryBind(NotificationEntry entry, StatusBarNotification sbn) {
+ findAndUpdateMediaNotifications();
+ }
+
+ @Override
+ public void onEntryRemoved(@NonNull NotificationEntry entry, int reason) {
+ removeEntry(entry);
+ }
+
+ @Override
+ public void onEntryCleanUp(@NonNull NotificationEntry entry) {
+ removeEntry(entry);
+ }
+ });
+
+ mMediaDataManager.addListener(new MediaDataManager.Listener() {
+ @Override
+ public void onMediaDataLoaded(@NonNull String key,
+ @Nullable String oldKey, @NonNull MediaData data) {
+ }
+
+ @Override
+ public void onMediaDataRemoved(@NonNull String key) {
+ mNotifPipeline.getAllNotifs()
+ .stream()
+ .filter(entry -> Objects.equals(entry.getKey(), key))
+ .findAny()
+ .ifPresent(entry -> {
+ // TODO(b/160713608): "removing" this notification won't happen and
+ // won't send the 'deleteIntent' if the notification is ongoing.
+ mNotifCollection.dismissNotification(entry,
+ getDismissedByUserStats(entry));
+ });
+ }
+ });
+ }
+
+ private void setupNEM() {
+ mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
@Override
public void onPendingEntryAdded(NotificationEntry entry) {
- mediaDataManager.onNotificationAdded(entry.getKey(), entry.getSbn());
+ mMediaDataManager.onNotificationAdded(entry.getKey(), entry.getSbn());
}
@Override
public void onPreEntryUpdated(NotificationEntry entry) {
- mediaDataManager.onNotificationAdded(entry.getKey(), entry.getSbn());
+ mMediaDataManager.onNotificationAdded(entry.getKey(), entry.getSbn());
}
@Override
@@ -231,8 +316,8 @@ public class NotificationMediaManager implements Dumpable {
@Override
public void onEntryRemoved(
- NotificationEntry entry,
- NotificationVisibility visibility,
+ @NonNull NotificationEntry entry,
+ @Nullable NotificationVisibility visibility,
boolean removedByUser,
int reason) {
removeEntry(entry);
@@ -242,20 +327,49 @@ public class NotificationMediaManager implements Dumpable {
// Pending entries are never inflated, and will never generate a call to onEntryRemoved().
// This can happen when notifications are added and canceled before inflation. Add this
// separate listener for cleanup, since media inflation occurs onPendingEntryAdded().
- notificationEntryManager.addCollectionListener(new NotifCollectionListener() {
+ mEntryManager.addCollectionListener(new NotifCollectionListener() {
@Override
public void onEntryCleanUp(@NonNull NotificationEntry entry) {
removeEntry(entry);
}
});
- mShowCompactMediaSeekbar = "true".equals(
- DeviceConfig.getProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.COMPACT_MEDIA_SEEKBAR_ENABLED));
+ mMediaDataManager.addListener(new MediaDataManager.Listener() {
+ @Override
+ public void onMediaDataLoaded(@NonNull String key,
+ @Nullable String oldKey, @NonNull MediaData data) {
+ }
- deviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
- mContext.getMainExecutor(),
- mPropertiesChangedListener);
+ @Override
+ public void onMediaDataRemoved(@NonNull String key) {
+ NotificationEntry entry = mEntryManager.getPendingOrActiveNotif(key);
+ if (entry != null) {
+ // TODO(b/160713608): "removing" this notification won't happen and
+ // won't send the 'deleteIntent' if the notification is ongoing.
+ mEntryManager.performRemoveNotification(entry.getSbn(),
+ getDismissedByUserStats(entry),
+ NotificationListenerService.REASON_CANCEL);
+ }
+ }
+ });
+ }
+
+ private DismissedByUserStats getDismissedByUserStats(NotificationEntry entry) {
+ final int activeNotificationsCount;
+ if (mUsingNotifPipeline) {
+ activeNotificationsCount = mNotifPipeline.getShadeListCount();
+ } else {
+ activeNotificationsCount = mEntryManager.getActiveNotificationsCount();
+ }
+ return new DismissedByUserStats(
+ NotificationStats.DISMISSAL_SHADE, // Add DISMISSAL_MEDIA?
+ NotificationStats.DISMISS_SENTIMENT_NEUTRAL,
+ NotificationVisibility.obtain(
+ entry.getKey(),
+ entry.getRanking().getRank(),
+ activeNotificationsCount,
+ /* visible= */ true,
+ NotificationLogger.getNotificationLocation(entry)));
}
private void removeEntry(NotificationEntry entry) {
@@ -299,14 +413,24 @@ public class NotificationMediaManager implements Dumpable {
if (mMediaNotificationKey == null) {
return null;
}
- synchronized (mEntryManager) {
- NotificationEntry entry = mEntryManager
+ if (mUsingNotifPipeline) {
+ // TODO(b/169655596): Either add O(1) lookup, or cache this icon?
+ return mNotifPipeline.getAllNotifs().stream()
+ .filter(entry -> Objects.equals(entry.getKey(), mMediaNotificationKey))
+ .findAny()
+ .map(entry -> entry.getIcons().getShelfIcon())
+ .map(StatusBarIconView::getSourceIcon)
+ .orElse(null);
+ } else {
+ synchronized (mEntryManager) {
+ NotificationEntry entry = mEntryManager
.getActiveNotificationUnfiltered(mMediaNotificationKey);
- if (entry == null || entry.getIcons().getShelfIcon() == null) {
- return null;
- }
+ if (entry == null || entry.getIcons().getShelfIcon() == null) {
+ return null;
+ }
- return entry.getIcons().getShelfIcon().getSourceIcon();
+ return entry.getIcons().getShelfIcon().getSourceIcon();
+ }
}
}
@@ -321,94 +445,110 @@ public class NotificationMediaManager implements Dumpable {
}
public void findAndUpdateMediaNotifications() {
+ boolean metaDataChanged;
+ if (mUsingNotifPipeline) {
+ // TODO(b/169655907): get the semi-filtered notifications for current user
+ Collection<NotificationEntry> allNotifications = mNotifPipeline.getAllNotifs();
+ metaDataChanged = findPlayingMediaNotification(allNotifications);
+ } else {
+ synchronized (mEntryManager) {
+ Collection<NotificationEntry> allNotifications = mEntryManager.getAllNotifs();
+ metaDataChanged = findPlayingMediaNotification(allNotifications);
+ }
+
+ if (metaDataChanged) {
+ mEntryManager.updateNotifications("NotificationMediaManager - metaDataChanged");
+ }
+
+ }
+ dispatchUpdateMediaMetaData(metaDataChanged, true /* allowEnterAnimation */);
+ }
+
+ /**
+ * Find a notification and media controller associated with the playing media session, and
+ * update this manager's internal state.
+ * @return whether the current MediaMetadata changed (and needs to be announced to listeners).
+ */
+ private boolean findPlayingMediaNotification(
+ @NonNull Collection<NotificationEntry> allNotifications) {
boolean metaDataChanged = false;
+ // Promote the media notification with a controller in 'playing' state, if any.
+ NotificationEntry mediaNotification = null;
+ MediaController controller = null;
+ for (NotificationEntry entry : allNotifications) {
+ if (entry.isMediaNotification()) {
+ final MediaSession.Token token =
+ entry.getSbn().getNotification().extras.getParcelable(
+ Notification.EXTRA_MEDIA_SESSION);
+ if (token != null) {
+ MediaController aController = new MediaController(mContext, token);
+ if (PlaybackState.STATE_PLAYING
+ == getMediaControllerPlaybackState(aController)) {
+ if (DEBUG_MEDIA) {
+ Log.v(TAG, "DEBUG_MEDIA: found mediastyle controller matching "
+ + entry.getSbn().getKey());
+ }
+ mediaNotification = entry;
+ controller = aController;
+ break;
+ }
+ }
+ }
+ }
+ if (mediaNotification == null) {
+ // Still nothing? OK, let's just look for live media sessions and see if they match
+ // one of our notifications. This will catch apps that aren't (yet!) using media
+ // notifications.
+
+ if (mMediaSessionManager != null) {
+ // TODO: Should this really be for all users? It appears that inactive users
+ // can't have active sessions, which would mean it is fine.
+ final List<MediaController> sessions =
+ mMediaSessionManager.getActiveSessionsForUser(null, UserHandle.USER_ALL);
- synchronized (mEntryManager) {
- Collection<NotificationEntry> allNotifications = mEntryManager.getAllNotifs();
-
- // Promote the media notification with a controller in 'playing' state, if any.
- NotificationEntry mediaNotification = null;
- MediaController controller = null;
- for (NotificationEntry entry : allNotifications) {
- if (entry.isMediaNotification()) {
- final MediaSession.Token token =
- entry.getSbn().getNotification().extras.getParcelable(
- Notification.EXTRA_MEDIA_SESSION);
- if (token != null) {
- MediaController aController = new MediaController(mContext, token);
- if (PlaybackState.STATE_PLAYING ==
- getMediaControllerPlaybackState(aController)) {
+ for (MediaController aController : sessions) {
+ // now to see if we have one like this
+ final String pkg = aController.getPackageName();
+
+ for (NotificationEntry entry : allNotifications) {
+ if (entry.getSbn().getPackageName().equals(pkg)) {
if (DEBUG_MEDIA) {
- Log.v(TAG, "DEBUG_MEDIA: found mediastyle controller matching "
+ Log.v(TAG, "DEBUG_MEDIA: found controller matching "
+ entry.getSbn().getKey());
}
- mediaNotification = entry;
controller = aController;
+ mediaNotification = entry;
break;
}
}
}
}
- if (mediaNotification == null) {
- // Still nothing? OK, let's just look for live media sessions and see if they match
- // one of our notifications. This will catch apps that aren't (yet!) using media
- // notifications.
-
- if (mMediaSessionManager != null) {
- // TODO: Should this really be for all users?
- final List<MediaController> sessions
- = mMediaSessionManager.getActiveSessionsForUser(
- null,
- UserHandle.USER_ALL);
-
- for (MediaController aController : sessions) {
- // now to see if we have one like this
- final String pkg = aController.getPackageName();
-
- for (NotificationEntry entry : allNotifications) {
- if (entry.getSbn().getPackageName().equals(pkg)) {
- if (DEBUG_MEDIA) {
- Log.v(TAG, "DEBUG_MEDIA: found controller matching "
- + entry.getSbn().getKey());
- }
- controller = aController;
- mediaNotification = entry;
- break;
- }
- }
- }
- }
- }
-
- if (controller != null && !sameSessions(mMediaController, controller)) {
- // We have a new media session
- clearCurrentMediaNotificationSession();
- mMediaController = controller;
- mMediaController.registerCallback(mMediaListener);
- mMediaMetadata = mMediaController.getMetadata();
- if (DEBUG_MEDIA) {
- Log.v(TAG, "DEBUG_MEDIA: insert listener, found new controller: "
- + mMediaController + ", receive metadata: " + mMediaMetadata);
- }
+ }
- metaDataChanged = true;
+ if (controller != null && !sameSessions(mMediaController, controller)) {
+ // We have a new media session
+ clearCurrentMediaNotificationSession();
+ mMediaController = controller;
+ mMediaController.registerCallback(mMediaListener);
+ mMediaMetadata = mMediaController.getMetadata();
+ if (DEBUG_MEDIA) {
+ Log.v(TAG, "DEBUG_MEDIA: insert listener, found new controller: "
+ + mMediaController + ", receive metadata: " + mMediaMetadata);
}
- if (mediaNotification != null
- && !mediaNotification.getSbn().getKey().equals(mMediaNotificationKey)) {
- mMediaNotificationKey = mediaNotification.getSbn().getKey();
- if (DEBUG_MEDIA) {
- Log.v(TAG, "DEBUG_MEDIA: Found new media notification: key="
- + mMediaNotificationKey);
- }
- }
+ metaDataChanged = true;
}
- if (metaDataChanged) {
- mEntryManager.updateNotifications("NotificationMediaManager - metaDataChanged");
+ if (mediaNotification != null
+ && !mediaNotification.getSbn().getKey().equals(mMediaNotificationKey)) {
+ mMediaNotificationKey = mediaNotification.getSbn().getKey();
+ if (DEBUG_MEDIA) {
+ Log.v(TAG, "DEBUG_MEDIA: Found new media notification: key="
+ + mMediaNotificationKey);
+ }
}
- dispatchUpdateMediaMetaData(metaDataChanged, true /* allowEnterAnimation */);
+ return metaDataChanged;
}
public void clearCurrentMediaNotification() {
@@ -428,7 +568,7 @@ public class NotificationMediaManager implements Dumpable {
}
@Override
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
pw.print(" mMediaSessionManager=");
pw.println(mMediaSessionManager);
pw.print(" mMediaNotificationKey=");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index c1196d65b702..01aa53f14550 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -189,7 +189,11 @@ class NotificationShadeDepthController @Inject constructor(
blurUtils.applyBlur(blurRoot?.viewRootImpl ?: root.viewRootImpl, blur)
val zoomOut = blurUtils.ratioOfBlurRadius(blur)
try {
- wallpaperManager.setWallpaperZoomOut(root.windowToken, zoomOut)
+ if (root.isAttachedToWindow) {
+ wallpaperManager.setWallpaperZoomOut(root.windowToken, zoomOut)
+ } else {
+ Log.i(TAG, "Won't set zoom. Window not attached $root")
+ }
} catch (e: IllegalArgumentException) {
Log.w(TAG, "Can't set zoom. Window is gone: ${root.windowToken}", e)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index 38c7e5c50f63..53179ba4be90 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -26,7 +26,7 @@ import android.view.View;
import android.view.ViewGroup;
import com.android.systemui.R;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.dagger.StatusBarModule;
@@ -47,6 +47,7 @@ import com.android.systemui.util.Assert;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import java.util.Optional;
import java.util.Stack;
/**
@@ -84,7 +85,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
* possible.
*/
private final boolean mAlwaysExpandNonGroupedNotification;
- private final BubbleController mBubbleController;
+ private final Optional<Bubbles> mBubblesOptional;
private final DynamicPrivacyController mDynamicPrivacyController;
private final KeyguardBypassController mBypassController;
private final ForegroundServiceSectionController mFgsSectionController;
@@ -112,7 +113,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
StatusBarStateController statusBarStateController,
NotificationEntryManager notificationEntryManager,
KeyguardBypassController bypassController,
- BubbleController bubbleController,
+ Optional<Bubbles> bubblesOptional,
DynamicPrivacyController privacyController,
ForegroundServiceSectionController fgsSectionController,
DynamicChildBindController dynamicChildBindController,
@@ -130,7 +131,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
Resources res = context.getResources();
mAlwaysExpandNonGroupedNotification =
res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications);
- mBubbleController = bubbleController;
+ mBubblesOptional = bubblesOptional;
mDynamicPrivacyController = privacyController;
mDynamicChildBindController = dynamicChildBindController;
mLowPriorityInflationHelper = lowPriorityInflationHelper;
@@ -157,8 +158,10 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
final int N = activeNotifications.size();
for (int i = 0; i < N; i++) {
NotificationEntry ent = activeNotifications.get(i);
+ final boolean isBubbleNotificationSuppressedFromShade = mBubblesOptional.isPresent()
+ && mBubblesOptional.get().isBubbleNotificationSuppressedFromShade(ent);
if (ent.isRowDismissed() || ent.isRowRemoved()
- || mBubbleController.isBubbleNotificationSuppressedFromShade(ent)
+ || isBubbleNotificationSuppressedFromShade
|| mFgsSectionController.hasEntry(ent)) {
// we don't want to update removed notifications because they could
// temporarily become children if they were isolated before.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandRegistry.kt b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandRegistry.kt
new file mode 100644
index 000000000000..ce0a08cd4ccf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandRegistry.kt
@@ -0,0 +1,204 @@
+/*
+ * 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.commandline
+
+import android.content.Context
+
+import com.android.systemui.Prefs
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+
+import java.io.PrintWriter
+import java.lang.IllegalStateException
+import java.util.concurrent.Executor
+import java.util.concurrent.FutureTask
+
+import javax.inject.Inject
+
+/**
+ * Registry / dispatcher for incoming shell commands. See [StatusBarManagerService] and
+ * [StatusBarShellCommand] for how things are set up. Commands come in here by way of the service
+ * like so:
+ *
+ * `adb shell cmd statusbar <command>`
+ *
+ * Where `cmd statusbar` send the shell command through to StatusBarManagerService, and
+ * <command> is either processed in system server, or sent through to IStatusBar (CommandQueue)
+ */
+@SysUISingleton
+class CommandRegistry @Inject constructor(
+ val context: Context,
+ @Main val mainExecutor: Executor
+) {
+ // To keep the command line parser hermetic, create a new one for every shell command
+ private val commandMap = mutableMapOf<String, CommandWrapper>()
+ private var initialized = false
+
+ /**
+ * Register a [Command] for a given name. The name here is the top-level namespace for
+ * the registered command. A command could look like this for instance:
+ *
+ * `adb shell cmd statusbar notifications list`
+ *
+ * Where `notifications` is the command that signifies which receiver to send the remaining args
+ * to.
+ *
+ * @param command String name of the command to register. Currently does not support aliases
+ * @param receiverFactory Creates an instance of the receiver on every command
+ * @param executor Pass an executor to offload your `receive` to another thread
+ */
+ @Synchronized
+ fun registerCommand(
+ name: String,
+ commandFactory: () -> Command,
+ executor: Executor
+ ) {
+ if (commandMap[name] != null) {
+ throw IllegalStateException("A command is already registered for ($name)")
+ }
+ commandMap[name] = CommandWrapper(commandFactory, executor)
+ }
+
+ /**
+ * Register a [Command] for a given name, to be executed on the main thread.
+ */
+ @Synchronized
+ fun registerCommand(name: String, commandFactory: () -> Command) {
+ registerCommand(name, commandFactory, mainExecutor)
+ }
+
+ /** Unregister a receiver */
+ @Synchronized
+ fun unregisterCommand(command: String) {
+ commandMap.remove(command)
+ }
+
+ private fun initializeCommands() {
+ initialized = true
+ // TODO: Might want a dedicated place for commands without a home. Currently
+ // this is here because Prefs.java is just an interface
+ registerCommand("prefs") { PrefsCommand(context) }
+ }
+
+ /**
+ * Receive a shell command and dispatch to the appropriate [Command]. Blocks until finished.
+ */
+ fun onShellCommand(pw: PrintWriter, args: Array<String>) {
+ if (!initialized) initializeCommands()
+
+ if (args.isEmpty()) {
+ help(pw)
+ return
+ }
+
+ val commandName = args[0]
+ val wrapper = commandMap[commandName]
+
+ if (wrapper == null) {
+ help(pw)
+ return
+ }
+
+ // Create a new instance of the command
+ val command = wrapper.commandFactory()
+
+ // Wrap the receive command in a task so that we can wait for its completion
+ val task = FutureTask<Unit> {
+ command.execute(pw, args.drop(1))
+ }
+
+ wrapper.executor.execute {
+ task.run()
+ }
+
+ // Wait for the future to complete
+ task.get()
+ }
+
+ private fun help(pw: PrintWriter) {
+ pw.println("Usage: adb shell cmd statusbar <command>")
+ pw.println(" known commands:")
+ for (k in commandMap.keys) {
+ pw.println(" $k")
+ }
+ }
+}
+
+private const val TAG = "CommandRegistry"
+
+interface Command {
+ fun execute(pw: PrintWriter, args: List<String>)
+ fun help(pw: PrintWriter)
+}
+
+// Wrap commands in an executor package
+private data class CommandWrapper(val commandFactory: () -> Command, val executor: Executor)
+
+// Commands can go here for now, but they should move outside
+
+private class PrefsCommand(val context: Context) : Command {
+ override fun help(pw: PrintWriter) {
+ pw.println("usage: prefs <command> [args]")
+ pw.println("Available commands:")
+ pw.println(" list-prefs")
+ pw.println(" set-pref <pref name> <value>")
+ }
+
+ override fun execute(pw: PrintWriter, args: List<String>) {
+ if (args.isEmpty()) {
+ help(pw)
+ return
+ }
+
+ val topLevel = args[0]
+
+ when (topLevel) {
+ "list-prefs" -> listPrefs(pw)
+ "set-pref" -> setPref(pw, args.drop(1))
+ else -> help(pw)
+ }
+ }
+
+ private fun listPrefs(pw: PrintWriter) {
+ pw.println("Available keys:")
+ for (field in Prefs.Key::class.java.declaredFields) {
+ pw.print(" ")
+ pw.println(field.get(Prefs.Key::class.java))
+ }
+ }
+
+ /**
+ * Sets a preference from [Prefs]
+ */
+ private fun setPref(pw: PrintWriter, args: List<String>) {
+ if (args.isEmpty()) {
+ pw.println("invalid arguments: $args")
+ return
+ }
+ val pref = args[0]
+
+ when (pref) {
+ Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING -> {
+ val value = Integer.parseInt(args[1])
+ Prefs.putBoolean(context, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING, value != 0)
+ }
+ else -> {
+ pw.println("Cannot set pref ($pref)")
+ }
+ }
+ }
+}
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 d15b8476b3c5..cee9c70f53eb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
@@ -21,13 +21,14 @@ import android.content.Context;
import android.os.Handler;
import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.media.MediaDataManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.ActionClickLogger;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.MediaArtworkProcessor;
import com.android.systemui.statusbar.NotificationClickNotifier;
import com.android.systemui.statusbar.NotificationListener;
@@ -39,10 +40,13 @@ import com.android.systemui.statusbar.NotificationViewHierarchyManager;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.commandline.CommandRegistry;
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
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.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.inflation.LowPriorityInflationHelper;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
@@ -59,6 +63,8 @@ import com.android.systemui.tracing.ProtoTracer;
import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.util.concurrency.DelayableExecutor;
+import java.util.Optional;
+
import dagger.Binds;
import dagger.Lazy;
import dagger.Module;
@@ -108,6 +114,9 @@ public interface StatusBarDependenciesModule {
NotificationEntryManager notificationEntryManager,
MediaArtworkProcessor mediaArtworkProcessor,
KeyguardBypassController keyguardBypassController,
+ NotifPipeline notifPipeline,
+ NotifCollection notifCollection,
+ FeatureFlags featureFlags,
@Main DelayableExecutor mainExecutor,
DeviceConfigProxy deviceConfigProxy,
MediaDataManager mediaDataManager) {
@@ -118,6 +127,9 @@ public interface StatusBarDependenciesModule {
notificationEntryManager,
mediaArtworkProcessor,
keyguardBypassController,
+ notifPipeline,
+ notifCollection,
+ featureFlags,
mainExecutor,
deviceConfigProxy,
mediaDataManager);
@@ -162,7 +174,7 @@ public interface StatusBarDependenciesModule {
StatusBarStateController statusBarStateController,
NotificationEntryManager notificationEntryManager,
KeyguardBypassController bypassController,
- BubbleController bubbleController,
+ Optional<Bubbles> bubblesOptional,
DynamicPrivacyController privacyController,
ForegroundServiceSectionController fgsSectionController,
DynamicChildBindController dynamicChildBindController,
@@ -177,7 +189,7 @@ public interface StatusBarDependenciesModule {
statusBarStateController,
notificationEntryManager,
bypassController,
- bubbleController,
+ bubblesOptional,
privacyController,
fgsSectionController,
dynamicChildBindController,
@@ -190,8 +202,11 @@ public interface StatusBarDependenciesModule {
*/
@Provides
@SysUISingleton
- static CommandQueue provideCommandQueue(Context context, ProtoTracer protoTracer) {
- return new CommandQueue(context, protoTracer);
+ static CommandQueue provideCommandQueue(
+ Context context,
+ ProtoTracer protoTracer,
+ CommandRegistry registry) {
+ return new CommandQueue(context, protoTracer, registry);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
index 433c8b0d361d..ddfa18e65ee0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
@@ -27,10 +27,10 @@ import com.android.internal.widget.ConversationLayout
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.NotificationContentView
import com.android.systemui.statusbar.notification.stack.StackStateAnimator
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy
import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
@@ -85,38 +85,43 @@ class ConversationNotificationManager @Inject constructor(
for (entry in activeConversationEntries) {
if (rankingMap.getRanking(entry.sbn.key, ranking) && ranking.isConversation) {
val important = ranking.channel.isImportantConversation
- val layouts = entry.row?.layouts?.asSequence()
+ var changed = false
+ entry.row?.layouts?.asSequence()
?.flatMap(::getLayouts)
?.mapNotNull { it as? ConversationLayout }
- ?: emptySequence()
- var changed = false
- for (layout in layouts) {
- if (important == layout.isImportantConversation) {
- continue
- }
- changed = true
- if (important && entry.isMarkedForUserTriggeredMovement) {
- // delay this so that it doesn't animate in until after
- // the notif has been moved in the shade
- mainHandler.postDelayed({
- layout.setIsImportantConversation(
- important, true /* animate */)
- }, IMPORTANCE_ANIMATION_DELAY.toLong())
- } else {
- layout.setIsImportantConversation(important)
- }
- }
+ ?.filterNot { it.isImportantConversation == important }
+ ?.forEach { layout ->
+ changed = true
+ if (important && entry.isMarkedForUserTriggeredMovement) {
+ // delay this so that it doesn't animate in until after
+ // the notif has been moved in the shade
+ mainHandler.postDelayed(
+ {
+ layout.setIsImportantConversation(
+ important,
+ true)
+ },
+ IMPORTANCE_ANIMATION_DELAY.toLong())
+ } else {
+ layout.setIsImportantConversation(important, false)
+ }
+ }
if (changed) {
notificationGroupManager.updateIsolation(entry)
+ // ensure that the conversation icon isn't hidden
+ // (ex: if it was showing in the shelf)
+ entry.row?.updateIconVisibilities()
}
}
}
}
override fun onEntryInflated(entry: NotificationEntry) {
- if (!entry.ranking.isConversation) return
+ if (!entry.ranking.isConversation) {
+ return
+ }
fun updateCount(isExpanded: Boolean) {
- if (isExpanded && (!notifPanelCollapsed || entry.isPinnedAndExpanded())) {
+ if (isExpanded && (!notifPanelCollapsed || entry.isPinnedAndExpanded)) {
resetCount(entry.key)
entry.row?.let(::resetBadgeUi)
}
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 d364689a65d4..7d8979ca1129 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
@@ -22,7 +22,7 @@ import android.util.Log;
import android.view.View;
import com.android.systemui.DejankUtils;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -38,19 +38,19 @@ import javax.inject.Inject;
public final class NotificationClicker implements View.OnClickListener {
private static final String TAG = "NotificationClicker";
- private final BubbleController mBubbleController;
private final NotificationClickerLogger mLogger;
- private final Optional<StatusBar> mStatusBar;
+ private final Optional<StatusBar> mStatusBarOptional;
+ private final Optional<Bubbles> mBubblesOptional;
private final NotificationActivityStarter mNotificationActivityStarter;
private NotificationClicker(
- BubbleController bubbleController,
NotificationClickerLogger logger,
- Optional<StatusBar> statusBar,
+ Optional<StatusBar> statusBarOptional,
+ Optional<Bubbles> bubblesOptional,
NotificationActivityStarter notificationActivityStarter) {
- mBubbleController = bubbleController;
mLogger = logger;
- mStatusBar = statusBar;
+ mStatusBarOptional = statusBarOptional;
+ mBubblesOptional = bubblesOptional;
mNotificationActivityStarter = notificationActivityStarter;
}
@@ -61,7 +61,7 @@ public final class NotificationClicker implements View.OnClickListener {
return;
}
- mStatusBar.ifPresent(statusBar -> statusBar.wakeUpIfDozing(
+ mStatusBarOptional.ifPresent(statusBar -> statusBar.wakeUpIfDozing(
SystemClock.uptimeMillis(), v, "NOTIFICATION_CLICK"));
final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
@@ -92,8 +92,8 @@ public final class NotificationClicker implements View.OnClickListener {
row.setJustClicked(true);
DejankUtils.postAfterTraversal(() -> row.setJustClicked(false));
- if (!row.getEntry().isBubble()) {
- mBubbleController.collapseStack();
+ if (!row.getEntry().isBubble() && mBubblesOptional.isPresent()) {
+ mBubblesOptional.get().collapseStack();
}
mNotificationActivityStarter.onNotificationClicked(entry.getSbn(), row);
@@ -118,26 +118,23 @@ public final class NotificationClicker implements View.OnClickListener {
/** 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;
+ public Builder(NotificationClickerLogger logger) {
mLogger = logger;
}
/** Builds an instance. */
public NotificationClicker build(
- Optional<StatusBar> statusBar,
+ Optional<StatusBar> statusBarOptional,
+ Optional<Bubbles> bubblesOptional,
NotificationActivityStarter notificationActivityStarter
) {
return new NotificationClicker(
- mBubbleController,
mLogger,
- statusBar,
+ statusBarOptional,
+ bubblesOptional,
notificationActivityStarter);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index fdfd72489e93..d617dff372da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -291,6 +291,7 @@ public class NotifCollection implements Dumpable {
mLogger.logDismissAll(userId);
try {
+ // TODO(b/169585328): Do not clear media player notifications
mStatusBarService.onClearAllNotifications(userId);
} catch (RemoteException e) {
// system process is dead if we're here.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
index 4ddc1dc8498d..0455b0f18afc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.collection.coordinator;
import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -26,6 +27,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.Di
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
import java.util.HashSet;
+import java.util.Optional;
import java.util.Set;
import javax.inject.Inject;
@@ -54,7 +56,7 @@ import javax.inject.Inject;
public class BubbleCoordinator implements Coordinator {
private static final String TAG = "BubbleCoordinator";
- private final BubbleController mBubbleController;
+ private final Optional<Bubbles> mBubblesOptional;
private final NotifCollection mNotifCollection;
private final Set<String> mInterceptedDismissalEntries = new HashSet<>();
private NotifPipeline mNotifPipeline;
@@ -62,9 +64,9 @@ public class BubbleCoordinator implements Coordinator {
@Inject
public BubbleCoordinator(
- BubbleController bubbleController,
+ Optional<Bubbles> bubblesOptional,
NotifCollection notifCollection) {
- mBubbleController = bubbleController;
+ mBubblesOptional = bubblesOptional;
mNotifCollection = notifCollection;
}
@@ -73,13 +75,17 @@ public class BubbleCoordinator implements Coordinator {
mNotifPipeline = pipeline;
mNotifPipeline.addNotificationDismissInterceptor(mDismissInterceptor);
mNotifPipeline.addFinalizeFilter(mNotifFilter);
- mBubbleController.addNotifCallback(mNotifCallback);
+ if (mBubblesOptional.isPresent()) {
+ mBubblesOptional.get().addNotifCallback(mNotifCallback);
+ }
+
}
private final NotifFilter mNotifFilter = new NotifFilter(TAG) {
@Override
public boolean shouldFilterOut(NotificationEntry entry, long now) {
- return mBubbleController.isBubbleNotificationSuppressedFromShade(entry);
+ return mBubblesOptional.isPresent()
+ && mBubblesOptional.get().isBubbleNotificationSuppressedFromShade(entry);
}
};
@@ -97,7 +103,8 @@ public class BubbleCoordinator implements Coordinator {
@Override
public boolean shouldInterceptDismissal(NotificationEntry entry) {
// for experimental bubbles
- if (mBubbleController.handleDismissalInterception(entry)) {
+ if (mBubblesOptional.isPresent()
+ && mBubblesOptional.get().handleDismissalInterception(entry)) {
mInterceptedDismissalEntries.add(entry.getKey());
return true;
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
index 21d54c85160b..490989dbb39e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
@@ -21,9 +21,8 @@ import android.service.notification.StatusBarNotification;
import android.util.ArraySet;
import android.util.Log;
-import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
@@ -43,6 +42,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
import javax.inject.Inject;
@@ -64,25 +64,20 @@ public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener,
new ArraySet<>();
private final ArraySet<OnGroupChangeListener> mGroupChangeListeners = new ArraySet<>();
private final Lazy<PeopleNotificationIdentifier> mPeopleNotificationIdentifier;
+ private final Optional<Lazy<Bubbles>> mBubblesOptional;
private int mBarState = -1;
private HashMap<String, StatusBarNotification> mIsolatedEntries = new HashMap<>();
private HeadsUpManager mHeadsUpManager;
private boolean mIsUpdatingUnchangedGroup;
- @Nullable private BubbleController mBubbleController = null;
@Inject
public NotificationGroupManagerLegacy(
StatusBarStateController statusBarStateController,
- Lazy<PeopleNotificationIdentifier> peopleNotificationIdentifier) {
+ Lazy<PeopleNotificationIdentifier> peopleNotificationIdentifier,
+ Optional<Lazy<Bubbles>> bubblesOptional) {
statusBarStateController.addCallback(this);
mPeopleNotificationIdentifier = peopleNotificationIdentifier;
- }
-
- private BubbleController getBubbleController() {
- if (mBubbleController == null) {
- mBubbleController = Dependency.get(BubbleController.class);
- }
- return mBubbleController;
+ mBubblesOptional = bubblesOptional;
}
/**
@@ -247,7 +242,8 @@ public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener,
int childCount = 0;
boolean hasBubbles = false;
for (NotificationEntry entry : group.children.values()) {
- if (!getBubbleController().isBubbleNotificationSuppressedFromShade(entry)) {
+ if (mBubblesOptional.isPresent() && !mBubblesOptional.get().get()
+ .isBubbleNotificationSuppressedFromShade(entry)) {
childCount++;
} else {
hasBubbles = true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
index 498b8e884b17..1311e3e756dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
@@ -17,11 +17,13 @@
package com.android.systemui.statusbar.notification.collection.render
import android.annotation.StringRes
+import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.android.systemui.R
-import com.android.systemui.statusbar.notification.dagger.HeaderClick
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.notification.dagger.HeaderClickAction
import com.android.systemui.statusbar.notification.dagger.HeaderText
import com.android.systemui.statusbar.notification.dagger.NodeLabel
import com.android.systemui.statusbar.notification.dagger.SectionHeaderScope
@@ -39,11 +41,19 @@ internal class SectionHeaderNodeControllerImpl @Inject constructor(
@NodeLabel override val nodeLabel: String,
private val layoutInflater: LayoutInflater,
@HeaderText @StringRes private val headerTextResId: Int,
- @HeaderClick private val onHeaderClickListener: View.OnClickListener
+ private val activityStarter: ActivityStarter,
+ @HeaderClickAction private val clickIntentAction: String
) : NodeController, SectionHeaderController {
private var _view: SectionHeaderView? = null
private var clearAllClickListener: View.OnClickListener? = null
+ private val onHeaderClickListener = View.OnClickListener {
+ activityStarter.startActivity(
+ Intent(clickIntentAction),
+ true /* onlyProvisioned */,
+ true /* dismissShade */,
+ Intent.FLAG_ACTIVITY_SINGLE_TOP)
+ }
override fun reinflateView(parent: ViewGroup) {
var oldPos = -1
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
index 22ca4961320c..7babbb40b6c1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
@@ -172,14 +172,19 @@ class ShadeViewDiffer(
private fun treeToMap(tree: NodeSpec): Map<NodeController, NodeSpec> {
val map = mutableMapOf<NodeController, NodeSpec>()
- registerNodes(tree, map)
+ try {
+ registerNodes(tree, map)
+ } catch (ex: DuplicateNodeException) {
+ logger.logDuplicateNodeInTree(tree, ex)
+ throw ex
+ }
return map
}
private fun registerNodes(node: NodeSpec, map: MutableMap<NodeController, NodeSpec>) {
if (map.containsKey(node.controller)) {
- throw RuntimeException("Node ${node.controller.nodeLabel} appears more than once")
+ throw DuplicateNodeException("Node ${node.controller.nodeLabel} appears more than once")
}
map[node.controller] = node
@@ -191,6 +196,8 @@ class ShadeViewDiffer(
}
}
+private class DuplicateNodeException(message: String) : RuntimeException(message)
+
private class ShadeNode(
val controller: NodeController
) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
index 19e156f572d4..d27455004c01 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.collection.render
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.NotificationLog
+import java.lang.RuntimeException
import javax.inject.Inject
class ShadeViewDifferLogger @Inject constructor(
@@ -67,6 +68,15 @@ class ShadeViewDifferLogger @Inject constructor(
"Moving child view $str1 in $str2 to index $int1"
})
}
+
+ fun logDuplicateNodeInTree(node: NodeSpec, ex: RuntimeException) {
+ buffer.log(TAG, LogLevel.ERROR, {
+ str1 = ex.toString()
+ str2 = treeSpecToStr(node)
+ }, {
+ "$str1 when mapping tree: $str2"
+ })
+ }
}
private const val TAG = "NotifViewManager" \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationSectionHeadersModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationSectionHeadersModule.kt
index 179d49cb55a1..2a9cfd034dce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationSectionHeadersModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationSectionHeadersModule.kt
@@ -17,12 +17,9 @@
package com.android.systemui.statusbar.notification.dagger
import android.annotation.StringRes
-import android.content.Intent
import android.provider.Settings
-import android.view.View
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.notification.collection.render.NodeController
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderNodeControllerImpl
@@ -39,18 +36,6 @@ import javax.inject.Scope
object NotificationSectionHeadersModule {
@Provides
- @HeaderClick
- @JvmStatic fun providesOnHeaderClickListener(
- activityStarter: ActivityStarter
- ) = View.OnClickListener {
- activityStarter.startActivity(
- Intent(Settings.ACTION_NOTIFICATION_SETTINGS),
- true /* onlyProvisioned */,
- true /* dismissShade */,
- Intent.FLAG_ACTIVITY_SINGLE_TOP)
- }
-
- @Provides
@IncomingHeader
@SysUISingleton
@JvmStatic fun providesIncomingHeaderSubcomponent(
@@ -58,6 +43,7 @@ object NotificationSectionHeadersModule {
) = builder.get()
.nodeLabel("incoming header")
.headerText(R.string.notification_section_header_incoming)
+ .clickIntentAction(Settings.ACTION_NOTIFICATION_SETTINGS)
.build()
@Provides
@@ -68,6 +54,7 @@ object NotificationSectionHeadersModule {
) = builder.get()
.nodeLabel("alerting header")
.headerText(R.string.notification_section_header_alerting)
+ .clickIntentAction(Settings.ACTION_NOTIFICATION_SETTINGS)
.build()
@Provides
@@ -78,6 +65,7 @@ object NotificationSectionHeadersModule {
) = builder.get()
.nodeLabel("people header")
.headerText(R.string.notification_section_header_conversations)
+ .clickIntentAction(Settings.ACTION_CONVERSATION_SETTINGS)
.build()
@Provides
@@ -88,6 +76,7 @@ object NotificationSectionHeadersModule {
) = builder.get()
.nodeLabel("silent header")
.headerText(R.string.notification_section_header_gentle)
+ .clickIntentAction(Settings.ACTION_NOTIFICATION_SETTINGS)
.build()
@Provides
@@ -151,6 +140,7 @@ interface SectionHeaderControllerSubcomponent {
fun build(): SectionHeaderControllerSubcomponent
@BindsInstance fun nodeLabel(@NodeLabel nodeLabel: String): Builder
@BindsInstance fun headerText(@HeaderText @StringRes headerText: Int): Builder
+ @BindsInstance fun clickIntentAction(@HeaderClickAction clickIntentAction: String): Builder
}
}
@@ -188,7 +178,7 @@ annotation class NodeLabel
@Qualifier
@Retention(AnnotationRetention.BINARY)
-annotation class HeaderClick
+annotation class HeaderClickAction
@Scope
@Retention(AnnotationRetention.BINARY)
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 01333f0a47d5..4fff99b482d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -26,7 +26,7 @@ import android.view.accessibility.AccessibilityManager;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.R;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
@@ -74,6 +74,7 @@ import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.util.leak.LeakDetector;
+import java.util.Optional;
import java.util.concurrent.Executor;
import javax.inject.Provider;
@@ -132,7 +133,7 @@ public interface NotificationsModule {
UserContextProvider contextTracker,
Provider<PriorityOnboardingDialogController.Builder> builderProvider,
AssistantFeedbackController assistantFeedbackController,
- BubbleController bubbleController,
+ Optional<Bubbles> bubblesOptional,
UiEventLogger uiEventLogger,
OnUserInteractionCallback onUserInteractionCallback) {
return new NotificationGutsManager(
@@ -149,7 +150,7 @@ public interface NotificationsModule {
contextTracker,
builderProvider,
assistantFeedbackController,
- bubbleController,
+ bubblesOptional,
uiEventLogger,
onUserInteractionCallback);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt
index 9da8b8a3fd92..049b471aa7cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.init
import android.service.notification.StatusBarNotification
+import com.android.systemui.bubbles.Bubbles
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption
import com.android.systemui.statusbar.NotificationPresenter
import com.android.systemui.statusbar.notification.NotificationActivityStarter
@@ -25,6 +26,7 @@ import com.android.systemui.statusbar.notification.stack.NotificationListContain
import com.android.systemui.statusbar.phone.StatusBar
import java.io.FileDescriptor
import java.io.PrintWriter
+import java.util.Optional
/**
* The master controller for all notifications-related work
@@ -35,6 +37,7 @@ import java.io.PrintWriter
interface NotificationsController {
fun initialize(
statusBar: StatusBar,
+ bubblesOptional: Optional<Bubbles>,
presenter: NotificationPresenter,
listContainer: NotificationListContainer,
notificationActivityStarter: NotificationActivityStarter,
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 9fb292878553..45a5d1044b9a 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,6 +17,7 @@
package com.android.systemui.statusbar.notification.init
import android.service.notification.StatusBarNotification
+import com.android.systemui.bubbles.Bubbles
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption
import com.android.systemui.statusbar.FeatureFlags
@@ -75,6 +76,7 @@ class NotificationsControllerImpl @Inject constructor(
override fun initialize(
statusBar: StatusBar,
+ bubblesOptional: Optional<Bubbles>,
presenter: NotificationPresenter,
listContainer: NotificationListContainer,
notificationActivityStarter: NotificationActivityStarter,
@@ -90,7 +92,8 @@ class NotificationsControllerImpl @Inject constructor(
listController.bind()
notificationRowBinder.setNotificationClicker(
- clickerBuilder.build(Optional.of(statusBar), notificationActivityStarter))
+ clickerBuilder.build(
+ Optional.of(statusBar), bubblesOptional, notificationActivityStarter))
notificationRowBinder.setUpWithPresenter(
presenter,
listContainer,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt
index ded855dd84f9..7569c1bdbb73 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.init
import android.service.notification.StatusBarNotification
+import com.android.systemui.bubbles.Bubbles
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption
import com.android.systemui.statusbar.NotificationListener
import com.android.systemui.statusbar.NotificationPresenter
@@ -26,6 +27,7 @@ import com.android.systemui.statusbar.notification.stack.NotificationListContain
import com.android.systemui.statusbar.phone.StatusBar
import java.io.FileDescriptor
import java.io.PrintWriter
+import java.util.Optional
import javax.inject.Inject
/**
@@ -37,6 +39,7 @@ class NotificationsControllerStub @Inject constructor(
override fun initialize(
statusBar: StatusBar,
+ bubblesOptional: Optional<Bubbles>,
presenter: NotificationPresenter,
listContainer: NotificationListContainer,
notificationActivityStarter: NotificationActivityStarter,
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 f1727ec91c72..094e8661d262 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
@@ -124,6 +124,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
private float mAppearAnimationTranslation;
private int mNormalColor;
private boolean mIsBelowSpeedBump;
+ private long mLastActionUpTime;
private float mNormalBackgroundVisibilityAmount;
private float mDimmedBackgroundFadeInAmount = -1;
@@ -225,6 +226,22 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
return super.onInterceptTouchEvent(ev);
}
+ /** Sets the last action up time this view was touched. */
+ void setLastActionUpTime(long eventTime) {
+ mLastActionUpTime = eventTime;
+ }
+
+ /**
+ * Returns the last action up time. The last time will also be cleared because the source of
+ * action is not only from touch event. That prevents the caller from utilizing the time with
+ * unrelated event. The time can be 0 if the event is unavailable.
+ */
+ public long getAndResetLastActionUpTime() {
+ long lastActionUpTime = mLastActionUpTime;
+ mLastActionUpTime = 0;
+ return lastActionUpTime;
+ }
+
protected boolean disallowSingleClick(MotionEvent ev) {
return false;
}
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 dd30c890e75b..41ce51c2762f 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
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.row;
+import android.os.SystemClock;
import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
@@ -92,6 +93,9 @@ public class ActivatableNotificationViewController {
mBlockNextTouch = false;
return true;
}
+ if (ev.getAction() == MotionEvent.ACTION_UP) {
+ mView.setLastActionUpTime(SystemClock.uptimeMillis());
+ }
if (mNeedsDimming && !mAccessibilityManager.isTouchExplorationEnabled()
&& mView.isInteractive()) {
if (mNeedsDimming && !mView.isDimmed()) {
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 811a72de093c..d8d412bf2d41 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
@@ -72,7 +72,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.bubbles.Bubbles;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -1074,7 +1074,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
return new View.OnClickListener() {
@Override
public void onClick(View v) {
- Dependency.get(BubbleController.class)
+ Dependency.get(Bubbles.class)
.onUserChangedBubble(mEntry, !mEntry.isBubble() /* createBubble */);
mHeadsUpManager.removeNotification(mEntry.getKey(), true /* releaseImmediately */);
}
@@ -1478,8 +1478,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
}
- private void updateIconVisibilities() {
- // The shelficon is never hidden for children in groups
+ /** Refreshes the visibility of notification icons */
+ public void updateIconVisibilities() {
+ // The shelf icon is never hidden for children in groups
boolean visible = !isChildInGroup() && mShelfIconVisible;
for (NotificationContentView l : mLayouts) {
l.setShelfIconVisible(visible);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index 1d72557c6a89..c995e324ecfe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -81,21 +81,27 @@ public class ExpandableNotificationRowController implements NodeController {
private final PeopleNotificationIdentifier mPeopleNotificationIdentifier;
@Inject
- public ExpandableNotificationRowController(ExpandableNotificationRow view,
+ public ExpandableNotificationRowController(
+ ExpandableNotificationRow view,
NotificationListContainer listContainer,
ActivatableNotificationViewController activatableNotificationViewController,
- NotificationMediaManager mediaManager, PluginManager pluginManager,
- SystemClock clock, @AppName String appName, @NotificationKey String notificationKey,
+ NotificationMediaManager mediaManager,
+ PluginManager pluginManager,
+ SystemClock clock,
+ @AppName String appName,
+ @NotificationKey String notificationKey,
KeyguardBypassController keyguardBypassController,
GroupMembershipManager groupMembershipManager,
GroupExpansionManager groupExpansionManager,
RowContentBindStage rowContentBindStage,
- NotificationLogger notificationLogger, HeadsUpManager headsUpManager,
+ NotificationLogger notificationLogger,
+ HeadsUpManager headsUpManager,
ExpandableNotificationRow.OnExpandClickListener onExpandClickListener,
StatusBarStateController statusBarStateController,
NotificationGutsManager notificationGutsManager,
@Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
- OnUserInteractionCallback onUserInteractionCallback, FalsingManager falsingManager,
+ OnUserInteractionCallback onUserInteractionCallback,
+ FalsingManager falsingManager,
PeopleNotificationIdentifier peopleNotificationIdentifier) {
mView = view;
mListContainer = listContainer;
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 9bcac1163acc..c2c4590fa6cb 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
@@ -42,17 +42,15 @@ import com.android.systemui.media.MediaDataManagerKt;
import com.android.systemui.media.MediaFeatureFlag;
import com.android.systemui.statusbar.InflationTask;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
import com.android.systemui.statusbar.notification.InflationException;
import com.android.systemui.statusbar.notification.MediaNotificationProcessor;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.InflatedSmartReplies;
import com.android.systemui.statusbar.policy.InflatedSmartReplies.SmartRepliesAndActions;
-import com.android.systemui.statusbar.policy.SmartReplyConstants;
+import com.android.systemui.statusbar.policy.SmartRepliesAndActionsInflater;
import com.android.systemui.util.Assert;
import java.util.HashMap;
@@ -60,8 +58,6 @@ import java.util.concurrent.Executor;
import javax.inject.Inject;
-import dagger.Lazy;
-
/**
* {@link NotificationContentInflater} binds content to a {@link ExpandableNotificationRow} by
* asynchronously building the content's {@link RemoteViews} and applying it to the row.
@@ -76,27 +72,24 @@ public class NotificationContentInflater implements NotificationRowContentBinder
private final boolean mIsMediaInQS;
private final NotificationRemoteInputManager mRemoteInputManager;
private final NotifRemoteViewCache mRemoteViewCache;
- private final Lazy<SmartReplyConstants> mSmartReplyConstants;
- private final Lazy<SmartReplyController> mSmartReplyController;
private final ConversationNotificationProcessor mConversationProcessor;
private final Executor mBgExecutor;
+ private final SmartRepliesAndActionsInflater mSmartRepliesAndActionsInflater;
@Inject
NotificationContentInflater(
NotifRemoteViewCache remoteViewCache,
NotificationRemoteInputManager remoteInputManager,
- Lazy<SmartReplyConstants> smartReplyConstants,
- Lazy<SmartReplyController> smartReplyController,
ConversationNotificationProcessor conversationProcessor,
MediaFeatureFlag mediaFeatureFlag,
- @Background Executor bgExecutor) {
+ @Background Executor bgExecutor,
+ SmartRepliesAndActionsInflater smartRepliesInflater) {
mRemoteViewCache = remoteViewCache;
mRemoteInputManager = remoteInputManager;
- mSmartReplyConstants = smartReplyConstants;
- mSmartReplyController = smartReplyController;
mConversationProcessor = conversationProcessor;
mIsMediaInQS = mediaFeatureFlag.getEnabled();
mBgExecutor = bgExecutor;
+ mSmartRepliesAndActionsInflater = smartRepliesInflater;
}
@Override
@@ -132,8 +125,6 @@ public class NotificationContentInflater implements NotificationRowContentBinder
contentToBind,
mRemoteViewCache,
entry,
- mSmartReplyConstants.get(),
- mSmartReplyController.get(),
mConversationProcessor,
row,
bindParams.isLowPriority,
@@ -141,7 +132,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder
bindParams.usesIncreasedHeadsUpHeight,
callback,
mRemoteInputManager.getRemoteViewsOnClickHandler(),
- mIsMediaInQS);
+ mIsMediaInQS,
+ mSmartRepliesAndActionsInflater);
if (mInflateSynchronously) {
task.onPostExecute(task.doInBackground());
} else {
@@ -157,17 +149,19 @@ public class NotificationContentInflater implements NotificationRowContentBinder
boolean inflateSynchronously,
@InflationFlag int reInflateFlags,
Notification.Builder builder,
- Context packageContext) {
+ Context packageContext,
+ SmartRepliesAndActionsInflater smartRepliesInflater) {
InflationProgress result = createRemoteViews(reInflateFlags,
builder,
bindParams.isLowPriority,
bindParams.usesIncreasedHeight,
bindParams.usesIncreasedHeadsUpHeight,
packageContext);
+
result = inflateSmartReplyViews(result, reInflateFlags, entry,
- row.getContext(), packageContext, row.getHeadsUpManager(),
- mSmartReplyConstants.get(), mSmartReplyController.get(),
- row.getExistingSmartRepliesAndActions());
+ row.getContext(), packageContext,
+ row.getExistingSmartRepliesAndActions(),
+ smartRepliesInflater);
apply(
mBgExecutor,
@@ -268,22 +262,21 @@ public class NotificationContentInflater implements NotificationRowContentBinder
}
}
- private static InflationProgress inflateSmartReplyViews(InflationProgress result,
- @InflationFlag int reInflateFlags, NotificationEntry entry, Context context,
- Context packageContext, HeadsUpManager headsUpManager,
- SmartReplyConstants smartReplyConstants, SmartReplyController smartReplyController,
- SmartRepliesAndActions previousSmartRepliesAndActions) {
+ private static InflationProgress inflateSmartReplyViews(
+ InflationProgress result,
+ @InflationFlag int reInflateFlags,
+ NotificationEntry entry,
+ Context context,
+ Context packageContext,
+ SmartRepliesAndActions previousSmartRepliesAndActions,
+ SmartRepliesAndActionsInflater inflater) {
if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0 && result.newExpandedView != null) {
- result.expandedInflatedSmartReplies =
- InflatedSmartReplies.inflate(
- context, packageContext, entry, smartReplyConstants,
- smartReplyController, headsUpManager, previousSmartRepliesAndActions);
+ result.expandedInflatedSmartReplies = inflater.inflateSmartReplies(
+ context, packageContext, entry, previousSmartRepliesAndActions);
}
if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0 && result.newHeadsUpView != null) {
- result.headsUpInflatedSmartReplies =
- InflatedSmartReplies.inflate(
- context, packageContext, entry, smartReplyConstants,
- smartReplyController, headsUpManager, previousSmartRepliesAndActions);
+ result.headsUpInflatedSmartReplies = inflater.inflateSmartReplies(
+ context, packageContext, entry, previousSmartRepliesAndActions);
}
return result;
}
@@ -709,8 +702,6 @@ public class NotificationContentInflater implements NotificationRowContentBinder
private final boolean mUsesIncreasedHeadsUpHeight;
private final @InflationFlag int mReInflateFlags;
private final NotifRemoteViewCache mRemoteViewCache;
- private final SmartReplyConstants mSmartReplyConstants;
- private final SmartReplyController mSmartReplyController;
private final Executor mBgExecutor;
private ExpandableNotificationRow mRow;
private Exception mError;
@@ -718,6 +709,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
private CancellationSignal mCancellationSignal;
private final ConversationNotificationProcessor mConversationProcessor;
private final boolean mIsMediaInQS;
+ private final SmartRepliesAndActionsInflater mSmartRepliesInflater;
private AsyncInflationTask(
Executor bgExecutor,
@@ -725,8 +717,6 @@ public class NotificationContentInflater implements NotificationRowContentBinder
@InflationFlag int reInflateFlags,
NotifRemoteViewCache cache,
NotificationEntry entry,
- SmartReplyConstants smartReplyConstants,
- SmartReplyController smartReplyController,
ConversationNotificationProcessor conversationProcessor,
ExpandableNotificationRow row,
boolean isLowPriority,
@@ -734,15 +724,15 @@ public class NotificationContentInflater implements NotificationRowContentBinder
boolean usesIncreasedHeadsUpHeight,
InflationCallback callback,
RemoteViews.OnClickHandler remoteViewClickHandler,
- boolean isMediaFlagEnabled) {
+ boolean isMediaFlagEnabled,
+ SmartRepliesAndActionsInflater smartRepliesInflater) {
mEntry = entry;
mRow = row;
- mSmartReplyConstants = smartReplyConstants;
- mSmartReplyController = smartReplyController;
mBgExecutor = bgExecutor;
mInflateSynchronously = inflateSynchronously;
mReInflateFlags = reInflateFlags;
mRemoteViewCache = cache;
+ mSmartRepliesInflater = smartRepliesInflater;
mContext = mRow.getContext();
mIsLowPriority = isLowPriority;
mUsesIncreasedHeight = usesIncreasedHeight;
@@ -786,10 +776,16 @@ public class NotificationContentInflater implements NotificationRowContentBinder
InflationProgress inflationProgress = createRemoteViews(mReInflateFlags,
recoveredBuilder, mIsLowPriority, mUsesIncreasedHeight,
mUsesIncreasedHeadsUpHeight, packageContext);
- return inflateSmartReplyViews(inflationProgress, mReInflateFlags, mEntry,
- mRow.getContext(), packageContext, mRow.getHeadsUpManager(),
- mSmartReplyConstants, mSmartReplyController,
- mRow.getExistingSmartRepliesAndActions());
+ SmartRepliesAndActions repliesAndActions =
+ mRow.getExistingSmartRepliesAndActions();
+ return inflateSmartReplyViews(
+ inflationProgress,
+ mReInflateFlags,
+ mEntry,
+ mContext,
+ packageContext,
+ repliesAndActions,
+ mSmartRepliesInflater);
} catch (Exception e) {
mError = e;
return null;
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 1de9308a40b1..8a644ed4d3ff 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
@@ -57,6 +57,7 @@ import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewW
import com.android.systemui.statusbar.policy.InflatedSmartReplies;
import com.android.systemui.statusbar.policy.InflatedSmartReplies.SmartRepliesAndActions;
import com.android.systemui.statusbar.policy.RemoteInputView;
+import com.android.systemui.statusbar.policy.SmartRepliesAndActionsInflaterKt;
import com.android.systemui.statusbar.policy.SmartReplyConstants;
import com.android.systemui.statusbar.policy.SmartReplyView;
@@ -1191,23 +1192,31 @@ public class NotificationContentView extends FrameLayout {
View bigContentView = mExpandedChild;
if (bigContentView != null && (bigContentView instanceof ViewGroup)) {
- mMediaTransferManager.applyMediaTransferView((ViewGroup) bigContentView,
- entry);
+ mMediaTransferManager.applyMediaTransferView((ViewGroup) bigContentView, entry);
}
View smallContentView = mContractedChild;
if (smallContentView != null && (smallContentView instanceof ViewGroup)) {
- mMediaTransferManager.applyMediaTransferView((ViewGroup) smallContentView,
- entry);
+ mMediaTransferManager.applyMediaTransferView((ViewGroup) smallContentView, entry);
}
}
+ /**
+ * Returns whether the {@link Notification} represented by entry has a free-form remote input.
+ * Such an input can be used e.g. to implement smart reply buttons - by passing the replies
+ * through the remote input.
+ */
+ public static boolean hasFreeformRemoteInput(NotificationEntry entry) {
+ Notification notification = entry.getSbn().getNotification();
+ return null != notification.findRemoteInputActionPair(true /* freeform */);
+ }
+
private void applyRemoteInputAndSmartReply(final NotificationEntry entry) {
if (mRemoteInputController == null) {
return;
}
- applyRemoteInput(entry, InflatedSmartReplies.hasFreeformRemoteInput(entry));
+ applyRemoteInput(entry, hasFreeformRemoteInput(entry));
if (mExpandedInflatedSmartReplies == null && mHeadsUpInflatedSmartReplies == null) {
if (DEBUG) {
@@ -1438,7 +1447,8 @@ public class NotificationContentView extends FrameLayout {
}
LinearLayout smartReplyContainer = (LinearLayout) smartReplyContainerCandidate;
- if (!InflatedSmartReplies.shouldShowSmartReplyView(entry, smartRepliesAndActions)) {
+ if (!SmartRepliesAndActionsInflaterKt
+ .shouldShowSmartReplyView(entry, smartRepliesAndActions)) {
smartReplyContainer.setVisibility(View.GONE);
return null;
}
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 b19997d15664..07a4a188bc48 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -67,7 +67,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.notification.ConversationIconFactory;
import com.android.systemui.Prefs;
import com.android.systemui.R;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.statusbar.notification.NotificationChannelHelper;
@@ -75,6 +75,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import java.lang.annotation.Retention;
+import java.util.Optional;
import javax.inject.Provider;
@@ -93,7 +94,7 @@ public class NotificationConversationInfo extends LinearLayout implements
private OnUserInteractionCallback mOnUserInteractionCallback;
private Handler mMainHandler;
private Handler mBgHandler;
- private BubbleController mBubbleController;
+ private Optional<Bubbles> mBubblesOptional;
private String mPackageName;
private String mAppName;
private int mAppUid;
@@ -222,7 +223,7 @@ public class NotificationConversationInfo extends LinearLayout implements
@Main Handler mainHandler,
@Background Handler bgHandler,
OnConversationSettingsClickListener onConversationSettingsClickListener,
- BubbleController bubbleController) {
+ Optional<Bubbles> bubblesOptional) {
mSelectedAction = -1;
mINotificationManager = iNotificationManager;
mOnUserInteractionCallback = onUserInteractionCallback;
@@ -241,7 +242,7 @@ public class NotificationConversationInfo extends LinearLayout implements
mIconFactory = conversationIconFactory;
mUserContext = userContext;
mBubbleMetadata = bubbleMetadata;
- mBubbleController = bubbleController;
+ mBubblesOptional = bubblesOptional;
mBuilderProvider = builderProvider;
mMainHandler = mainHandler;
mBgHandler = bgHandler;
@@ -640,9 +641,11 @@ public class NotificationConversationInfo extends LinearLayout implements
mINotificationManager.setBubblesAllowed(mAppPkg, mAppUid,
BUBBLE_PREFERENCE_SELECTED);
}
- post(() -> {
- mBubbleController.onUserChangedImportance(mEntry);
- });
+ if (mBubblesOptional.isPresent()) {
+ post(() -> {
+ mBubblesOptional.get().onUserChangedImportance(mEntry);
+ });
+ }
}
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 7d418f30e4c5..373f20e6ba96 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -47,7 +47,7 @@ import com.android.settingslib.notification.ConversationIconFactory;
import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -70,6 +70,7 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.Optional;
import javax.inject.Provider;
@@ -116,7 +117,7 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
private final Lazy<StatusBar> mStatusBarLazy;
private final Handler mMainHandler;
private final Handler mBgHandler;
- private final BubbleController mBubbleController;
+ private final Optional<Bubbles> mBubblesOptional;
private Runnable mOpenRunnable;
private final INotificationManager mNotificationManager;
private final LauncherApps mLauncherApps;
@@ -141,7 +142,7 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
UserContextProvider contextTracker,
Provider<PriorityOnboardingDialogController.Builder> builderProvider,
AssistantFeedbackController assistantFeedbackController,
- BubbleController bubbleController,
+ Optional<Bubbles> bubblesOptional,
UiEventLogger uiEventLogger,
OnUserInteractionCallback onUserInteractionCallback) {
mContext = context;
@@ -157,7 +158,7 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
mBuilderProvider = builderProvider;
mChannelEditorDialogController = channelEditorDialogController;
mAssistantFeedbackController = assistantFeedbackController;
- mBubbleController = bubbleController;
+ mBubblesOptional = bubblesOptional;
mUiEventLogger = uiEventLogger;
mOnUserInteractionCallback = onUserInteractionCallback;
}
@@ -490,7 +491,7 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
mMainHandler,
mBgHandler,
onConversationSettingsListener,
- mBubbleController);
+ mBubblesOptional);
}
/**
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 fe70c818216e..17f326b69848 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
@@ -147,6 +147,8 @@ class NotificationConversationTemplateViewWrapper constructor(
// hiding the conversationIcon will already do that via its listener.
return
}
+ } else {
+ conversationIconView.isForceHidden = false
}
super.setShelfIconVisible(visible)
}
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 500de2d29d03..93204995c5b0 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
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.stack;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_SILENT;
import static com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.ANCHOR_SCROLLING;
@@ -73,6 +74,7 @@ import android.widget.ScrollView;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.ColorUtils;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.keyguard.KeyguardSliceView;
import com.android.settingslib.Utils;
@@ -97,7 +99,6 @@ import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.ShadeViewRefactor;
import com.android.systemui.statusbar.notification.ShadeViewRefactor.RefactorComponent;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
@@ -260,6 +261,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private boolean mDismissAllInProgress;
private boolean mFadeNotificationsOnDismiss;
private FooterDismissListener mFooterDismissListener;
+ private boolean mFlingAfterUpEvent;
/**
* Was the scroller scrolled to the top when the down motion was observed?
@@ -3789,6 +3791,13 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
float currentOverScrollTop = getCurrentOverScrollAmount(true);
if (currentOverScrollTop == 0.0f || initialVelocity > 0) {
+ mFlingAfterUpEvent = true;
+ setFinishScrollingCallback(() -> {
+ mFlingAfterUpEvent = false;
+ InteractionJankMonitor.getInstance()
+ .end(CUJ_NOTIFICATION_SHADE_SCROLL_FLING);
+ setFinishScrollingCallback(null);
+ });
fling(-initialVelocity);
} else {
onOverScrollFling(false, initialVelocity);
@@ -3840,6 +3849,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
return true;
}
+ boolean isFlingAfterUpEvent() {
+ return mFlingAfterUpEvent;
+ }
+
@ShadeViewRefactor(RefactorComponent.INPUT)
protected boolean isInsideQsContainer(MotionEvent ev) {
return ev.getY() < mQsContainer.getBottom();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 703c214ed3ac..7698133e1521 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.stack;
import static android.service.notification.NotificationStats.DISMISSAL_SHADE;
import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnEmptySpaceClickListener;
@@ -47,6 +48,7 @@ import android.view.WindowInsets;
import android.widget.FrameLayout;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
@@ -1301,7 +1303,8 @@ public class NotificationStackScrollLayoutController {
mView.clearNotifications(ROWS_GENTLE, closeShade);
}
- private void onAnimationEnd(List<ExpandableNotificationRow> viewsToRemove, int selectedRows) {
+ private void onAnimationEnd(List<ExpandableNotificationRow> viewsToRemove,
+ @SelectedRows int selectedRows) {
if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
if (selectedRows == ROWS_ALL) {
mNotifCollection.dismissAllNotifications(
@@ -1334,6 +1337,7 @@ public class NotificationStackScrollLayoutController {
}
if (selectedRows == ROWS_ALL) {
try {
+ // TODO(b/169585328): Do not clear media player notifications
mIStatusBarService.onClearAllNotifications(
mLockscreenUserManager.getCurrentUserId());
} catch (Exception ignored) {
@@ -1556,6 +1560,14 @@ public class NotificationStackScrollLayoutController {
if (ev.getActionMasked() == MotionEvent.ACTION_UP) {
mView.setCheckForLeaveBehind(true);
}
+
+ // When swiping directly on the NSSL, this would only get an onTouchEvent.
+ // We log any touches other than down, which will be captured by onTouchEvent.
+ // In the intercept we only start tracing when it's not a down (otherwise that down
+ // would be duplicated when intercepted).
+ if (scrollWantsIt && ev.getActionMasked() != MotionEvent.ACTION_DOWN) {
+ InteractionJankMonitor.getInstance().begin(CUJ_NOTIFICATION_SHADE_SCROLL_FLING);
+ }
return swipeWantsIt || scrollWantsIt || expandWantsIt;
}
@@ -1611,7 +1623,32 @@ public class NotificationStackScrollLayoutController {
if (ev.getActionMasked() == MotionEvent.ACTION_UP) {
mView.setCheckForLeaveBehind(true);
}
+ traceJankOnTouchEvent(ev.getActionMasked(), scrollerWantsIt);
return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt;
}
+
+ private void traceJankOnTouchEvent(int action, boolean scrollerWantsIt) {
+ // Handle interaction jank monitor cases.
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ if (scrollerWantsIt) {
+ InteractionJankMonitor.getInstance()
+ .begin(CUJ_NOTIFICATION_SHADE_SCROLL_FLING);
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ if (scrollerWantsIt && !mView.isFlingAfterUpEvent()) {
+ InteractionJankMonitor.getInstance()
+ .end(CUJ_NOTIFICATION_SHADE_SCROLL_FLING);
+ }
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ if (scrollerWantsIt) {
+ InteractionJankMonitor.getInstance()
+ .cancel(CUJ_NOTIFICATION_SHADE_SCROLL_FLING);
+ }
+ break;
+ }
+ }
}
}
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 95edfe37fee7..d7a8202d7a4c 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
@@ -29,6 +29,7 @@ import com.android.systemui.R;
import com.android.systemui.statusbar.EmptyShadeView;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.FooterView;
@@ -687,15 +688,27 @@ public class StackScrollAlgorithm {
AmbientState ambientState) {
int childCount = algorithmState.visibleChildren.size();
float childrenOnTop = 0.0f;
+
+ int topHunIndex = -1;
+ for (int i = 0; i < childCount; i++) {
+ ExpandableView child = algorithmState.visibleChildren.get(i);
+ if (child instanceof ActivatableNotificationView
+ && (child.isAboveShelf() || child.showingPulsing())) {
+ topHunIndex = i;
+ break;
+ }
+ }
+
for (int i = childCount - 1; i >= 0; i--) {
childrenOnTop = updateChildZValue(i, childrenOnTop,
- algorithmState, ambientState);
+ algorithmState, ambientState, i == topHunIndex);
}
}
protected float updateChildZValue(int i, float childrenOnTop,
StackScrollAlgorithmState algorithmState,
- AmbientState ambientState) {
+ AmbientState ambientState,
+ boolean shouldElevateHun) {
ExpandableView child = algorithmState.visibleChildren.get(i);
ExpandableViewState childViewState = child.getViewState();
int zDistanceBetweenElements = ambientState.getZDistanceBetweenElements();
@@ -713,8 +726,7 @@ public class StackScrollAlgorithm {
}
childViewState.zTranslation = baseZ
+ childrenOnTop * zDistanceBetweenElements;
- } else if (child == ambientState.getTrackedHeadsUpRow()
- || (i == 0 && (child.isAboveShelf() || child.showingPulsing()))) {
+ } else if (shouldElevateHun) {
// In case this is a new view that has never been measured before, we don't want to
// elevate if we are currently expanded more then the notification
int shelfHeight = ambientState.getShelf() == null ? 0 :
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
index efd6767e66a7..76c5baf6e9f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -169,7 +169,8 @@ public class AutoTileManager implements UserAwareController {
String setting = split[0];
String spec = split[1];
// Populate all the settings. As they may not have been added in other users
- AutoAddSetting s = new AutoAddSetting(mContext, mHandler, setting, spec);
+ AutoAddSetting s = new AutoAddSetting(
+ mContext, mHandler, setting, mCurrentUser.getIdentifier(), spec);
mAutoAddSettingList.add(s);
} else {
Log.w(TAG, "Malformed item in array: " + tile);
@@ -319,8 +320,14 @@ public class AutoTileManager implements UserAwareController {
private class AutoAddSetting extends SecureSetting {
private final String mSpec;
- AutoAddSetting(Context context, Handler handler, String setting, String tileSpec) {
- super(context, handler, setting);
+ AutoAddSetting(
+ Context context,
+ Handler handler,
+ String setting,
+ int userId,
+ String tileSpec
+ ) {
+ super(context, handler, setting, userId);
mSpec = tileSpec;
}
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 b2c1900bf1dc..780e54d56821 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -458,6 +458,7 @@ public final class DozeServiceHost implements DozeHost {
return;
}
mSuppressed = suppressed;
+ mDozeLog.traceDozingSuppressed(mSuppressed);
for (Callback callback : mCallbacks) {
callback.onDozeSuppressedChanged(suppressed);
}
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 d7c176207f5e..bf1118253f8a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -86,8 +86,6 @@ 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.
@@ -565,7 +563,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
}
};
if (!mKeyguardStateController.canDismissLockScreen()) {
- Dependency.get(Executor.class).execute(runnable);
+ Dependency.get(Dependency.BACKGROUND_EXECUTOR).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/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index a3f14ba28dcb..1fdf631a858d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -23,6 +23,7 @@ import android.content.res.Resources;
import android.util.MathUtils;
import com.android.keyguard.KeyguardStatusView;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
@@ -122,6 +123,8 @@ public class KeyguardClockPositionAlgorithm {
*/
private int mUnlockedStackScrollerPadding;
+ private int mLockScreenMode;
+
/**
* Refreshes the dimension values.
*/
@@ -171,6 +174,13 @@ public class KeyguardClockPositionAlgorithm {
result.clockX = (int) interpolate(0, burnInPreventionOffsetX(), mDarkAmount);
}
+ /**
+ * Update lock screen mode for testing different layouts
+ */
+ public void onLockScreenModeChanged(int mode) {
+ mLockScreenMode = mode;
+ }
+
public float getMinStackScrollerPadding() {
return mBypassEnabled ? mUnlockedStackScrollerPadding
: mMinTopMargin + mKeyguardStatusHeight + mClockNotificationsMargin;
@@ -185,6 +195,9 @@ public class KeyguardClockPositionAlgorithm {
}
private int getExpandedPreferredClockY() {
+ if (mLockScreenMode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL) {
+ return mMinTopMargin;
+ }
return (mHasCustomClock && (!mHasVisibleNotifs || mBypassEnabled)) ? getPreferredClockY()
: getExpandedClockPosition();
}
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 3d51854a348c..54fb863b5de7 100755
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
@@ -168,7 +168,7 @@ public class LockIcon extends KeyguardAffordanceView {
int iconRes = isAnim ? getThemedAnimationResId(lockAnimIndex) : getIconForState(newState);
if (!mDrawableCache.contains(iconRes)) {
- mDrawableCache.put(iconRes, getResources().getDrawable(iconRes));
+ mDrawableCache.put(iconRes, getContext().getDrawable(iconRes));
}
return mDrawableCache.get(iconRes);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index bda35fb0a48e..d1c83555c062 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -19,7 +19,7 @@ import com.android.internal.util.ContrastColorUtil;
import com.android.settingslib.Utils;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.demomode.DemoModeController;
@@ -40,6 +40,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
+import java.util.Optional;
import java.util.function.Function;
import javax.inject.Inject;
@@ -65,7 +66,7 @@ public class NotificationIconAreaController implements
private final NotificationWakeUpCoordinator mWakeUpCoordinator;
private final KeyguardBypassController mBypassController;
private final DozeParameters mDozeParameters;
- private final BubbleController mBubbleController;
+ private final Optional<Bubbles> mBubblesOptional;
private final StatusBarWindowController mStatusBarWindowController;
private int mIconSize;
@@ -114,7 +115,7 @@ public class NotificationIconAreaController implements
NotificationMediaManager notificationMediaManager,
NotificationListener notificationListener,
DozeParameters dozeParameters,
- BubbleController bubbleController,
+ Optional<Bubbles> bubblesOptional,
DemoModeController demoModeController,
DarkIconDispatcher darkIconDispatcher,
StatusBarWindowController statusBarWindowController) {
@@ -127,7 +128,7 @@ public class NotificationIconAreaController implements
mWakeUpCoordinator = wakeUpCoordinator;
wakeUpCoordinator.addListener(this);
mBypassController = keyguardBypassController;
- mBubbleController = bubbleController;
+ mBubblesOptional = bubblesOptional;
mDemoModeController = demoModeController;
mDemoModeController.addCallback(this);
mStatusBarWindowController = statusBarWindowController;
@@ -298,7 +299,7 @@ public class NotificationIconAreaController implements
|| !entry.isPulseSuppressed())) {
return false;
}
- if (mBubbleController.isBubbleExpanded(entry)) {
+ if (mBubblesOptional.isPresent() && mBubblesOptional.get().isBubbleExpanded(entry)) {
return false;
}
return true;
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 cd9cc0775c66..86d4ac1cb443 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -18,6 +18,8 @@ package com.android.systemui.statusbar.phone;
import static android.view.View.GONE;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE;
import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
@@ -62,6 +64,7 @@ import android.widget.FrameLayout;
import android.widget.TextView;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.LatencyTracker;
@@ -218,6 +221,11 @@ public class NotificationPanelViewController extends PanelViewController {
new KeyguardUpdateMonitorCallback() {
@Override
+ public void onLockScreenModeChanged(int mode) {
+ mClockPositionAlgorithm.onLockScreenModeChanged(mode);
+ }
+
+ @Override
public void onBiometricAuthenticated(int userId,
BiometricSourceType biometricSourceType,
boolean isStrongBiometric) {
@@ -1139,6 +1147,7 @@ public class NotificationPanelViewController extends PanelViewController {
onQsExpansionStarted();
mInitialHeightOnTouch = mQsExpansionHeight;
mQsTracking = true;
+ traceQsJank(true /* startTracing */, false /* wasCancelled */);
mNotificationStackScrollLayoutController.cancelLongPress();
}
break;
@@ -1170,6 +1179,7 @@ public class NotificationPanelViewController extends PanelViewController {
&& shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) {
mView.getParent().requestDisallowInterceptTouchEvent(true);
mQsTracking = true;
+ traceQsJank(true /* startTracing */, false /* wasCancelled */);
onQsExpansionStarted();
notifyExpandingFinished();
mInitialHeightOnTouch = mQsExpansionHeight;
@@ -1202,6 +1212,19 @@ public class NotificationPanelViewController extends PanelViewController {
&& x < stackScrollerX + mNotificationStackScrollLayoutController.getWidth();
}
+ private void traceQsJank(boolean startTracing, boolean wasCancelled) {
+ InteractionJankMonitor monitor = InteractionJankMonitor.getInstance();
+ if (startTracing) {
+ monitor.begin(CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE);
+ } else {
+ if (wasCancelled) {
+ monitor.cancel(CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE);
+ } else {
+ monitor.end(CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE);
+ }
+ }
+ }
+
private void initDownStates(MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
mOnlyAffordanceInThisMotion = false;
@@ -1315,9 +1338,9 @@ public class NotificationPanelViewController extends PanelViewController {
final int action = event.getActionMasked();
if (action == MotionEvent.ACTION_DOWN && getExpandedFraction() == 1f
&& mBarState != KEYGUARD && !mQsExpanded && mQsExpansionEnabled) {
-
// Down in the empty area while fully expanded - go to QS.
mQsTracking = true;
+ traceQsJank(true /* startTracing */, false /* wasCancelled */);
mConflictingQsExpansionGesture = true;
onQsExpansionStarted();
mInitialHeightOnTouch = mQsExpansionHeight;
@@ -1405,6 +1428,7 @@ public class NotificationPanelViewController extends PanelViewController {
return;
}
mExpectingSynthesizedDown = true;
+ InteractionJankMonitor.getInstance().begin(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
onTrackingStarted();
updatePanelExpanded();
}
@@ -1474,6 +1498,7 @@ public class NotificationPanelViewController extends PanelViewController {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
mQsTracking = true;
+ traceQsJank(true /* startTracing */, false /* wasCancelled */);
mInitialTouchY = y;
mInitialTouchX = x;
onQsExpansionStarted();
@@ -1513,6 +1538,9 @@ public class NotificationPanelViewController extends PanelViewController {
if (fraction != 0f || y >= mInitialTouchY) {
flingQsWithCurrentVelocity(y,
event.getActionMasked() == MotionEvent.ACTION_CANCEL);
+ } else {
+ traceQsJank(false /* startTracing */,
+ event.getActionMasked() == MotionEvent.ACTION_CANCEL);
}
if (mQsVelocityTracker != null) {
mQsVelocityTracker.recycle();
@@ -1893,7 +1921,7 @@ public class NotificationPanelViewController extends PanelViewController {
* @see #flingSettings(float, int, Runnable, boolean)
*/
public void flingSettings(float vel, int type) {
- flingSettings(vel, type, null, false /* isClick */);
+ flingSettings(vel, type, null /* onFinishRunnable */, false /* isClick */);
}
/**
@@ -1923,6 +1951,7 @@ public class NotificationPanelViewController extends PanelViewController {
if (onFinishRunnable != null) {
onFinishRunnable.run();
}
+ traceQsJank(false /* startTracing */, type != FLING_EXPAND /* wasCancelled */);
return;
}
@@ -1947,12 +1976,18 @@ public class NotificationPanelViewController extends PanelViewController {
setQsExpansion((Float) animation.getAnimatedValue());
});
animator.addListener(new AnimatorListenerAdapter() {
+ private boolean mIsCanceled;
@Override
public void onAnimationStart(Animator animation) {
notifyExpandingStarted();
}
@Override
+ public void onAnimationCancel(Animator animation) {
+ mIsCanceled = true;
+ }
+
+ @Override
public void onAnimationEnd(Animator animation) {
mAnimatingQS = false;
notifyExpandingFinished();
@@ -1961,6 +1996,7 @@ public class NotificationPanelViewController extends PanelViewController {
if (onFinishRunnable != null) {
onFinishRunnable.run();
}
+ traceQsJank(false /* startTracing */, mIsCanceled /* wasCancelled */);
}
});
// Let's note that we're animating QS. Moving the animator here will cancel it immediately,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java
index bc80a1a5137d..a4fc3a39c706 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java
@@ -49,6 +49,7 @@ import android.view.WindowInsets;
import android.view.WindowInsetsController;
import android.widget.FrameLayout;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.view.FloatingActionMode;
import com.android.internal.widget.FloatingToolbar;
import com.android.systemui.R;
@@ -145,6 +146,7 @@ public class NotificationShadeWindowView extends FrameLayout {
protected void onAttachedToWindow() {
super.onAttachedToWindow();
setWillNotDraw(!DEBUG);
+ InteractionJankMonitor.getInstance().init(this);
}
@Override
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 271650fabc4b..06fd6564854b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.phone;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE;
import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK;
import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
import static com.android.systemui.classifier.Classifier.UNLOCK;
@@ -40,6 +41,7 @@ import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.animation.Interpolator;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.LatencyTracker;
import com.android.systemui.DejankUtils;
@@ -110,6 +112,7 @@ public abstract class PanelViewController {
private boolean mMotionAborted;
private boolean mUpwardsWhenThresholdReached;
private boolean mAnimatingOnDown;
+ private boolean mHandlingPointerUp;
private ValueAnimator mHeightAnimator;
private ObjectAnimator mPeekAnimator;
@@ -364,6 +367,9 @@ public abstract class PanelViewController {
protected void startExpandMotion(float newX, float newY, boolean startTracking,
float expandedHeight) {
+ if (!mHandlingPointerUp) {
+ InteractionJankMonitor.getInstance().begin(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+ }
mInitialOffsetOnTouch = expandedHeight;
mInitialTouchY = newY;
mInitialTouchX = newX;
@@ -579,6 +585,7 @@ public abstract class PanelViewController {
target = getMaxPanelHeight() - getClearAllHeightWithPadding();
}
if (target == mExpandedHeight || getOverExpansionAmount() > 0f && expand) {
+ InteractionJankMonitor.getInstance().end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
notifyExpandingFinished();
return;
}
@@ -640,7 +647,12 @@ public abstract class PanelViewController {
}
setAnimator(null);
if (!mCancelled) {
+ InteractionJankMonitor.getInstance()
+ .end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
notifyExpandingFinished();
+ } else {
+ InteractionJankMonitor.getInstance()
+ .cancel(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
}
notifyBarPanelExpansionChanged();
}
@@ -1290,7 +1302,9 @@ public abstract class PanelViewController {
final float newY = event.getY(newIndex);
final float newX = event.getX(newIndex);
mTrackingPointer = event.getPointerId(newIndex);
+ mHandlingPointerUp = true;
startExpandMotion(newX, newY, true /* startTracking */, mExpandedHeight);
+ mHandlingPointerUp = false;
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
@@ -1348,6 +1362,12 @@ public abstract class PanelViewController {
case MotionEvent.ACTION_CANCEL:
addMovement(event);
endMotionEvent(event, x, y, false /* forceCancel */);
+ InteractionJankMonitor monitor = InteractionJankMonitor.getInstance();
+ if (event.getActionMasked() == MotionEvent.ACTION_UP) {
+ monitor.end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+ } else {
+ monitor.cancel(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+ }
break;
}
return !mGestureWaitForTouchSlop || mTracking;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index e95cf2806691..4af27877c201 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -34,6 +34,8 @@ import android.view.ViewTreeObserver;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
+import androidx.annotation.Nullable;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.colorextraction.ColorExtractor;
import com.android.internal.colorextraction.ColorExtractor.GradientColors;
@@ -139,6 +141,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
private ScrimView mScrimInFront;
private ScrimView mScrimBehind;
+ @Nullable
private ScrimView mScrimForBubble;
private Runnable mScrimBehindChangeRunnable;
@@ -238,7 +241,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
* Attach the controller to the supplied views.
*/
public void attachViews(
- ScrimView scrimBehind, ScrimView scrimInFront, ScrimView scrimForBubble) {
+ ScrimView scrimBehind, ScrimView scrimInFront, @Nullable ScrimView scrimForBubble) {
mScrimBehind = scrimBehind;
mScrimInFront = scrimInFront;
mScrimForBubble = scrimForBubble;
@@ -258,7 +261,9 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
mScrimBehind.setDefaultFocusHighlightEnabled(false);
mScrimInFront.setDefaultFocusHighlightEnabled(false);
- mScrimForBubble.setDefaultFocusHighlightEnabled(false);
+ if (mScrimForBubble != null) {
+ mScrimForBubble.setDefaultFocusHighlightEnabled(false);
+ }
updateScrims();
mKeyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback);
}
@@ -455,7 +460,11 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
}
}
- private void setOrAdaptCurrentAnimation(View scrim) {
+ private void setOrAdaptCurrentAnimation(@Nullable View scrim) {
+ if (scrim == null) {
+ return;
+ }
+
float alpha = getCurrentScrimAlpha(scrim);
if (isAnimating(scrim)) {
// Adapt current animation.
@@ -606,11 +615,9 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
// Only animate scrim color if the scrim view is actually visible
boolean animateScrimInFront = mScrimInFront.getViewAlpha() != 0 && !mBlankScreen;
boolean animateScrimBehind = mScrimBehind.getViewAlpha() != 0 && !mBlankScreen;
- boolean animateScrimForBubble = mScrimForBubble.getViewAlpha() != 0 && !mBlankScreen;
mScrimInFront.setColors(mColors, animateScrimInFront);
mScrimBehind.setColors(mColors, animateScrimBehind);
- mScrimForBubble.setColors(mColors, animateScrimForBubble);
// Calculate minimum scrim opacity for white or black text.
int textColor = mColors.supportsDarkText() ? Color.BLACK : Color.WHITE;
@@ -632,7 +639,12 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
}
setScrimAlpha(mScrimInFront, mInFrontAlpha);
setScrimAlpha(mScrimBehind, mBehindAlpha);
- setScrimAlpha(mScrimForBubble, mBubbleAlpha);
+
+ if (mScrimForBubble != null) {
+ boolean animateScrimForBubble = mScrimForBubble.getViewAlpha() != 0 && !mBlankScreen;
+ mScrimForBubble.setColors(mColors, animateScrimForBubble);
+ setScrimAlpha(mScrimForBubble, mBubbleAlpha);
+ }
// The animation could have all already finished, let's call onFinished just in case
onFinished();
dispatchScrimsVisible();
@@ -828,12 +840,14 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
mBubbleTint = Color.TRANSPARENT;
updateScrimColor(mScrimInFront, mInFrontAlpha, mInFrontTint);
updateScrimColor(mScrimBehind, mBehindAlpha, mBehindTint);
- updateScrimColor(mScrimForBubble, mBubbleAlpha, mBubbleTint);
+ if (mScrimForBubble != null) {
+ updateScrimColor(mScrimForBubble, mBubbleAlpha, mBubbleTint);
+ }
}
}
- private boolean isAnimating(View scrim) {
- return scrim.getTag(TAG_KEY_ANIM) != null;
+ private boolean isAnimating(@Nullable View scrim) {
+ return scrim != null && scrim.getTag(TAG_KEY_ANIM) != null;
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index 2db36f4a62f8..fc91c16f1a48 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -19,6 +19,8 @@ package com.android.systemui.statusbar.phone;
import android.graphics.Color;
import android.os.Trace;
+import androidx.annotation.Nullable;
+
import com.android.systemui.dock.DockManager;
import com.android.systemui.statusbar.ScrimView;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
@@ -212,7 +214,9 @@ public enum ScrimState {
// Set all scrims black, before they fade transparent.
updateScrimColor(mScrimInFront, 1f /* alpha */, Color.BLACK /* tint */);
updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK /* tint */);
- updateScrimColor(mScrimForBubble, 1f /* alpha */, Color.BLACK /* tint */);
+ if (mScrimForBubble != null) {
+ updateScrimColor(mScrimForBubble, 1f /* alpha */, Color.BLACK /* tint */);
+ }
// Scrims should still be black at the end of the transition.
mFrontTint = Color.BLACK;
@@ -258,7 +262,7 @@ public enum ScrimState {
float mDefaultScrimAlpha;
ScrimView mScrimInFront;
ScrimView mScrimBehind;
- ScrimView mScrimForBubble;
+ @Nullable ScrimView mScrimForBubble;
DozeParameters mDozeParameters;
DockManager mDockManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
index 1ce22194878f..af2f3e55c9ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
@@ -22,7 +22,7 @@ import android.view.ViewTreeObserver;
import android.view.WindowManager;
import com.android.systemui.assist.AssistManager;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
@@ -31,6 +31,7 @@ import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.StatusBarState;
import java.util.ArrayList;
+import java.util.Optional;
import javax.inject.Inject;
@@ -50,7 +51,7 @@ public class ShadeControllerImpl implements ShadeController {
private final int mDisplayId;
protected final Lazy<StatusBar> mStatusBarLazy;
private final Lazy<AssistManager> mAssistManagerLazy;
- private final Lazy<BubbleController> mBubbleControllerLazy;
+ private final Optional<Lazy<Bubbles>> mBubblesOptional;
private final ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>();
@@ -63,7 +64,7 @@ public class ShadeControllerImpl implements ShadeController {
WindowManager windowManager,
Lazy<StatusBar> statusBarLazy,
Lazy<AssistManager> assistManagerLazy,
- Lazy<BubbleController> bubbleControllerLazy
+ Optional<Lazy<Bubbles>> bubblesOptional
) {
mCommandQueue = commandQueue;
mStatusBarStateController = statusBarStateController;
@@ -73,7 +74,7 @@ public class ShadeControllerImpl implements ShadeController {
// TODO: Remove circular reference to StatusBar when possible.
mStatusBarLazy = statusBarLazy;
mAssistManagerLazy = assistManagerLazy;
- mBubbleControllerLazy = bubbleControllerLazy;
+ mBubblesOptional = bubblesOptional;
}
@Override
@@ -133,8 +134,8 @@ public class ShadeControllerImpl implements ShadeController {
getStatusBar().getNotificationShadeWindowViewController().cancelExpandHelper();
getStatusBarView().collapsePanel(true /* animate */, delayed, speedUpFactor);
- } else {
- mBubbleControllerLazy.get().collapseStack();
+ } else if (mBubblesOptional.isPresent()) {
+ mBubblesOptional.get().get().collapseStack();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 8d5e05f8a894..28a22cd6a194 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -16,8 +16,6 @@
package com.android.systemui.statusbar.phone;
-import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
-import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
import static android.app.StatusBarManager.WindowType;
@@ -37,7 +35,6 @@ import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASL
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
import static com.android.systemui.shared.system.WindowManagerWrapper.NAV_BAR_POS_INVALID;
-import static com.android.systemui.shared.system.WindowManagerWrapper.NAV_BAR_POS_LEFT;
import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
@@ -72,6 +69,7 @@ import android.content.IntentFilter;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.PointF;
@@ -147,6 +145,7 @@ import com.android.systemui.SystemUI;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.charging.WirelessChargingAnimation;
import com.android.systemui.classifier.FalsingLog;
import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -154,6 +153,7 @@ import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.demomode.DemoModeCommandReceiver;
import com.android.systemui.demomode.DemoModeController;
+import com.android.systemui.emergency.EmergencyGesture;
import com.android.systemui.fragments.ExtensionFragmentListener;
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.keyguard.DismissCallbackRegistry;
@@ -173,7 +173,6 @@ import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.Snoo
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSFragment;
import com.android.systemui.qs.QSPanel;
-import com.android.systemui.recents.Recents;
import com.android.systemui.recents.ScreenPinningRequest;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.shared.system.WindowManagerWrapper;
@@ -649,7 +648,7 @@ public class StatusBar extends SystemUI implements DemoMode,
protected StatusBarNotificationPresenter mPresenter;
private NotificationActivityStarter mNotificationActivityStarter;
private Lazy<NotificationShadeDepthController> mNotificationShadeDepthControllerLazy;
- private final BubbleController mBubbleController;
+ private final Optional<Bubbles> mBubblesOptional;
private final BubbleController.BubbleExpandListener mBubbleExpandListener;
private ActivityIntentHelper mActivityIntentHelper;
@@ -698,7 +697,7 @@ public class StatusBar extends SystemUI implements DemoMode,
WakefulnessLifecycle wakefulnessLifecycle,
SysuiStatusBarStateController statusBarStateController,
VibratorHelper vibratorHelper,
- BubbleController bubbleController,
+ Optional<Bubbles> bubblesOptional,
VisualStabilityManager visualStabilityManager,
DeviceProvisionedController deviceProvisionedController,
NavigationBarController navigationBarController,
@@ -717,7 +716,6 @@ public class StatusBar extends SystemUI implements DemoMode,
DozeScrimController dozeScrimController,
VolumeComponent volumeComponent,
CommandQueue commandQueue,
- Optional<Recents> recentsOptional,
Provider<StatusBarComponent.Builder> statusBarComponentBuilder,
PluginManager pluginManager,
Optional<SplitScreen> splitScreenOptional,
@@ -778,7 +776,7 @@ public class StatusBar extends SystemUI implements DemoMode,
mWakefulnessLifecycle = wakefulnessLifecycle;
mStatusBarStateController = statusBarStateController;
mVibratorHelper = vibratorHelper;
- mBubbleController = bubbleController;
+ mBubblesOptional = bubblesOptional;
mVisualStabilityManager = visualStabilityManager;
mDeviceProvisionedController = deviceProvisionedController;
mNavigationBarController = navigationBarController;
@@ -798,7 +796,6 @@ public class StatusBar extends SystemUI implements DemoMode,
mNotificationShadeDepthControllerLazy = notificationShadeDepthControllerLazy;
mVolumeComponent = volumeComponent;
mCommandQueue = commandQueue;
- mRecentsOptional = recentsOptional;
mStatusBarComponentBuilder = statusBarComponentBuilder;
mPluginManager = pluginManager;
mSplitScreenOptional = splitScreenOptional;
@@ -824,7 +821,7 @@ public class StatusBar extends SystemUI implements DemoMode,
updateScrimController();
};
-
+ mActivityIntentHelper = new ActivityIntentHelper(mContext);
DateTimeView.setReceiverHandler(timeTickHandler);
}
@@ -834,8 +831,9 @@ public class StatusBar extends SystemUI implements DemoMode,
mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
mUiModeManager = mContext.getSystemService(UiModeManager.class);
mBypassHeadsUpNotifier.setUp();
- mBubbleController.setExpandListener(mBubbleExpandListener);
- mActivityIntentHelper = new ActivityIntentHelper(mContext);
+ if (mBubblesOptional.isPresent()) {
+ mBubblesOptional.get().setExpandListener(mBubbleExpandListener);
+ }
mColorExtractor.addOnColorsChangedListener(this);
mStatusBarStateController.addCallback(this,
@@ -1145,7 +1143,8 @@ public class StatusBar extends SystemUI implements DemoMode,
ScrimView scrimBehind = mNotificationShadeWindowView.findViewById(R.id.scrim_behind);
ScrimView scrimInFront = mNotificationShadeWindowView.findViewById(R.id.scrim_in_front);
- ScrimView scrimForBubble = mBubbleController.getScrimForBubble();
+ ScrimView scrimForBubble = mBubblesOptional.isPresent()
+ ? mBubblesOptional.get().getScrimForBubble() : null;
mScrimController.setScrimVisibleListener(scrimsVisible -> {
mNotificationShadeWindowController.setScrimsVisibility(scrimsVisible);
@@ -1341,6 +1340,7 @@ public class StatusBar extends SystemUI implements DemoMode,
mNotificationsController.initialize(
this,
+ mBubblesOptional,
mPresenter,
mStackScrollerController.getNotificationListContainer(),
mNotificationActivityStarter,
@@ -1542,35 +1542,37 @@ public class StatusBar extends SystemUI implements DemoMode,
}
public boolean toggleSplitScreenMode(int metricsDockAction, int metricsUndockAction) {
- if (!mRecentsOptional.isPresent()) {
+ if (!mSplitScreenOptional.isPresent()) {
return false;
}
- if (mSplitScreenOptional.isPresent()) {
- SplitScreen splitScreen = mSplitScreenOptional.get();
- if (splitScreen.isDividerVisible()) {
- if (splitScreen.isMinimized()
- && !splitScreen.isHomeStackResizable()) {
- // Undocking from the minimized state is not supported
- return false;
- } else {
- splitScreen.onUndockingTask();
- if (metricsUndockAction != -1) {
- mMetricsLogger.action(metricsUndockAction);
- }
- }
- return true;
+ final SplitScreen splitScreen = mSplitScreenOptional.get();
+ if (splitScreen.isDividerVisible()) {
+ if (splitScreen.isMinimized() && !splitScreen.isHomeStackResizable()) {
+ // Undocking from the minimized state is not supported
+ return false;
+ }
+
+ splitScreen.onUndockingTask();
+ if (metricsUndockAction != -1) {
+ mMetricsLogger.action(metricsUndockAction);
}
+ return true;
}
final int navbarPos = WindowManagerWrapper.getInstance().getNavBarPosition(mDisplayId);
if (navbarPos == NAV_BAR_POS_INVALID) {
return false;
}
- int createMode = navbarPos == NAV_BAR_POS_LEFT
- ? SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT
- : SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
- return mRecentsOptional.get().splitPrimaryTask(createMode, null, metricsDockAction);
+
+ if (splitScreen.splitPrimaryTask()) {
+ if (metricsDockAction != -1) {
+ mMetricsLogger.action(metricsDockAction);
+ }
+ return true;
+ }
+
+ return false;
}
/**
@@ -2491,10 +2493,12 @@ public class StatusBar extends SystemUI implements DemoMode,
/** Temporarily hides Bubbles if the status bar is hidden. */
private void updateBubblesVisibility() {
- mBubbleController.onStatusBarVisibilityChanged(
- mStatusBarMode != MODE_LIGHTS_OUT
- && mStatusBarMode != MODE_LIGHTS_OUT_TRANSPARENT
- && !mStatusBarWindowHidden);
+ if (mBubblesOptional.isPresent()) {
+ mBubblesOptional.get().onStatusBarVisibilityChanged(
+ mStatusBarMode != MODE_LIGHTS_OUT
+ && mStatusBarMode != MODE_LIGHTS_OUT_TRANSPARENT
+ && !mStatusBarWindowHidden);
+ }
}
void checkBarMode(@TransitionMode int mode, @WindowVisibleState int windowState,
@@ -2817,8 +2821,8 @@ public class StatusBar extends SystemUI implements DemoMode,
if (mRemoteInputManager.getController() != null) {
mRemoteInputManager.getController().closeRemoteInputs();
}
- if (mBubbleController.isStackExpanded()) {
- mBubbleController.collapseStack();
+ if (mBubblesOptional.isPresent() && mBubblesOptional.get().isStackExpanded()) {
+ mBubblesOptional.get().collapseStack();
}
if (mLockscreenUserManager.isCurrentProfile(getSendingUserId())) {
int flags = CommandQueue.FLAG_EXCLUDE_NONE;
@@ -2833,9 +2837,9 @@ public class StatusBar extends SystemUI implements DemoMode,
if (mNotificationShadeWindowController != null) {
mNotificationShadeWindowController.setNotTouchable(false);
}
- if (mBubbleController.isStackExpanded()) {
+ if (mBubblesOptional.isPresent() && mBubblesOptional.get().isStackExpanded()) {
// Post to main thread handler, since updating the UI.
- mMainThreadHandler.post(() -> mBubbleController.collapseStack());
+ mMainThreadHandler.post(() -> mBubblesOptional.get().collapseStack());
}
finishBarAnimations();
resetUserExpandedStates();
@@ -3535,8 +3539,6 @@ public class StatusBar extends SystemUI implements DemoMode,
if (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED) {
if (mNotificationPanelViewController.canPanelBeCollapsed()) {
mShadeController.animateCollapsePanels();
- } else {
- mBubbleController.performBackPressIfNeeded();
}
return true;
}
@@ -3979,6 +3981,27 @@ public class StatusBar extends SystemUI implements DemoMode,
}
}
+ @Override
+ public void onEmergencyActionLaunchGestureDetected() {
+ // TODO (b/169793384) Polish the panic gesture to be just like its older brother, camera.
+ Intent emergencyIntent = new Intent(EmergencyGesture.ACTION_LAUNCH_EMERGENCY);
+ PackageManager pm = mContext.getPackageManager();
+ ResolveInfo resolveInfo = pm.resolveActivity(emergencyIntent, /*flags=*/0);
+ if (resolveInfo == null) {
+ Log.wtf(TAG, "Couldn't find an app to process the emergency intent.");
+ return;
+ }
+
+ if (mVibrator != null && mVibrator.hasVibrator()) {
+ mVibrator.vibrate(500L);
+ }
+
+ emergencyIntent.setComponent(new ComponentName(resolveInfo.activityInfo.packageName,
+ resolveInfo.activityInfo.name));
+ emergencyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(emergencyIntent, /*dismissShade=*/true);
+ }
+
boolean isCameraAllowedByAdmin() {
if (mDevicePolicyManager.getCameraDisabled(null,
mLockscreenUserManager.getCurrentUserId())) {
@@ -4055,7 +4078,7 @@ public class StatusBar extends SystemUI implements DemoMode,
mScrimController.transitionTo(ScrimState.AOD);
} else if (mIsKeyguard && !unlocking) {
mScrimController.transitionTo(ScrimState.KEYGUARD);
- } else if (mBubbleController.isStackExpanded()) {
+ } else if (mBubblesOptional.isPresent() && mBubblesOptional.get().isStackExpanded()) {
mScrimController.transitionTo(ScrimState.BUBBLE_EXPANDED, mUnlockScrimCallback);
} else {
mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback);
@@ -4114,8 +4137,6 @@ public class StatusBar extends SystemUI implements DemoMode,
protected Display mDisplay;
private int mDisplayId;
- private final Optional<Recents> mRecentsOptional;
-
protected NotificationShelfController mNotificationShelfController;
private final Lazy<AssistManager> mAssistManagerLazy;
@@ -4283,6 +4304,19 @@ public class StatusBar extends SystemUI implements DemoMode,
}
public static Bundle getActivityOptions(@Nullable RemoteAnimationAdapter animationAdapter) {
+ return getDefaultActivityOptions(animationAdapter).toBundle();
+ }
+
+ public static Bundle getActivityOptions(@Nullable RemoteAnimationAdapter animationAdapter,
+ boolean isKeyguardShowing, long eventTime) {
+ ActivityOptions options = getDefaultActivityOptions(animationAdapter);
+ options.setSourceInfo(isKeyguardShowing ? ActivityOptions.SourceInfo.TYPE_LOCKSCREEN
+ : ActivityOptions.SourceInfo.TYPE_NOTIFICATION, eventTime);
+ return options.toBundle();
+ }
+
+ public static ActivityOptions getDefaultActivityOptions(
+ @Nullable RemoteAnimationAdapter animationAdapter) {
ActivityOptions options;
if (animationAdapter != null) {
options = ActivityOptions.makeRemoteAnimation(animationAdapter);
@@ -4292,7 +4326,7 @@ public class StatusBar extends SystemUI implements DemoMode,
// Anything launched from the notification shade should always go into the secondary
// split-screen windowing mode.
options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
- return options.toBundle();
+ return options;
}
void visibilityChanged(boolean visible) {
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 737cdeba797a..256ee2081f41 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -30,6 +30,7 @@ import android.app.TaskStackBuilder;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
+import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
@@ -40,7 +41,6 @@ import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
import android.util.EventLog;
import android.view.RemoteAnimationAdapter;
-import android.view.View;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.statusbar.NotificationVisibility;
@@ -48,7 +48,7 @@ import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.ActivityIntentHelper;
import com.android.systemui.EventLogTags;
import com.android.systemui.assist.AssistManager;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
@@ -77,6 +77,7 @@ import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback
import com.android.systemui.statusbar.policy.HeadsUpUtil;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import java.util.Optional;
import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -103,7 +104,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private final KeyguardManager mKeyguardManager;
private final IDreamManager mDreamManager;
- private final BubbleController mBubbleController;
+ private final Optional<Bubbles> mBubblesOptional;
private final Lazy<AssistManager> mAssistManagerLazy;
private final NotificationRemoteInputManager mRemoteInputManager;
private final GroupMembershipManager mGroupMembershipManager;
@@ -141,7 +142,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
KeyguardManager keyguardManager,
IDreamManager dreamManager,
- BubbleController bubbleController,
+ Optional<Bubbles> bubblesOptional,
Lazy<AssistManager> assistManagerLazy,
NotificationRemoteInputManager remoteInputManager,
GroupMembershipManager groupMembershipManager,
@@ -175,7 +176,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mKeyguardManager = keyguardManager;
mDreamManager = dreamManager;
- mBubbleController = bubbleController;
+ mBubblesOptional = bubblesOptional;
mAssistManagerLazy = assistManagerLazy;
mRemoteInputManager = remoteInputManager;
mGroupMembershipManager = groupMembershipManager;
@@ -386,11 +387,14 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
}
private void expandBubbleStackOnMainThread(NotificationEntry entry) {
+ if (!mBubblesOptional.isPresent()) {
+ return;
+ }
+
if (Looper.getMainLooper().isCurrentThread()) {
- mBubbleController.expandStackAndSelectBubble(entry);
+ mBubblesOptional.get().expandStackAndSelectBubble(entry);
} else {
- mMainThreadHandler.post(
- () -> mBubbleController.expandStackAndSelectBubble(entry));
+ mMainThreadHandler.post(() -> mBubblesOptional.get().expandStackAndSelectBubble(entry));
}
}
@@ -398,7 +402,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
PendingIntent intent,
Intent fillInIntent,
NotificationEntry entry,
- View row,
+ ExpandableNotificationRow row,
boolean wasOccluded,
boolean isActivityIntent) {
RemoteAnimationAdapter adapter = mActivityLaunchAnimator.getLaunchAnimation(row,
@@ -410,8 +414,11 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
.registerRemoteAnimationForNextActivityStart(
intent.getCreatorPackage(), adapter);
}
+ long eventTime = row.getAndResetLastActionUpTime();
+ Bundle options = eventTime > 0 ? getActivityOptions(adapter,
+ mKeyguardStateController.isShowing(), eventTime) : getActivityOptions(adapter);
int launchResult = intent.sendAndReturnResult(mContext, 0, fillInIntent, null,
- null, null, getActivityOptions(adapter));
+ null, null, options);
mMainThreadHandler.post(() -> {
mActivityLaunchAnimator.setLaunchResult(launchResult, isActivityIntent);
});
@@ -602,7 +609,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private final KeyguardManager mKeyguardManager;
private final IDreamManager mDreamManager;
- private final BubbleController mBubbleController;
+ private final Optional<Bubbles> mBubblesOptional;
private final Lazy<AssistManager> mAssistManagerLazy;
private final NotificationRemoteInputManager mRemoteInputManager;
private final GroupMembershipManager mGroupMembershipManager;
@@ -639,7 +646,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
KeyguardManager keyguardManager,
IDreamManager dreamManager,
- BubbleController bubbleController,
+ Optional<Bubbles> bubblesOptional,
Lazy<AssistManager> assistManagerLazy,
NotificationRemoteInputManager remoteInputManager,
GroupMembershipManager groupMembershipManager,
@@ -669,7 +676,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mKeyguardManager = keyguardManager;
mDreamManager = dreamManager;
- mBubbleController = bubbleController;
+ mBubblesOptional = bubblesOptional;
mAssistManagerLazy = assistManagerLazy;
mRemoteInputManager = remoteInputManager;
mGroupMembershipManager = groupMembershipManager;
@@ -725,7 +732,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
mStatusBarKeyguardViewManager,
mKeyguardManager,
mDreamManager,
- mBubbleController,
+ mBubblesOptional,
mAssistManagerLazy,
mRemoteInputManager,
mGroupMembershipManager,
@@ -736,12 +743,10 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
mLockPatternUtils,
mRemoteInputCallback,
mActivityIntentHelper,
-
mFeatureFlags,
mMetricsLogger,
mLogger,
mOnUserInteractionCallback,
-
mStatusBar,
mNotificationPresenter,
mNotificationPanelViewController,
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 b7f83145f477..6d4099b656cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -31,7 +31,7 @@ import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.InitController;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.UiBackground;
@@ -43,7 +43,6 @@ import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.PluginDependencyProvider;
-import com.android.systemui.recents.Recents;
import com.android.systemui.recents.ScreenPinningRequest;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.CommandQueue;
@@ -156,7 +155,7 @@ public interface StatusBarPhoneModule {
WakefulnessLifecycle wakefulnessLifecycle,
SysuiStatusBarStateController statusBarStateController,
VibratorHelper vibratorHelper,
- BubbleController bubbleController,
+ Optional<Bubbles> bubblesOptional,
VisualStabilityManager visualStabilityManager,
DeviceProvisionedController deviceProvisionedController,
NavigationBarController navigationBarController,
@@ -175,7 +174,6 @@ public interface StatusBarPhoneModule {
DozeScrimController dozeScrimController,
VolumeComponent volumeComponent,
CommandQueue commandQueue,
- Optional<Recents> recentsOptional,
Provider<StatusBarComponent.Builder> statusBarComponentBuilder,
PluginManager pluginManager,
Optional<SplitScreen> splitScreenOptional,
@@ -235,7 +233,7 @@ public interface StatusBarPhoneModule {
wakefulnessLifecycle,
statusBarStateController,
vibratorHelper,
- bubbleController,
+ bubblesOptional,
visualStabilityManager,
deviceProvisionedController,
navigationBarController,
@@ -254,7 +252,6 @@ public interface StatusBarPhoneModule {
dozeScrimController,
volumeComponent,
commandQueue,
- recentsOptional,
statusBarComponentBuilder,
pluginManager,
splitScreenOptional,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index c43ad36d4462..82ad00ad7c6d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -20,6 +20,7 @@ import static com.android.systemui.statusbar.notification.row.NotificationRowCon
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.Notification;
import android.content.Context;
import android.content.res.Resources;
import android.database.ContentObserver;
@@ -359,6 +360,11 @@ public abstract class HeadsUpManager extends AlertingNotificationManager {
return false;
}
+ private static boolean isOngoingCallNotif(NotificationEntry entry) {
+ return entry.getSbn().isOngoing() && Notification.CATEGORY_CALL.equals(
+ entry.getSbn().getNotification().category);
+ }
+
/**
* This represents a notification and how long it is in a heads up mode. It also manages its
* lifecycle automatically when created.
@@ -391,6 +397,15 @@ public abstract class HeadsUpManager extends AlertingNotificationManager {
return 1;
}
+ boolean selfCall = isOngoingCallNotif(mEntry);
+ boolean otherCall = isOngoingCallNotif(headsUpEntry.mEntry);
+
+ if (selfCall && !otherCall) {
+ return -1;
+ } else if (!selfCall && otherCall) {
+ return 1;
+ }
+
if (remoteInputActive && !headsUpEntry.remoteInputActive) {
return -1;
} else if (!remoteInputActive && headsUpEntry.remoteInputActive) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplies.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplies.java
index c6ae669d5d08..cbc8405cc057 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplies.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplies.java
@@ -19,27 +19,8 @@ package com.android.systemui.statusbar.policy;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Notification;
-import android.app.RemoteInput;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ResolveInfo;
-import android.os.Build;
-import android.util.Log;
-import android.util.Pair;
import android.widget.Button;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
-import com.android.systemui.Dependency;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
-import com.android.systemui.shared.system.PackageManagerWrapper;
-import com.android.systemui.statusbar.NotificationUiAdjustment;
-import com.android.systemui.statusbar.SmartReplyController;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-
-import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -48,13 +29,11 @@ import java.util.List;
* thread, to later be accessed and modified on the (performance critical) UI thread.
*/
public class InflatedSmartReplies {
- private static final String TAG = "InflatedSmartReplies";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@Nullable private final SmartReplyView mSmartReplyView;
@Nullable private final List<Button> mSmartSuggestionButtons;
@NonNull private final SmartRepliesAndActions mSmartRepliesAndActions;
- private InflatedSmartReplies(
+ public InflatedSmartReplies(
@Nullable SmartReplyView smartReplyView,
@Nullable List<Button> smartSuggestionButtons,
@NonNull SmartRepliesAndActions smartRepliesAndActions) {
@@ -76,206 +55,6 @@ public class InflatedSmartReplies {
}
/**
- * Inflate a SmartReplyView and its smart suggestions.
- */
- public static InflatedSmartReplies inflate(
- Context context,
- Context packageContext,
- NotificationEntry entry,
- SmartReplyConstants smartReplyConstants,
- SmartReplyController smartReplyController,
- HeadsUpManager headsUpManager,
- SmartRepliesAndActions existingSmartRepliesAndActions) {
- SmartRepliesAndActions newSmartRepliesAndActions =
- chooseSmartRepliesAndActions(smartReplyConstants, entry);
- if (!shouldShowSmartReplyView(entry, newSmartRepliesAndActions)) {
- return new InflatedSmartReplies(null /* smartReplyView */,
- null /* smartSuggestionButtons */, newSmartRepliesAndActions);
- }
-
- // Only block clicks if the smart buttons are different from the previous set - to avoid
- // scenarios where a user incorrectly cannot click smart buttons because the notification is
- // updated.
- boolean delayOnClickListener =
- !areSuggestionsSimilar(existingSmartRepliesAndActions, newSmartRepliesAndActions);
-
- SmartReplyView smartReplyView = SmartReplyView.inflate(context);
-
- List<Button> suggestionButtons = new ArrayList<>();
- if (newSmartRepliesAndActions.smartReplies != null) {
- suggestionButtons.addAll(smartReplyView.inflateRepliesFromRemoteInput(
- newSmartRepliesAndActions.smartReplies, smartReplyController, entry,
- delayOnClickListener));
- }
- if (newSmartRepliesAndActions.smartActions != null) {
- suggestionButtons.addAll(
- smartReplyView.inflateSmartActions(packageContext,
- newSmartRepliesAndActions.smartActions, smartReplyController, entry,
- headsUpManager, delayOnClickListener));
- }
-
- return new InflatedSmartReplies(smartReplyView, suggestionButtons,
- newSmartRepliesAndActions);
- }
-
- @VisibleForTesting
- static boolean areSuggestionsSimilar(
- SmartRepliesAndActions left, SmartRepliesAndActions right) {
- if (left == right) return true;
- if (left == null || right == null) return false;
-
- if (!left.getSmartReplies().equals(right.getSmartReplies())) {
- return false;
- }
-
- return !NotificationUiAdjustment.areDifferent(
- left.getSmartActions(), right.getSmartActions());
- }
-
- /**
- * Returns whether we should show the smart reply view and its smart suggestions.
- */
- public static boolean shouldShowSmartReplyView(
- NotificationEntry entry,
- SmartRepliesAndActions smartRepliesAndActions) {
- if (smartRepliesAndActions.smartReplies == null
- && smartRepliesAndActions.smartActions == null) {
- // There are no smart replies and no smart actions.
- return false;
- }
- // If we are showing the spinner we don't want to add the buttons.
- boolean showingSpinner = entry.getSbn().getNotification()
- .extras.getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false);
- if (showingSpinner) {
- return false;
- }
- // If we are keeping the notification around while sending we don't want to add the buttons.
- boolean hideSmartReplies = entry.getSbn().getNotification()
- .extras.getBoolean(Notification.EXTRA_HIDE_SMART_REPLIES, false);
- if (hideSmartReplies) {
- return false;
- }
- return true;
- }
-
- /**
- * Chose what smart replies and smart actions to display. App generated suggestions take
- * precedence. So if the app provides any smart replies, we don't show any
- * replies or actions generated by the NotificationAssistantService (NAS), and if the app
- * provides any smart actions we also don't show any NAS-generated replies or actions.
- */
- @NonNull
- public static SmartRepliesAndActions chooseSmartRepliesAndActions(
- SmartReplyConstants smartReplyConstants,
- final NotificationEntry entry) {
- Notification notification = entry.getSbn().getNotification();
- Pair<RemoteInput, Notification.Action> remoteInputActionPair =
- notification.findRemoteInputActionPair(false /* freeform */);
- Pair<RemoteInput, Notification.Action> freeformRemoteInputActionPair =
- notification.findRemoteInputActionPair(true /* freeform */);
-
- if (!smartReplyConstants.isEnabled()) {
- if (DEBUG) {
- Log.d(TAG, "Smart suggestions not enabled, not adding suggestions for "
- + entry.getSbn().getKey());
- }
- return new SmartRepliesAndActions(null, null);
- }
- // Only use smart replies from the app if they target P or above. We have this check because
- // the smart reply API has been used for other things (Wearables) in the past. The API to
- // add smart actions is new in Q so it doesn't require a target-sdk check.
- boolean enableAppGeneratedSmartReplies = (!smartReplyConstants.requiresTargetingP()
- || entry.targetSdk >= Build.VERSION_CODES.P);
-
- boolean appGeneratedSmartRepliesExist =
- enableAppGeneratedSmartReplies
- && remoteInputActionPair != null
- && !ArrayUtils.isEmpty(remoteInputActionPair.first.getChoices())
- && remoteInputActionPair.second.actionIntent != null;
-
- List<Notification.Action> appGeneratedSmartActions = notification.getContextualActions();
- boolean appGeneratedSmartActionsExist = !appGeneratedSmartActions.isEmpty();
-
- SmartReplyView.SmartReplies smartReplies = null;
- SmartReplyView.SmartActions smartActions = null;
- if (appGeneratedSmartRepliesExist) {
- smartReplies = new SmartReplyView.SmartReplies(
- Arrays.asList(remoteInputActionPair.first.getChoices()),
- remoteInputActionPair.first,
- remoteInputActionPair.second.actionIntent,
- false /* fromAssistant */);
- }
- if (appGeneratedSmartActionsExist) {
- smartActions = new SmartReplyView.SmartActions(appGeneratedSmartActions,
- false /* fromAssistant */);
- }
- // Apps didn't provide any smart replies / actions, use those from NAS (if any).
- if (!appGeneratedSmartRepliesExist && !appGeneratedSmartActionsExist) {
- boolean useGeneratedReplies = !ArrayUtils.isEmpty(entry.getSmartReplies())
- && freeformRemoteInputActionPair != null
- && freeformRemoteInputActionPair.second.getAllowGeneratedReplies()
- && freeformRemoteInputActionPair.second.actionIntent != null;
- if (useGeneratedReplies) {
- smartReplies = new SmartReplyView.SmartReplies(
- entry.getSmartReplies(),
- freeformRemoteInputActionPair.first,
- freeformRemoteInputActionPair.second.actionIntent,
- true /* fromAssistant */);
- }
- boolean useSmartActions = !ArrayUtils.isEmpty(entry.getSmartActions())
- && notification.getAllowSystemGeneratedContextualActions();
- if (useSmartActions) {
- List<Notification.Action> systemGeneratedActions =
- entry.getSmartActions();
- // Filter actions if we're in kiosk-mode - we don't care about screen pinning mode,
- // since notifications aren't shown there anyway.
- ActivityManagerWrapper activityManagerWrapper =
- Dependency.get(ActivityManagerWrapper.class);
- if (activityManagerWrapper.isLockTaskKioskModeActive()) {
- systemGeneratedActions = filterWhiteListedLockTaskApps(systemGeneratedActions);
- }
- smartActions = new SmartReplyView.SmartActions(
- systemGeneratedActions, true /* fromAssistant */);
- }
- }
- return new SmartRepliesAndActions(smartReplies, smartActions);
- }
-
- /**
- * Filter actions so that only actions pointing to whitelisted apps are allowed.
- * This filtering is only meaningful when in lock-task mode.
- */
- private static List<Notification.Action> filterWhiteListedLockTaskApps(
- List<Notification.Action> actions) {
- PackageManagerWrapper packageManagerWrapper = Dependency.get(PackageManagerWrapper.class);
- DevicePolicyManagerWrapper devicePolicyManagerWrapper =
- Dependency.get(DevicePolicyManagerWrapper.class);
- List<Notification.Action> filteredActions = new ArrayList<>();
- for (Notification.Action action : actions) {
- if (action.actionIntent == null) continue;
- Intent intent = action.actionIntent.getIntent();
- // Only allow actions that are explicit (implicit intents are not handled in lock-task
- // mode), and link to whitelisted apps.
- ResolveInfo resolveInfo = packageManagerWrapper.resolveActivity(intent, 0 /* flags */);
- if (resolveInfo != null && devicePolicyManagerWrapper.isLockTaskPermitted(
- resolveInfo.activityInfo.packageName)) {
- filteredActions.add(action);
- }
- }
- return filteredActions;
- }
-
- /**
- * Returns whether the {@link Notification} represented by entry has a free-form remote input.
- * Such an input can be used e.g. to implement smart reply buttons - by passing the replies
- * through the remote input.
- */
- public static boolean hasFreeformRemoteInput(NotificationEntry entry) {
- Notification notification = entry.getSbn().getNotification();
- return null != notification.findRemoteInputActionPair(true /* freeform */);
- }
-
- /**
* A storage for smart replies and smart action.
*/
public static class SmartRepliesAndActions {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
index f52a6e0191a1..f45178cd2230 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
@@ -342,7 +342,7 @@ public class KeyguardUserSwitcher {
}
v.setActivated(true);
}
- switchTo(user);
+ onUserListItemClicked(user);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
index 0fdc80b3d97a..8e8a33fd0d9b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
@@ -20,7 +20,6 @@ import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION;
import static com.android.settingslib.Utils.updateLocationEnabled;
-import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -43,6 +42,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.util.Utils;
import java.util.ArrayList;
@@ -60,6 +60,7 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio
private final Context mContext;
private final AppOpsController mAppOpsController;
private final BootCompleteCache mBootCompleteCache;
+ private final UserTracker mUserTracker;
private final H mHandler;
@@ -68,11 +69,13 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio
@Inject
public LocationControllerImpl(Context context, AppOpsController appOpsController,
@Main Looper mainLooper, @Background Handler backgroundHandler,
- BroadcastDispatcher broadcastDispatcher, BootCompleteCache bootCompleteCache) {
+ BroadcastDispatcher broadcastDispatcher, BootCompleteCache bootCompleteCache,
+ UserTracker userTracker) {
mContext = context;
mAppOpsController = appOpsController;
mBootCompleteCache = bootCompleteCache;
mHandler = new H(mainLooper);
+ mUserTracker = userTracker;
// Register to listen for changes in location settings.
IntentFilter filter = new IntentFilter();
@@ -113,7 +116,7 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio
public boolean setLocationEnabled(boolean enabled) {
// QuickSettings always runs as the owner, so specifically set the settings
// for the current foreground user.
- int currentUserId = ActivityManager.getCurrentUser();
+ int currentUserId = mUserTracker.getUserId();
if (isUserLocationRestricted(currentUserId)) {
return false;
}
@@ -134,7 +137,7 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio
LocationManager locationManager =
(LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
return mBootCompleteCache.isBootComplete() && locationManager.isLocationEnabledForUser(
- UserHandle.of(ActivityManager.getCurrentUser()));
+ mUserTracker.getUserHandle());
}
@Override
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 0d259fc481db..c15560ae9f38 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -151,26 +151,22 @@ public class MobileSignalController extends SignalController<
updateDataSim();
int phoneId = mSubscriptionInfo.getSimSlotIndex();
- mFeatureConnector = new FeatureConnector(mContext, phoneId,
- new FeatureConnector.Listener<ImsManager> () {
- @Override
- public ImsManager getFeatureManager() {
- return ImsManager.getInstance(mContext, phoneId);
- }
-
- @Override
- public void connectionReady(ImsManager manager) throws ImsException {
- Log.d(mTag, "ImsManager: connection ready.");
- mImsManager = manager;
- setListeners();
- }
+ mFeatureConnector = ImsManager.getConnector(
+ mContext, phoneId, "?",
+ new FeatureConnector.Listener<ImsManager> () {
+ @Override
+ public void connectionReady(ImsManager manager) throws ImsException {
+ Log.d(mTag, "ImsManager: connection ready.");
+ mImsManager = manager;
+ setListeners();
+ }
- @Override
- public void connectionUnavailable() {
- Log.d(mTag, "ImsManager: connection unavailable.");
- removeListeners();
- }
- }, "?");
+ @Override
+ public void connectionUnavailable(int reason) {
+ Log.d(mTag, "ImsManager: connection unavailable.");
+ removeListeners();
+ }
+ }, mContext.getMainExecutor());
mObserver = new ContentObserver(new Handler(receiverLooper)) {
@@ -456,8 +452,8 @@ public class MobileSignalController extends SignalController<
}
try {
- mImsManager.addCapabilitiesCallback(mCapabilityCallback);
- mImsManager.addRegistrationCallback(mImsRegistrationCallback);
+ mImsManager.addCapabilitiesCallback(mCapabilityCallback, mContext.getMainExecutor());
+ mImsManager.addRegistrationCallback(mImsRegistrationCallback, mContext.getMainExecutor());
Log.d(mTag, "addCapabilitiesCallback " + mCapabilityCallback + " into " + mImsManager);
Log.d(mTag, "addRegistrationCallback " + mImsRegistrationCallback
+ " into " + mImsManager);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartRepliesAndActionsInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartRepliesAndActionsInflater.kt
new file mode 100644
index 000000000000..6a3a69c0419e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartRepliesAndActionsInflater.kt
@@ -0,0 +1,464 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy
+
+import android.app.Notification
+import android.app.PendingIntent
+import android.app.RemoteInput
+import android.content.Context
+import android.content.Intent
+import android.os.Build
+import android.os.Bundle
+import android.os.SystemClock
+import android.util.Log
+import android.view.ContextThemeWrapper
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.accessibility.AccessibilityNodeInfo
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction
+import android.widget.Button
+import com.android.systemui.R
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.shared.system.ActivityManagerWrapper
+import com.android.systemui.shared.system.DevicePolicyManagerWrapper
+import com.android.systemui.shared.system.PackageManagerWrapper
+import com.android.systemui.statusbar.NotificationRemoteInputManager
+import com.android.systemui.statusbar.NotificationUiAdjustment
+import com.android.systemui.statusbar.SmartReplyController
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.logging.NotificationLogger
+import com.android.systemui.statusbar.phone.KeyguardDismissUtil
+import com.android.systemui.statusbar.policy.InflatedSmartReplies.SmartRepliesAndActions
+import com.android.systemui.statusbar.policy.SmartReplyView.SmartActions
+import com.android.systemui.statusbar.policy.SmartReplyView.SmartButtonType
+import com.android.systemui.statusbar.policy.SmartReplyView.SmartReplies
+import javax.inject.Inject
+
+/** Returns whether we should show the smart reply view and its smart suggestions. */
+fun shouldShowSmartReplyView(
+ entry: NotificationEntry,
+ smartRepliesAndActions: SmartRepliesAndActions
+): Boolean {
+ if (smartRepliesAndActions.smartReplies == null
+ && smartRepliesAndActions.smartActions == null) {
+ // There are no smart replies and no smart actions.
+ return false
+ }
+ // If we are showing the spinner we don't want to add the buttons.
+ val showingSpinner = entry.sbn.notification.extras
+ .getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false)
+ if (showingSpinner) {
+ return false
+ }
+ // If we are keeping the notification around while sending we don't want to add the buttons.
+ return !entry.sbn.notification.extras
+ .getBoolean(Notification.EXTRA_HIDE_SMART_REPLIES, false)
+}
+
+/** Determines if two [SmartRepliesAndActions] are visually similar. */
+fun areSuggestionsSimilar(
+ left: SmartRepliesAndActions?,
+ right: SmartRepliesAndActions?
+): Boolean = when {
+ left === right -> true
+ left == null || right == null -> false
+ left.getSmartReplies() != right.getSmartReplies() -> false
+ else -> !NotificationUiAdjustment.areDifferent(left.getSmartActions(), right.getSmartActions())
+}
+
+interface SmartRepliesAndActionsInflater {
+ fun inflateSmartReplies(
+ sysuiContext: Context,
+ notifPackageContext: Context,
+ entry: NotificationEntry,
+ existingRepliesAndAction: SmartRepliesAndActions?
+ ): InflatedSmartReplies
+}
+
+/*internal*/ class SmartRepliesAndActionsInflaterImpl @Inject constructor(
+ private val constants: SmartReplyConstants,
+ private val activityManagerWrapper: ActivityManagerWrapper,
+ private val packageManagerWrapper: PackageManagerWrapper,
+ private val devicePolicyManagerWrapper: DevicePolicyManagerWrapper,
+ private val smartRepliesInflater: SmartReplyInflater,
+ private val smartActionsInflater: SmartActionInflater
+) : SmartRepliesAndActionsInflater {
+
+ override fun inflateSmartReplies(
+ sysuiContext: Context,
+ notifPackageContext: Context,
+ entry: NotificationEntry,
+ existingRepliesAndAction: SmartRepliesAndActions?
+ ): InflatedSmartReplies {
+ val newRepliesAndActions = chooseSmartRepliesAndActions(entry)
+ if (!shouldShowSmartReplyView(entry, newRepliesAndActions)) {
+ return InflatedSmartReplies(
+ null /* smartReplyView */,
+ null /* smartSuggestionButtons */,
+ newRepliesAndActions)
+ }
+
+ // Only block clicks if the smart buttons are different from the previous set - to avoid
+ // scenarios where a user incorrectly cannot click smart buttons because the
+ // notification is updated.
+ val delayOnClickListener =
+ !areSuggestionsSimilar(existingRepliesAndAction, newRepliesAndActions)
+
+ val smartReplyView = SmartReplyView.inflate(sysuiContext, constants)
+
+ val smartReplies = newRepliesAndActions.smartReplies
+ smartReplyView.setSmartRepliesGeneratedByAssistant(smartReplies?.fromAssistant ?: false)
+ val smartReplyButtons = smartReplies?.let {
+ smartReplies.choices.asSequence().mapIndexed { index, choice ->
+ smartRepliesInflater.inflateReplyButton(
+ smartReplyView,
+ entry,
+ smartReplies,
+ index,
+ choice,
+ delayOnClickListener)
+ }
+ } ?: emptySequence()
+
+ val smartActionButtons = newRepliesAndActions.smartActions?.let { smartActions ->
+ val themedPackageContext =
+ ContextThemeWrapper(notifPackageContext, sysuiContext.theme)
+ smartActions.actions.asSequence()
+ .filter { it.actionIntent != null }
+ .mapIndexed { index, action ->
+ smartActionsInflater.inflateActionButton(
+ smartReplyView,
+ entry,
+ smartActions,
+ index,
+ action,
+ delayOnClickListener,
+ themedPackageContext)
+ }
+ } ?: emptySequence()
+
+ return InflatedSmartReplies(
+ smartReplyView,
+ (smartReplyButtons + smartActionButtons).toList(),
+ newRepliesAndActions)
+ }
+
+ /**
+ * Chose what smart replies and smart actions to display. App generated suggestions take
+ * precedence. So if the app provides any smart replies, we don't show any
+ * replies or actions generated by the NotificationAssistantService (NAS), and if the app
+ * provides any smart actions we also don't show any NAS-generated replies or actions.
+ */
+ fun chooseSmartRepliesAndActions(entry: NotificationEntry): SmartRepliesAndActions {
+ val notification = entry.sbn.notification
+ val remoteInputActionPair = notification.findRemoteInputActionPair(false /* freeform */)
+ val freeformRemoteInputActionPair =
+ notification.findRemoteInputActionPair(true /* freeform */)
+ if (!constants.isEnabled) {
+ if (DEBUG) {
+ Log.d(TAG, "Smart suggestions not enabled, not adding suggestions for "
+ + entry.sbn.key)
+ }
+ return SmartRepliesAndActions(null, null)
+ }
+ // Only use smart replies from the app if they target P or above. We have this check because
+ // the smart reply API has been used for other things (Wearables) in the past. The API to
+ // add smart actions is new in Q so it doesn't require a target-sdk check.
+ val enableAppGeneratedSmartReplies = (!constants.requiresTargetingP()
+ || entry.targetSdk >= Build.VERSION_CODES.P)
+ val appGeneratedSmartActions = notification.contextualActions
+
+ var smartReplies: SmartReplies? = when {
+ enableAppGeneratedSmartReplies -> remoteInputActionPair?.let { pair ->
+ pair.second.actionIntent?.let { actionIntent ->
+ if (pair.first.choices?.isNotEmpty() == true)
+ SmartReplies(
+ pair.first.choices.asList(),
+ pair.first,
+ actionIntent,
+ false /* fromAssistant */)
+ else null
+ }
+ }
+ else -> null
+ }
+ var smartActions: SmartActions? = when {
+ appGeneratedSmartActions.isNotEmpty() ->
+ SmartActions(appGeneratedSmartActions, false /* fromAssistant */)
+ else -> null
+ }
+ // Apps didn't provide any smart replies / actions, use those from NAS (if any).
+ if (smartReplies == null && smartActions == null) {
+ val entryReplies = entry.smartReplies
+ val entryActions = entry.smartActions
+ if (entryReplies.isNotEmpty()
+ && freeformRemoteInputActionPair != null
+ && freeformRemoteInputActionPair.second.allowGeneratedReplies
+ && freeformRemoteInputActionPair.second.actionIntent != null) {
+ smartReplies = SmartReplies(
+ entryReplies,
+ freeformRemoteInputActionPair.first,
+ freeformRemoteInputActionPair.second.actionIntent,
+ true /* fromAssistant */)
+ }
+ if (entryActions.isNotEmpty()
+ && notification.allowSystemGeneratedContextualActions) {
+ val systemGeneratedActions: List<Notification.Action> = when {
+ activityManagerWrapper.isLockTaskKioskModeActive ->
+ // Filter actions if we're in kiosk-mode - we don't care about screen
+ // pinning mode, since notifications aren't shown there anyway.
+ filterAllowlistedLockTaskApps(entryActions)
+ else -> entryActions
+ }
+ smartActions = SmartActions(systemGeneratedActions, true /* fromAssistant */)
+ }
+ }
+ return SmartRepliesAndActions(smartReplies, smartActions)
+ }
+
+ /**
+ * Filter actions so that only actions pointing to allowlisted apps are permitted.
+ * This filtering is only meaningful when in lock-task mode.
+ */
+ private fun filterAllowlistedLockTaskApps(
+ actions: List<Notification.Action>
+ ): List<Notification.Action> = actions.filter { action ->
+ // Only allow actions that are explicit (implicit intents are not handled in lock-task
+ // mode), and link to allowlisted apps.
+ action.actionIntent?.intent?.let { intent ->
+ packageManagerWrapper.resolveActivity(intent, 0 /* flags */)
+ }?.let { resolveInfo ->
+ devicePolicyManagerWrapper.isLockTaskPermitted(resolveInfo.activityInfo.packageName)
+ } ?: false
+ }
+}
+
+interface SmartActionInflater {
+ fun inflateActionButton(
+ parent: ViewGroup,
+ entry: NotificationEntry,
+ smartActions: SmartActions,
+ actionIndex: Int,
+ action: Notification.Action,
+ delayOnClickListener: Boolean,
+ packageContext: Context
+ ): Button
+}
+
+/* internal */ class SmartActionInflaterImpl @Inject constructor(
+ private val constants: SmartReplyConstants,
+ private val activityStarter: ActivityStarter,
+ private val smartReplyController: SmartReplyController,
+ private val headsUpManager: HeadsUpManager
+) : SmartActionInflater {
+
+ override fun inflateActionButton(
+ parent: ViewGroup,
+ entry: NotificationEntry,
+ smartActions: SmartActions,
+ actionIndex: Int,
+ action: Notification.Action,
+ delayOnClickListener: Boolean,
+ packageContext: Context
+ ): Button =
+ (LayoutInflater.from(parent.context)
+ .inflate(R.layout.smart_action_button, parent, false) as Button
+ ).apply {
+ text = action.title
+
+ // We received the Icon from the application - so use the Context of the application to
+ // reference icon resources.
+ val iconDrawable = action.getIcon().loadDrawable(packageContext)
+ .apply {
+ val newIconSize: Int = context.resources.getDimensionPixelSize(
+ R.dimen.smart_action_button_icon_size)
+ setBounds(0, 0, newIconSize, newIconSize)
+ }
+ // Add the action icon to the Smart Action button.
+ setCompoundDrawables(iconDrawable, null, null, null)
+
+ val onClickListener = View.OnClickListener {
+ onSmartActionClick(entry, smartActions, actionIndex, action)
+ }
+ setOnClickListener(
+ if (delayOnClickListener)
+ DelayedOnClickListener(onClickListener, constants.onClickInitDelay)
+ else onClickListener)
+
+ // Mark this as an Action button
+ (layoutParams as SmartReplyView.LayoutParams).mButtonType = SmartButtonType.ACTION
+ }
+
+ private fun onSmartActionClick(
+ entry: NotificationEntry,
+ smartActions: SmartActions,
+ actionIndex: Int,
+ action: Notification.Action
+ ) =
+ activityStarter.startPendingIntentDismissingKeyguard(action.actionIntent, entry.row) {
+ smartReplyController
+ .smartActionClicked(entry, actionIndex, action, smartActions.fromAssistant)
+ headsUpManager.removeNotification(entry.key, true /* releaseImmediately */)
+ }
+}
+
+interface SmartReplyInflater {
+ fun inflateReplyButton(
+ parent: SmartReplyView,
+ entry: NotificationEntry,
+ smartReplies: SmartReplies,
+ replyIndex: Int,
+ choice: CharSequence,
+ delayOnClickListener: Boolean
+ ): Button
+}
+
+class SmartReplyInflaterImpl @Inject constructor(
+ private val constants: SmartReplyConstants,
+ private val keyguardDismissUtil: KeyguardDismissUtil,
+ private val remoteInputManager: NotificationRemoteInputManager,
+ private val smartReplyController: SmartReplyController,
+ private val context: Context
+) : SmartReplyInflater {
+
+ override fun inflateReplyButton(
+ parent: SmartReplyView,
+ entry: NotificationEntry,
+ smartReplies: SmartReplies,
+ replyIndex: Int,
+ choice: CharSequence,
+ delayOnClickListener: Boolean
+ ): Button =
+ (LayoutInflater.from(parent.context)
+ .inflate(R.layout.smart_reply_button, parent, false) as Button
+ ).apply {
+ text = choice
+ val onClickListener = View.OnClickListener {
+ onSmartReplyClick(
+ entry,
+ smartReplies,
+ replyIndex,
+ parent,
+ this,
+ choice)
+ }
+ setOnClickListener(
+ if (delayOnClickListener)
+ DelayedOnClickListener(onClickListener, constants.onClickInitDelay)
+ else onClickListener)
+ accessibilityDelegate = object : View.AccessibilityDelegate() {
+ override fun onInitializeAccessibilityNodeInfo(
+ host: View,
+ info: AccessibilityNodeInfo
+ ) {
+ super.onInitializeAccessibilityNodeInfo(host, info)
+ val label = parent.resources
+ .getString(R.string.accessibility_send_smart_reply)
+ val action = AccessibilityAction(AccessibilityNodeInfo.ACTION_CLICK, label)
+ info.addAction(action)
+ }
+ }
+ // TODO: probably shouldn't do this here, bad API
+ // Mark this as a Reply button
+ (layoutParams as SmartReplyView.LayoutParams).mButtonType = SmartButtonType.REPLY
+ }
+
+ private fun onSmartReplyClick(
+ entry: NotificationEntry,
+ smartReplies: SmartReplies,
+ replyIndex: Int,
+ smartReplyView: SmartReplyView,
+ button: Button,
+ choice: CharSequence
+ ) = keyguardDismissUtil.executeWhenUnlocked(!entry.isRowPinned) {
+ val canEditBeforeSend = constants.getEffectiveEditChoicesBeforeSending(
+ smartReplies.remoteInput.editChoicesBeforeSending)
+ if (canEditBeforeSend) {
+ remoteInputManager.activateRemoteInput(
+ button,
+ arrayOf(smartReplies.remoteInput),
+ smartReplies.remoteInput,
+ smartReplies.pendingIntent,
+ NotificationEntry.EditedSuggestionInfo(choice, replyIndex))
+ } else {
+ smartReplyController.smartReplySent(
+ entry,
+ replyIndex,
+ button.text,
+ NotificationLogger.getNotificationLocation(entry).toMetricsEventEnum(),
+ false /* modifiedBeforeSending */)
+ entry.setHasSentReply()
+ try {
+ val intent = createRemoteInputIntent(smartReplies, choice)
+ smartReplies.pendingIntent.send(context, 0, intent)
+ } catch (e: PendingIntent.CanceledException) {
+ Log.w(TAG, "Unable to send smart reply", e)
+ }
+ smartReplyView.hideSmartSuggestions()
+ }
+ false // do not defer
+ }
+
+ private fun createRemoteInputIntent(smartReplies: SmartReplies, choice: CharSequence): Intent {
+ val results = Bundle()
+ results.putString(smartReplies.remoteInput.resultKey, choice.toString())
+ val intent = Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ RemoteInput.addResultsToIntent(arrayOf(smartReplies.remoteInput), intent, results)
+ RemoteInput.setResultsSource(intent, RemoteInput.SOURCE_CHOICE)
+ return intent
+ }
+}
+
+/**
+ * An OnClickListener wrapper that blocks the underlying OnClickListener for a given amount of
+ * time.
+ */
+private class DelayedOnClickListener(
+ private val mActualListener: View.OnClickListener,
+ private val mInitDelayMs: Long
+) : View.OnClickListener {
+
+ private val mInitTimeMs = SystemClock.elapsedRealtime()
+
+ override fun onClick(v: View) {
+ if (hasFinishedInitialization()) {
+ mActualListener.onClick(v)
+ } else {
+ Log.i(TAG, "Accidental Smart Suggestion click registered, delay: $mInitDelayMs")
+ }
+ }
+
+ private fun hasFinishedInitialization(): Boolean =
+ SystemClock.elapsedRealtime() >= mInitTimeMs + mInitDelayMs
+}
+
+private const val TAG = "SmartReplyViewInflater"
+private val DEBUG = Log.isLoggable(TAG, Log.DEBUG)
+
+// convenience function that swaps parameter order so that lambda can be placed at the end
+private fun KeyguardDismissUtil.executeWhenUnlocked(
+ requiresShadeOpen: Boolean,
+ onDismissAction: () -> Boolean
+) = executeWhenUnlocked(onDismissAction, requiresShadeOpen)
+
+// convenience function that swaps parameter order so that lambda can be placed at the end
+private fun ActivityStarter.startPendingIntentDismissingKeyguard(
+ intent: PendingIntent,
+ associatedView: View?,
+ runnable: () -> Unit
+) = startPendingIntentDismissingKeyguard(intent, runnable::invoke, associatedView) \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
index 949ac4df88ba..e7f84a55eb5f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
@@ -6,7 +6,6 @@ import android.app.Notification;
import android.app.PendingIntent;
import android.app.RemoteInput;
import android.content.Context;
-import android.content.Intent;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Canvas;
@@ -15,34 +14,20 @@ import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.InsetDrawable;
import android.graphics.drawable.RippleDrawable;
-import android.os.Bundle;
-import android.os.SystemClock;
import android.text.Layout;
import android.text.TextPaint;
import android.text.method.TransformationMethod;
import android.util.AttributeSet;
import android.util.Log;
-import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.widget.Button;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ContrastColorUtil;
-import com.android.systemui.Dependency;
import com.android.systemui.R;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.notification.NotificationUtils;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry.EditedSuggestionInfo;
-import com.android.systemui.statusbar.notification.logging.NotificationLogger;
-import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
import java.text.BreakIterator;
import java.util.ArrayList;
@@ -64,10 +49,6 @@ public class SmartReplyView extends ViewGroup {
private static final int SQUEEZE_FAILED = -1;
- private final SmartReplyConstants mConstants;
- private final KeyguardDismissUtil mKeyguardDismissUtil;
- private final NotificationRemoteInputManager mRemoteInputManager;
-
/**
* The upper bound for the height of this view in pixels. Notifications are automatically
* recreated on density or font size changes so caching this should be fine.
@@ -98,30 +79,25 @@ public class SmartReplyView extends ViewGroup {
*/
private boolean mSmartRepliesGeneratedByAssistant = false;
- @ColorInt
- private int mCurrentBackgroundColor;
- @ColorInt
- private final int mDefaultBackgroundColor;
- @ColorInt
- private final int mDefaultStrokeColor;
- @ColorInt
- private final int mDefaultTextColor;
- @ColorInt
- private final int mDefaultTextColorDarkBg;
- @ColorInt
- private final int mRippleColorDarkBg;
- @ColorInt
- private final int mRippleColor;
+ @ColorInt private int mCurrentBackgroundColor;
+ @ColorInt private final int mDefaultBackgroundColor;
+ @ColorInt private final int mDefaultStrokeColor;
+ @ColorInt private final int mDefaultTextColor;
+ @ColorInt private final int mDefaultTextColorDarkBg;
+ @ColorInt private final int mRippleColorDarkBg;
+ @ColorInt private final int mRippleColor;
private final int mStrokeWidth;
private final double mMinStrokeContrast;
- private ActivityStarter mActivityStarter;
+ @ColorInt private int mCurrentStrokeColor;
+ @ColorInt private int mCurrentTextColor;
+ @ColorInt private int mCurrentRippleColor;
+ private int mMaxSqueezeRemeasureAttempts;
+ private int mMaxNumActions;
+ private int mMinNumSystemGeneratedReplies;
public SmartReplyView(Context context, AttributeSet attrs) {
super(context, attrs);
- mConstants = Dependency.get(SmartReplyConstants.class);
- mKeyguardDismissUtil = Dependency.get(KeyguardDismissUtil.class);
- mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class);
mHeightUpperLimit = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.smart_reply_button_max_height);
@@ -172,6 +148,18 @@ public class SmartReplyView extends ViewGroup {
}
/**
+ * Inflate an instance of this class.
+ */
+ public static SmartReplyView inflate(Context context, SmartReplyConstants constants) {
+ SmartReplyView view = (SmartReplyView) LayoutInflater.from(context).inflate(
+ R.layout.smart_reply_view, null /* root */);
+ view.setMaxNumActions(constants.getMaxNumActions());
+ view.setMaxSqueezeRemeasureAttempts(constants.getMaxSqueezeRemeasureAttempts());
+ view.setMinNumSystemGeneratedReplies(constants.getMinNumSystemGeneratedReplies());
+ return view;
+ }
+
+ /**
* Returns an upper bound for the height of this view in pixels. This method is intended to be
* invoked before onMeasure, so it doesn't do any analysis on the contents of the buttons.
*/
@@ -197,174 +185,25 @@ public class SmartReplyView extends ViewGroup {
mCurrentBackgroundColor = mDefaultBackgroundColor;
}
- /**
- * Add buttons to the {@link SmartReplyView} - these buttons must have been preinflated using
- * one of the methods in this class.
- */
+ /** Add buttons to the {@link SmartReplyView} */
public void addPreInflatedButtons(List<Button> smartSuggestionButtons) {
for (Button button : smartSuggestionButtons) {
addView(button);
+ setButtonColors(button);
}
reallocateCandidateButtonQueueForSqueezing();
}
- /**
- * Add smart replies to this view, using the provided {@link RemoteInput} and
- * {@link PendingIntent} to respond when the user taps a smart reply. Only the replies that fit
- * into the notification are shown.
- */
- public List<Button> inflateRepliesFromRemoteInput(
- @NonNull SmartReplies smartReplies,
- SmartReplyController smartReplyController, NotificationEntry entry,
- boolean delayOnClickListener) {
- List<Button> buttons = new ArrayList<>();
-
- if (smartReplies.remoteInput != null && smartReplies.pendingIntent != null) {
- if (smartReplies.choices != null) {
- for (int i = 0; i < smartReplies.choices.size(); ++i) {
- buttons.add(inflateReplyButton(
- this, getContext(), i, smartReplies, smartReplyController, entry,
- delayOnClickListener));
- }
- this.mSmartRepliesGeneratedByAssistant = smartReplies.fromAssistant;
- }
- }
- return buttons;
- }
-
- /**
- * Add smart actions to be shown next to smart replies. Only the actions that fit into the
- * notification are shown.
- */
- public List<Button> inflateSmartActions(Context packageContext,
- @NonNull SmartActions smartActions, SmartReplyController smartReplyController,
- NotificationEntry entry, HeadsUpManager headsUpManager, boolean delayOnClickListener) {
- Context themedPackageContext = new ContextThemeWrapper(packageContext, mContext.getTheme());
- List<Button> buttons = new ArrayList<>();
- int numSmartActions = smartActions.actions.size();
- for (int n = 0; n < numSmartActions; n++) {
- Notification.Action action = smartActions.actions.get(n);
- if (action.actionIntent != null) {
- buttons.add(inflateActionButton(
- this, getContext(), themedPackageContext, n, smartActions,
- smartReplyController,
- entry, headsUpManager, delayOnClickListener));
- }
- }
- return buttons;
- }
-
- /**
- * Inflate an instance of this class.
- */
- public static SmartReplyView inflate(Context context) {
- return (SmartReplyView) LayoutInflater.from(context).inflate(
- R.layout.smart_reply_view, null /* root */);
+ public void setMaxNumActions(int maxNumActions) {
+ mMaxNumActions = maxNumActions;
}
- @VisibleForTesting
- static Button inflateReplyButton(SmartReplyView smartReplyView, Context context,
- int replyIndex, SmartReplies smartReplies, SmartReplyController smartReplyController,
- NotificationEntry entry, boolean useDelayedOnClickListener) {
- Button b = (Button) LayoutInflater.from(context).inflate(
- R.layout.smart_reply_button, smartReplyView, false);
- CharSequence choice = smartReplies.choices.get(replyIndex);
- b.setText(choice);
-
- OnDismissAction action = () -> {
- if (smartReplyView.mConstants.getEffectiveEditChoicesBeforeSending(
- smartReplies.remoteInput.getEditChoicesBeforeSending())) {
- EditedSuggestionInfo editedSuggestionInfo =
- new EditedSuggestionInfo(choice, replyIndex);
- smartReplyView.mRemoteInputManager.activateRemoteInput(b,
- new RemoteInput[] { smartReplies.remoteInput }, smartReplies.remoteInput,
- smartReplies.pendingIntent, editedSuggestionInfo);
- return false;
- }
-
- smartReplyController.smartReplySent(entry, replyIndex, b.getText(),
- NotificationLogger.getNotificationLocation(entry).toMetricsEventEnum(),
- false /* modifiedBeforeSending */);
- Bundle results = new Bundle();
- results.putString(smartReplies.remoteInput.getResultKey(), choice.toString());
- Intent intent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- RemoteInput.addResultsToIntent(new RemoteInput[] { smartReplies.remoteInput }, intent,
- results);
- RemoteInput.setResultsSource(intent, RemoteInput.SOURCE_CHOICE);
- entry.setHasSentReply();
- try {
- smartReplies.pendingIntent.send(context, 0, intent);
- } catch (PendingIntent.CanceledException e) {
- Log.w(TAG, "Unable to send smart reply", e);
- }
- // Note that as inflateReplyButton is called mSmartReplyContainer is null, but when the
- // reply Button is added to the SmartReplyView mSmartReplyContainer will be set. So, it
- // will not be possible for a user to trigger this on-click-listener without
- // mSmartReplyContainer being set.
- smartReplyView.mSmartReplyContainer.setVisibility(View.GONE);
- return false; // do not defer
- };
-
- OnClickListener onClickListener = view ->
- smartReplyView.mKeyguardDismissUtil.executeWhenUnlocked(action, !entry.isRowPinned());
- if (useDelayedOnClickListener) {
- onClickListener = new DelayedOnClickListener(onClickListener,
- smartReplyView.mConstants.getOnClickInitDelay());
- }
- b.setOnClickListener(onClickListener);
-
- b.setAccessibilityDelegate(new AccessibilityDelegate() {
- public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(host, info);
- String label = smartReplyView.getResources().getString(
- R.string.accessibility_send_smart_reply);
- info.addAction(new AccessibilityAction(AccessibilityNodeInfo.ACTION_CLICK, label));
- }
- });
-
- SmartReplyView.setButtonColors(b, smartReplyView.mCurrentBackgroundColor,
- smartReplyView.mDefaultStrokeColor, smartReplyView.mDefaultTextColor,
- smartReplyView.mRippleColor, smartReplyView.mStrokeWidth);
- return b;
+ public void setMinNumSystemGeneratedReplies(int minNumSystemGeneratedReplies) {
+ mMinNumSystemGeneratedReplies = minNumSystemGeneratedReplies;
}
- @VisibleForTesting
- static Button inflateActionButton(SmartReplyView smartReplyView, Context context,
- Context packageContext, int actionIndex, SmartActions smartActions,
- SmartReplyController smartReplyController, NotificationEntry entry,
- HeadsUpManager headsUpManager, boolean useDelayedOnClickListener) {
- Notification.Action action = smartActions.actions.get(actionIndex);
- Button button = (Button) LayoutInflater.from(context).inflate(
- R.layout.smart_action_button, smartReplyView, false);
- button.setText(action.title);
-
- // We received the Icon from the application - so use the Context of the application to
- // reference icon resources.
- Drawable iconDrawable = action.getIcon().loadDrawable(packageContext);
- // Add the action icon to the Smart Action button.
- int newIconSize = context.getResources().getDimensionPixelSize(
- R.dimen.smart_action_button_icon_size);
- iconDrawable.setBounds(0, 0, newIconSize, newIconSize);
- button.setCompoundDrawables(iconDrawable, null, null, null);
-
- OnClickListener onClickListener = view ->
- smartReplyView.getActivityStarter().startPendingIntentDismissingKeyguard(
- action.actionIntent,
- () -> {
- smartReplyController.smartActionClicked(
- entry, actionIndex, action, smartActions.fromAssistant);
- headsUpManager.removeNotification(entry.getKey(), true);
- }, entry.getRow());
- if (useDelayedOnClickListener) {
- onClickListener = new DelayedOnClickListener(onClickListener,
- smartReplyView.mConstants.getOnClickInitDelay());
- }
- button.setOnClickListener(onClickListener);
-
- // Mark this as an Action button
- final LayoutParams lp = (LayoutParams) button.getLayoutParams();
- lp.buttonType = SmartButtonType.ACTION;
- return button;
+ public void setMaxSqueezeRemeasureAttempts(int maxSqueezeRemeasureAttempts) {
+ mMaxSqueezeRemeasureAttempts = maxSqueezeRemeasureAttempts;
}
@Override
@@ -416,13 +255,13 @@ public class SmartReplyView extends ViewGroup {
// reply button is added.
SmartSuggestionMeasures actionsMeasures = null;
- final int maxNumActions = mConstants.getMaxNumActions();
+ final int maxNumActions = mMaxNumActions;
int numShownActions = 0;
for (View child : smartSuggestions) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (maxNumActions != -1 // -1 means 'no limit'
- && lp.buttonType == SmartButtonType.ACTION
+ && lp.mButtonType == SmartButtonType.ACTION
&& numShownActions >= maxNumActions) {
// We've reached the maximum number of actions, don't add another one!
continue;
@@ -446,7 +285,7 @@ public class SmartReplyView extends ViewGroup {
// Remember the current measurements in case the current button doesn't fit in.
SmartSuggestionMeasures originalMeasures = accumulatedMeasures.clone();
- if (actionsMeasures == null && lp.buttonType == SmartButtonType.REPLY) {
+ if (actionsMeasures == null && lp.mButtonType == SmartButtonType.REPLY) {
// We've added all actions (we go through actions first), now add their
// measurements.
actionsMeasures = accumulatedMeasures.clone();
@@ -510,7 +349,7 @@ public class SmartReplyView extends ViewGroup {
lp.show = true;
displayedChildCount++;
- if (lp.buttonType == SmartButtonType.ACTION) {
+ if (lp.mButtonType == SmartButtonType.ACTION) {
numShownActions++;
}
}
@@ -551,6 +390,19 @@ public class SmartReplyView extends ViewGroup {
resolveSize(buttonHeight, heightMeasureSpec));
}
+ // TODO: this should be replaced, and instead, setMinSystemGenerated... should be invoked
+ // with MAX_VALUE if mSmartRepliesGeneratedByAssistant would be false (essentially, this is a
+ // ViewModel decision, as opposed to a View decision)
+ void setSmartRepliesGeneratedByAssistant(boolean fromAssistant) {
+ mSmartRepliesGeneratedByAssistant = fromAssistant;
+ }
+
+ void hideSmartSuggestions() {
+ if (mSmartReplyContainer != null) {
+ mSmartReplyContainer.setVisibility(View.GONE);
+ }
+ }
+
/**
* Fields we keep track of inside onMeasure() to correctly measure the SmartReplyView depending
* on which suggestions are added.
@@ -577,6 +429,7 @@ public class SmartReplyView extends ViewGroup {
* Returns whether our notification contains at least N smart replies (or 0) where N is
* determined by {@link SmartReplyConstants}.
*/
+ // TODO: we probably sholdn't make this deliberation in the View
private boolean gotEnoughSmartReplies(List<View> smartReplies) {
int numShownReplies = 0;
for (View smartReplyButton : smartReplies) {
@@ -585,8 +438,7 @@ public class SmartReplyView extends ViewGroup {
numShownReplies++;
}
}
- if (numShownReplies == 0
- || numShownReplies >= mConstants.getMinNumSystemGeneratedReplies()) {
+ if (numShownReplies == 0 || numShownReplies >= mMinNumSystemGeneratedReplies) {
// We have enough replies, yay!
return true;
}
@@ -602,7 +454,7 @@ public class SmartReplyView extends ViewGroup {
if (child.getVisibility() != View.VISIBLE || !(child instanceof Button)) {
continue;
}
- if (lp.buttonType == buttonType) {
+ if (lp.mButtonType == buttonType) {
actions.add(child);
}
}
@@ -656,7 +508,7 @@ public class SmartReplyView extends ViewGroup {
// See if there's a better line-break point (leading to a more narrow button) in
// either left or right direction.
final boolean moveLeft = initialLeftTextWidth > initialRightTextWidth;
- final int maxSqueezeRemeasureAttempts = mConstants.getMaxSqueezeRemeasureAttempts();
+ final int maxSqueezeRemeasureAttempts = mMaxSqueezeRemeasureAttempts;
for (int i = 0; i < maxSqueezeRemeasureAttempts; i++) {
final int newPosition =
moveLeft ? mBreakIterator.previous() : mBreakIterator.next();
@@ -833,41 +685,38 @@ public class SmartReplyView extends ViewGroup {
final boolean dark = !ContrastColorUtil.isColorLight(backgroundColor);
- int textColor = ContrastColorUtil.ensureTextContrast(
+ mCurrentTextColor = ContrastColorUtil.ensureTextContrast(
dark ? mDefaultTextColorDarkBg : mDefaultTextColor,
backgroundColor | 0xff000000, dark);
- int strokeColor = ContrastColorUtil.ensureContrast(
+ mCurrentStrokeColor = ContrastColorUtil.ensureContrast(
mDefaultStrokeColor, backgroundColor | 0xff000000, dark, mMinStrokeContrast);
- int rippleColor = dark ? mRippleColorDarkBg : mRippleColor;
+ mCurrentRippleColor = dark ? mRippleColorDarkBg : mRippleColor;
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
- final Button child = (Button) getChildAt(i);
- setButtonColors(child, backgroundColor, strokeColor, textColor, rippleColor,
- mStrokeWidth);
+ setButtonColors((Button) getChildAt(i));
}
}
- private static void setButtonColors(Button button, int backgroundColor, int strokeColor,
- int textColor, int rippleColor, int strokeWidth) {
+ private void setButtonColors(Button button) {
Drawable drawable = button.getBackground();
if (drawable instanceof RippleDrawable) {
// Mutate in case other notifications are using this drawable.
drawable = drawable.mutate();
RippleDrawable ripple = (RippleDrawable) drawable;
- ripple.setColor(ColorStateList.valueOf(rippleColor));
+ ripple.setColor(ColorStateList.valueOf(mCurrentRippleColor));
Drawable inset = ripple.getDrawable(0);
if (inset instanceof InsetDrawable) {
Drawable background = ((InsetDrawable) inset).getDrawable();
if (background instanceof GradientDrawable) {
GradientDrawable gradientDrawable = (GradientDrawable) background;
- gradientDrawable.setColor(backgroundColor);
- gradientDrawable.setStroke(strokeWidth, strokeColor);
+ gradientDrawable.setColor(mCurrentBackgroundColor);
+ gradientDrawable.setStroke(mStrokeWidth, mCurrentStrokeColor);
}
}
button.setBackground(drawable);
}
- button.setTextColor(textColor);
+ button.setTextColor(mCurrentTextColor);
}
private void setCornerRadius(Button button, float radius) {
@@ -887,14 +736,7 @@ public class SmartReplyView extends ViewGroup {
}
}
- private ActivityStarter getActivityStarter() {
- if (mActivityStarter == null) {
- mActivityStarter = Dependency.get(ActivityStarter.class);
- }
- return mActivityStarter;
- }
-
- private enum SmartButtonType {
+ enum SmartButtonType {
REPLY,
ACTION
}
@@ -924,7 +766,7 @@ public class SmartReplyView extends ViewGroup {
private boolean show = false;
private int squeezeStatus = SQUEEZE_STATUS_NONE;
- private SmartButtonType buttonType = SmartButtonType.REPLY;
+ SmartButtonType mButtonType = SmartButtonType.REPLY;
private LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
@@ -975,32 +817,4 @@ public class SmartReplyView extends ViewGroup {
this.fromAssistant = fromAssistant;
}
}
-
- /**
- * An OnClickListener wrapper that blocks the underlying OnClickListener for a given amount of
- * time.
- */
- private static class DelayedOnClickListener implements OnClickListener {
- private final OnClickListener mActualListener;
- private final long mInitDelayMs;
- private final long mInitTimeMs;
-
- DelayedOnClickListener(OnClickListener actualOnClickListener, long initDelayMs) {
- mActualListener = actualOnClickListener;
- mInitDelayMs = initDelayMs;
- mInitTimeMs = SystemClock.elapsedRealtime();
- }
-
- public void onClick(View v) {
- if (hasFinishedInitialization()) {
- mActualListener.onClick(v);
- } else {
- Log.i(TAG, "Accidental Smart Suggestion click registered, delay: " + mInitDelayMs);
- }
- }
-
- private boolean hasFinishedInitialization() {
- return SystemClock.elapsedRealtime() >= mInitTimeMs + mInitDelayMs;
- }
- }
}
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 17fcb1dd6f1a..72e8e38735e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -23,6 +23,7 @@ import static com.android.systemui.DejankUtils.whitelistIpcs;
import android.app.ActivityManager;
import android.app.Dialog;
+import android.app.IActivityTaskManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
@@ -53,7 +54,6 @@ 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;
import com.android.systemui.Dumpable;
import com.android.systemui.GuestResumeSessionReceiver;
@@ -69,6 +69,7 @@ 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;
+import com.android.systemui.user.CreateUserActivity;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -104,6 +105,7 @@ public class UserSwitcherController implements Dumpable {
protected final Handler mHandler;
private final ActivityStarter mActivityStarter;
private final BroadcastDispatcher mBroadcastDispatcher;
+ private final IActivityTaskManager mActivityTaskManager;
private ArrayList<UserRecord> mUsers = new ArrayList<>();
private Dialog mExitGuestDialog;
@@ -121,9 +123,11 @@ public class UserSwitcherController implements Dumpable {
@Inject
public UserSwitcherController(Context context, KeyguardStateController keyguardStateController,
@Main Handler handler, ActivityStarter activityStarter,
- BroadcastDispatcher broadcastDispatcher, UiEventLogger uiEventLogger) {
+ BroadcastDispatcher broadcastDispatcher, UiEventLogger uiEventLogger,
+ IActivityTaskManager activityTaskManager) {
mContext = context;
mBroadcastDispatcher = broadcastDispatcher;
+ mActivityTaskManager = activityTaskManager;
mUiEventLogger = uiEventLogger;
if (!UserManager.isGuestUserEphemeral()) {
mGuestResumeSessionReceiver.register(mBroadcastDispatcher);
@@ -363,7 +367,7 @@ public class UserSwitcherController implements Dumpable {
}
}
- public void switchTo(UserRecord record) {
+ private void onUserListItemClicked(UserRecord record) {
int id;
if (record.isGuest && record.info == null) {
// No guest user. Create one.
@@ -408,19 +412,6 @@ public class UserSwitcherController implements Dumpable {
switchToUserId(id);
}
- public void switchTo(int userId) {
- final int count = mUsers.size();
- for (int i = 0; i < count; ++i) {
- UserRecord record = mUsers.get(i);
- if (record.info != null && record.info.id == userId) {
- switchTo(record);
- return;
- }
- }
-
- Log.e(TAG, "Couldn't switch to user, id=" + userId);
- }
-
protected void switchToUserId(int id) {
try {
pauseRefreshUsers();
@@ -666,8 +657,11 @@ public class UserSwitcherController implements Dumpable {
return position;
}
- public void switchTo(UserRecord record) {
- mController.switchTo(record);
+ /**
+ * It handles click events on user list items.
+ */
+ public void onUserListItemClicked(UserRecord record) {
+ mController.onUserListItemClicked(record);
}
public String getName(Context context, UserRecord item) {
@@ -924,18 +918,33 @@ public class UserSwitcherController implements Dumpable {
if (ActivityManager.isUserAMonkey()) {
return;
}
- UserInfo user = mUserManager.createUser(
- mContext.getString(R.string.user_new_user_name), 0 /* flags */);
- if (user == null) {
- // Couldn't create user, most likely because there are too many, but we haven't
- // been able to reload the list yet.
- return;
+ Intent intent = CreateUserActivity.createIntentForStart(getContext());
+
+ // There are some differences between ActivityStarter and ActivityTaskManager in
+ // terms of how they start an activity. ActivityStarter hides the notification bar
+ // before starting the activity to make sure nothing is in front of the new
+ // activity. ActivityStarter also tries to unlock the device if it's locked.
+ // When locked with PIN/pattern/password then it shows the prompt, if there are no
+ // security steps then it dismisses the keyguard and then starts the activity.
+ // ActivityTaskManager doesn't hide the notification bar or unlocks the device, but
+ // it can start an activity on top of the locked screen.
+ if (!mKeyguardStateController.isUnlocked()
+ && !mKeyguardStateController.canDismissLockScreen()) {
+ // Device is locked and can't be unlocked without a PIN/pattern/password so we
+ // need to use ActivityTaskManager to start the activity on top of the locked
+ // screen.
+ try {
+ mActivityTaskManager.startActivity(null,
+ mContext.getBasePackageName(), mContext.getAttributionTag(), intent,
+ intent.resolveTypeIfNeeded(mContext.getContentResolver()), null,
+ null, 0, 0, null, null);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ Log.e(TAG, "Couldn't start create user activity", e);
+ }
+ } else {
+ mActivityStarter.startActivity(intent, true);
}
- int id = user.id;
- Bitmap icon = UserIcons.convertToBitmap(UserIcons.getDefaultUserIcon(
- mContext.getResources(), id, /* light= */ false));
- mUserManager.setUserIcon(id, icon);
- switchToUserId(id);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/SmartRepliesInflationModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/SmartRepliesInflationModule.kt
new file mode 100644
index 000000000000..803d26ec3286
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/SmartRepliesInflationModule.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.policy.dagger
+
+import com.android.systemui.statusbar.policy.SmartActionInflater
+import com.android.systemui.statusbar.policy.SmartActionInflaterImpl
+import com.android.systemui.statusbar.policy.SmartRepliesAndActionsInflater
+import com.android.systemui.statusbar.policy.SmartRepliesAndActionsInflaterImpl
+import com.android.systemui.statusbar.policy.SmartReplyInflater
+import com.android.systemui.statusbar.policy.SmartReplyInflaterImpl
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface SmartRepliesInflationModule {
+ @Binds fun bindSmartActionsInflater(impl: SmartActionInflaterImpl): SmartActionInflater
+ @Binds fun bindSmartReplyInflater(impl: SmartReplyInflaterImpl): SmartReplyInflater
+ @Binds fun bindsInflatedSmartRepliesProvider(
+ impl: SmartRepliesAndActionsInflaterImpl
+ ): SmartRepliesAndActionsInflater
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvNotificationPanel.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvNotificationPanel.java
index 7a78c157e5b4..0bd36240a366 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvNotificationPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvNotificationPanel.java
@@ -58,6 +58,10 @@ public class TvNotificationPanel extends SystemUI implements CommandQueue.Callba
if (!mNotificationHandlerPackage.isEmpty()) {
startNotificationHandlerActivity(
new Intent(NotificationManager.ACTION_TOGGLE_NOTIFICATION_HANDLER_PANEL));
+ } else {
+ Log.w(TAG,
+ "Not toggling notification panel: config_notificationHandlerPackage is "
+ + "empty");
}
}
@@ -66,6 +70,10 @@ public class TvNotificationPanel extends SystemUI implements CommandQueue.Callba
if (!mNotificationHandlerPackage.isEmpty()) {
startNotificationHandlerActivity(
new Intent(NotificationManager.ACTION_OPEN_NOTIFICATION_HANDLER_PANEL));
+ } else {
+ Log.w(TAG,
+ "Not expanding notification panel: config_notificationHandlerPackage is "
+ + "empty");
}
}
@@ -77,6 +85,9 @@ public class TvNotificationPanel extends SystemUI implements CommandQueue.Callba
NotificationManager.ACTION_CLOSE_NOTIFICATION_HANDLER_PANEL);
closeNotificationIntent.setPackage(mNotificationHandlerPackage);
mContext.sendBroadcastAsUser(closeNotificationIntent, UserHandle.CURRENT);
+ } else {
+ Log.w(TAG,
+ "Not closing notification panel: config_notificationHandlerPackage is empty");
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioRecordingDisclosureBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioRecordingDisclosureBar.java
index a29db4d98329..c9d1b71bca77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioRecordingDisclosureBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioRecordingDisclosureBar.java
@@ -21,7 +21,6 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.annotation.IntDef;
import android.annotation.UiThread;
@@ -36,7 +35,6 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
-import android.widget.TextView;
import com.android.systemui.R;
import com.android.systemui.statusbar.tv.TvStatusBar;
@@ -83,19 +81,14 @@ public class AudioRecordingDisclosureBar implements
private static final int STATE_SHOWN = 2;
private static final int STATE_DISAPPEARING = 3;
- private static final int ANIMATION_DURATION = 600;
+ private static final int ANIMATION_DURATION_MS = 200;
private final Context mContext;
private boolean mIsEnabled;
private View mIndicatorView;
- private View mIconTextsContainer;
- private View mIconContainerBg;
- private View mIcon;
- private View mBgEnd;
- private View mTextsContainers;
- private TextView mTextView;
- private boolean mIsLtr;
+ private boolean mViewAndWindowAdded;
+ private ObjectAnimator mAnimator;
@State private int mState = STATE_STOPPED;
@@ -190,7 +183,7 @@ public class AudioRecordingDisclosureBar implements
}
if (active) {
- showIfNotShown();
+ showIfNeeded();
} else {
hideIndicatorIfNeeded();
}
@@ -198,152 +191,145 @@ public class AudioRecordingDisclosureBar implements
@UiThread
private void hideIndicatorIfNeeded() {
- // If not STATE_APPEARING, will check whether the indicator should be hidden when the
- // indicator comes to the STATE_SHOWN.
- // If STATE_DISAPPEARING or STATE_SHOWN - nothing else for us to do here.
- if (mState != STATE_SHOWN) return;
-
- // If is in the STATE_SHOWN and there are no active recorders - hide.
- if (!hasActiveRecorders()) {
- hide();
+ // If STOPPED, NOT_SHOWN or DISAPPEARING - nothing else for us to do here.
+ if (mState != STATE_SHOWN && mState != STATE_APPEARING) return;
+
+ if (hasActiveRecorders()) {
+ return;
+ }
+
+ if (mViewAndWindowAdded) {
+ mState = STATE_DISAPPEARING;
+ animateDisappearance();
+ } else {
+ // Appearing animation has not started yet, as we were still waiting for the View to be
+ // laid out.
+ mState = STATE_NOT_SHOWN;
+ removeIndicatorView();
}
}
@UiThread
- private void showIfNotShown() {
- if (mState != STATE_NOT_SHOWN) return;
+ private void showIfNeeded() {
+ // If STOPPED, SHOWN or APPEARING - nothing else for us to do here.
+ if (mState != STATE_NOT_SHOWN && mState != STATE_DISAPPEARING) return;
+
if (DEBUG) Log.d(TAG, "Showing indicator");
- mIsLtr = mContext.getResources().getConfiguration().getLayoutDirection()
- == View.LAYOUT_DIRECTION_LTR;
+ final int prevState = mState;
+ mState = STATE_APPEARING;
+
+ if (prevState == STATE_DISAPPEARING) {
+ animateAppearance();
+ return;
+ }
// Inflate the indicator view
mIndicatorView = LayoutInflater.from(mContext).inflate(
- R.layout.tv_audio_recording_indicator,
- null);
- mIconTextsContainer = mIndicatorView.findViewById(R.id.icon_texts_container);
- mIconContainerBg = mIconTextsContainer.findViewById(R.id.icon_container_bg);
- mIcon = mIconTextsContainer.findViewById(R.id.icon_mic);
- mTextsContainers = mIconTextsContainer.findViewById(R.id.texts_container);
- mTextView = mTextsContainers.findViewById(R.id.text);
- mBgEnd = mIndicatorView.findViewById(R.id.bg_end);
-
- mTextsContainers.setVisibility(View.GONE);
- mIconContainerBg.setVisibility(View.GONE);
- mTextView.setVisibility(View.GONE);
- mBgEnd.setVisibility(View.GONE);
- mTextsContainers = null;
- mIconContainerBg = null;
- mTextView = null;
- mBgEnd = null;
-
- // Initially change the visibility to INVISIBLE, wait until and receives the size and
- // then animate it moving from "off" the screen correctly
- mIndicatorView.setVisibility(View.INVISIBLE);
+ R.layout.tv_audio_recording_indicator, null);
+
+ // 1. Set alpha to 0.
+ // 2. Wait until the window is shown and the view is laid out.
+ // 3. Start a "fade in" (alpha) animation.
+ mIndicatorView.setAlpha(0f);
mIndicatorView
.getViewTreeObserver()
.addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
- if (mState == STATE_STOPPED) {
- return;
- }
+ // State could have changed to NOT_SHOWN (if all the recorders are
+ // already gone) to STOPPED (if the indicator was disabled)
+ if (mState != STATE_APPEARING) return;
+ mViewAndWindowAdded = true;
// Remove the observer
mIndicatorView.getViewTreeObserver().removeOnGlobalLayoutListener(
this);
- // Now that the width of the indicator has been assigned, we can
- // move it in from off the screen.
- final int initialOffset =
- (mIsLtr ? 1 : -1) * mIndicatorView.getWidth();
- final AnimatorSet set = new AnimatorSet();
- set.setDuration(ANIMATION_DURATION);
- set.playTogether(
- ObjectAnimator.ofFloat(mIndicatorView,
- View.TRANSLATION_X, initialOffset, 0),
- ObjectAnimator.ofFloat(mIndicatorView, View.ALPHA, 0f,
- 1f));
- set.addListener(
- new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation,
- boolean isReverse) {
- if (mState == STATE_STOPPED) return;
-
- // Indicator is INVISIBLE at the moment, change it.
- mIndicatorView.setVisibility(View.VISIBLE);
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- onAppeared();
- }
- });
- set.start();
+ animateAppearance();
}
});
+ final boolean isLtr = mContext.getResources().getConfiguration().getLayoutDirection()
+ == View.LAYOUT_DIRECTION_LTR;
final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
WRAP_CONTENT,
WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
- layoutParams.gravity = Gravity.TOP | (mIsLtr ? Gravity.RIGHT : Gravity.LEFT);
+ layoutParams.gravity = Gravity.TOP | (isLtr ? Gravity.RIGHT : Gravity.LEFT);
layoutParams.setTitle(LAYOUT_PARAMS_TITLE);
layoutParams.packageName = mContext.getPackageName();
final WindowManager windowManager = (WindowManager) mContext.getSystemService(
Context.WINDOW_SERVICE);
windowManager.addView(mIndicatorView, layoutParams);
-
- mState = STATE_APPEARING;
}
- @UiThread
- private void hide() {
- if (DEBUG) Log.d(TAG, "Hide indicator");
-
- final int targetOffset = (mIsLtr ? 1 : -1) * (mIndicatorView.getWidth()
- - (int) mIconTextsContainer.getTranslationX());
- final AnimatorSet set = new AnimatorSet();
- set.playTogether(
- ObjectAnimator.ofFloat(mIndicatorView, View.TRANSLATION_X, targetOffset),
- ObjectAnimator.ofFloat(mIcon, View.ALPHA, 0f));
- set.setDuration(ANIMATION_DURATION);
- set.addListener(
- new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- onHidden();
- }
- });
- set.start();
- mState = STATE_DISAPPEARING;
+ private void animateAppearance() {
+ animateAlphaTo(1f);
+ }
+
+ private void animateDisappearance() {
+ animateAlphaTo(0f);
}
+ private void animateAlphaTo(final float endValue) {
+ if (mAnimator == null) {
+ if (DEBUG) Log.d(TAG, "set up animator");
- @UiThread
- private void onAppeared() {
- if (mState == STATE_STOPPED) return;
+ mAnimator = new ObjectAnimator();
+ mAnimator.setTarget(mIndicatorView);
+ mAnimator.setProperty(View.ALPHA);
+ mAnimator.addListener(new AnimatorListenerAdapter() {
+ boolean mCancelled;
- mState = STATE_SHOWN;
+ @Override
+ public void onAnimationStart(Animator animation, boolean isReverse) {
+ if (DEBUG) Log.d(TAG, "AnimatorListenerAdapter#onAnimationStart");
+ mCancelled = false;
+ }
- hideIndicatorIfNeeded();
- }
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ if (DEBUG) Log.d(TAG, "AnimatorListenerAdapter#onAnimationCancel");
+ mCancelled = true;
+ }
- @UiThread
- private void onHidden() {
- if (mState == STATE_STOPPED) return;
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (DEBUG) Log.d(TAG, "AnimatorListenerAdapter#onAnimationEnd");
+ // When ValueAnimator#cancel() is called it always calls onAnimationCancel(...)
+ // and then onAnimationEnd(...). We, however, only want to proceed here if the
+ // animation ended "naturally".
+ if (!mCancelled) {
+ onAnimationFinished();
+ }
+ }
+ });
+ } else if (mAnimator.isRunning()) {
+ if (DEBUG) Log.d(TAG, "cancel running animation");
+ mAnimator.cancel();
+ }
- removeIndicatorView();
- mState = STATE_NOT_SHOWN;
+ final float currentValue = mIndicatorView.getAlpha();
+ if (DEBUG) Log.d(TAG, "animate alpha to " + endValue + " from " + currentValue);
- if (hasActiveRecorders()) {
- // Got new recorders, show again.
- showIfNotShown();
+ mAnimator.setDuration((int) (Math.abs(currentValue - endValue) * ANIMATION_DURATION_MS));
+ mAnimator.setFloatValues(endValue);
+ mAnimator.start();
+ }
+
+ private void onAnimationFinished() {
+ if (DEBUG) Log.d(TAG, "onAnimationFinished");
+
+ if (mState == STATE_APPEARING) {
+ mState = STATE_SHOWN;
+ } else if (mState == STATE_DISAPPEARING) {
+ removeIndicatorView();
+ mState = STATE_NOT_SHOWN;
}
}
@@ -358,17 +344,16 @@ public class AudioRecordingDisclosureBar implements
}
private void removeIndicatorView() {
+ if (DEBUG) Log.d(TAG, "removeIndicatorView");
+
final WindowManager windowManager = (WindowManager) mContext.getSystemService(
Context.WINDOW_SERVICE);
windowManager.removeView(mIndicatorView);
mIndicatorView = null;
- mIconTextsContainer = null;
- mIconContainerBg = null;
- mIcon = null;
- mTextsContainers = null;
- mTextView = null;
- mBgEnd = null;
+ mAnimator = null;
+
+ mViewAndWindowAdded = false;
}
private static List<String> splitByComma(String string) {
diff --git a/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
new file mode 100644
index 000000000000..e9fcf1aa9598
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
@@ -0,0 +1,139 @@
+/*
+ * 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.toast;
+
+import android.animation.Animator;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.view.View;
+import android.widget.ToastPresenter;
+
+import com.android.internal.R;
+import com.android.systemui.plugins.ToastPlugin;
+
+/**
+ * SystemUI TextToast that can be customized by ToastPlugins. Should never instantiate this class
+ * directly. Instead, use {@link ToastFactory#createToast}.
+ */
+public class SystemUIToast implements ToastPlugin.Toast {
+ final Context mContext;
+ final CharSequence mText;
+ final ToastPlugin.Toast mPluginToast;
+
+ final int mDefaultGravity;
+ final int mDefaultY;
+ final int mDefaultX = 0;
+ final int mDefaultHorizontalMargin = 0;
+ final int mDefaultVerticalMargin = 0;
+
+ SystemUIToast(Context context, CharSequence text) {
+ this(context, text, null);
+ }
+
+ SystemUIToast(Context context, CharSequence text, ToastPlugin.Toast pluginToast) {
+ mContext = context;
+ mText = text;
+ mPluginToast = pluginToast;
+
+ mDefaultGravity = context.getResources().getInteger(R.integer.config_toastDefaultGravity);
+ mDefaultY = context.getResources().getDimensionPixelSize(R.dimen.toast_y_offset);
+ }
+
+ @Override
+ @NonNull
+ public Integer getGravity() {
+ if (isPluginToast() && mPluginToast.getGravity() != null) {
+ return mPluginToast.getGravity();
+ }
+ return mDefaultGravity;
+ }
+
+ @Override
+ @NonNull
+ public Integer getXOffset() {
+ if (isPluginToast() && mPluginToast.getXOffset() != null) {
+ return mPluginToast.getXOffset();
+ }
+ return mDefaultX;
+ }
+
+ @Override
+ @NonNull
+ public Integer getYOffset() {
+ if (isPluginToast() && mPluginToast.getYOffset() != null) {
+ return mPluginToast.getYOffset();
+ }
+ return mDefaultY;
+ }
+
+ @Override
+ @NonNull
+ public Integer getHorizontalMargin() {
+ if (isPluginToast() && mPluginToast.getHorizontalMargin() != null) {
+ return mPluginToast.getHorizontalMargin();
+ }
+ return mDefaultHorizontalMargin;
+ }
+
+ @Override
+ @NonNull
+ public Integer getVerticalMargin() {
+ if (isPluginToast() && mPluginToast.getVerticalMargin() != null) {
+ return mPluginToast.getVerticalMargin();
+ }
+ return mDefaultVerticalMargin;
+ }
+
+ @Override
+ @NonNull
+ public View getView() {
+ if (isPluginToast() && mPluginToast.getView() != null) {
+ return mPluginToast.getView();
+ }
+ return ToastPresenter.getTextToastView(mContext, mText);
+ }
+
+ @Override
+ @Nullable
+ public Animator getInAnimation() {
+ if (isPluginToast() && mPluginToast.getInAnimation() != null) {
+ return mPluginToast.getInAnimation();
+ }
+ return null;
+ }
+
+ @Override
+ @Nullable
+ public Animator getOutAnimation() {
+ if (isPluginToast() && mPluginToast.getOutAnimation() != null) {
+ return mPluginToast.getOutAnimation();
+ }
+ return null;
+ }
+
+ /**
+ * Whether this toast has a custom animation.
+ */
+ public boolean hasCustomAnimation() {
+ return getInAnimation() != null || getOutAnimation() != null;
+ }
+
+ private boolean isPluginToast() {
+ return mPluginToast != null;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java b/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java
new file mode 100644
index 000000000000..d8cb61c6b349
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java
@@ -0,0 +1,83 @@
+/*
+ * 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.toast;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+
+import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.ToastPlugin;
+import com.android.systemui.shared.plugins.PluginManager;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+import javax.inject.Inject;
+
+/**
+ * Factory for creating toasts to be shown by ToastUI.
+ * These toasts can be customized by {@link ToastPlugin}.
+ */
+@SysUISingleton
+public class ToastFactory implements Dumpable {
+ // only one ToastPlugin can be connected at a time.
+ private ToastPlugin mPlugin;
+
+ @Inject
+ public ToastFactory(PluginManager pluginManager, DumpManager dumpManager) {
+ dumpManager.registerDumpable("ToastFactory", this);
+ pluginManager.addPluginListener(
+ new PluginListener<ToastPlugin>() {
+ @Override
+ public void onPluginConnected(ToastPlugin plugin, Context pluginContext) {
+ mPlugin = plugin;
+ }
+
+ @Override
+ public void onPluginDisconnected(ToastPlugin plugin) {
+ if (plugin.equals(mPlugin)) {
+ mPlugin = null;
+ }
+ }
+ }, ToastPlugin.class, false /* Allow multiple plugins */);
+ }
+
+ /**
+ * Create a toast to be shown by ToastUI.
+ */
+ public SystemUIToast createToast(Context context, CharSequence text, String packageName,
+ int userId) {
+ if (isPluginAvailable()) {
+ return new SystemUIToast(context, text, mPlugin.createToast(text, packageName, userId));
+ }
+ return new SystemUIToast(context, text);
+ }
+
+ private boolean isPluginAvailable() {
+ return mPlugin != null;
+ }
+
+ @Override
+ public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+ pw.println("ToastFactory:");
+ pw.println(" mAttachedPlugin=" + mPlugin);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt b/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
new file mode 100644
index 000000000000..78173cf62a93
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.toast
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.LogMessage
+import com.android.systemui.log.dagger.ToastLog
+import javax.inject.Inject
+
+private const val TAG = "ToastLog"
+
+class ToastLogger @Inject constructor(
+ @ToastLog private val buffer: LogBuffer
+) {
+
+ fun logOnShowToast(uid: Int, packageName: String, text: String, token: String) {
+ log(DEBUG, {
+ int1 = uid
+ str1 = packageName
+ str2 = text
+ str3 = token
+ }, {
+ "[$str3] Show toast for ($str1, $int1). msg=\'$str2\'"
+ })
+ }
+
+ fun logOnHideToast(packageName: String, token: String) {
+ log(DEBUG, {
+ str1 = packageName
+ str2 = token
+ }, {
+ "[$str2] Hide toast for [$str1]"
+ })
+ }
+
+ private inline fun log(
+ logLevel: LogLevel,
+ initializer: LogMessage.() -> Unit,
+ noinline printer: LogMessage.() -> String
+ ) {
+ buffer.log(TAG, logLevel, initializer, printer)
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
index a2203732c47c..1c682e3bb7dc 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
@@ -16,25 +16,27 @@
package com.android.systemui.toast;
+import android.animation.Animator;
import android.annotation.MainThread;
import android.annotation.Nullable;
import android.app.INotificationManager;
import android.app.ITransientNotificationCallback;
import android.content.Context;
-import android.content.res.Resources;
import android.os.IBinder;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.util.Log;
-import android.view.View;
+import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.IAccessibilityManager;
+import android.widget.Toast;
import android.widget.ToastPresenter;
-import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.SystemUI;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.util.concurrency.DelayableExecutor;
import java.util.Objects;
@@ -45,35 +47,53 @@ import javax.inject.Inject;
*/
@SysUISingleton
public class ToastUI extends SystemUI implements CommandQueue.Callbacks {
+ // values from NotificationManagerService#LONG_DELAY and NotificationManagerService#SHORT_DELAY
+ private static final int TOAST_LONG_TIME = 3500; // 3.5 seconds
+ private static final int TOAST_SHORT_TIME = 2000; // 2 seconds
+
private static final String TAG = "ToastUI";
private final CommandQueue mCommandQueue;
private final INotificationManager mNotificationManager;
- private final IAccessibilityManager mAccessibilityManager;
- private final int mGravity;
- private final int mY;
+ private final IAccessibilityManager mIAccessibilityManager;
+ private final AccessibilityManager mAccessibilityManager;
+ private final ToastFactory mToastFactory;
+ private final DelayableExecutor mMainExecutor;
+ private final ToastLogger mToastLogger;
+ private SystemUIToast mToast;
@Nullable private ToastPresenter mPresenter;
@Nullable private ITransientNotificationCallback mCallback;
@Inject
- public ToastUI(Context context, CommandQueue commandQueue) {
+ public ToastUI(
+ Context context,
+ CommandQueue commandQueue,
+ ToastFactory toastFactory,
+ @Main DelayableExecutor mainExecutor,
+ ToastLogger toastLogger) {
this(context, commandQueue,
INotificationManager.Stub.asInterface(
ServiceManager.getService(Context.NOTIFICATION_SERVICE)),
IAccessibilityManager.Stub.asInterface(
- ServiceManager.getService(Context.ACCESSIBILITY_SERVICE)));
+ ServiceManager.getService(Context.ACCESSIBILITY_SERVICE)),
+ toastFactory,
+ mainExecutor,
+ toastLogger);
}
@VisibleForTesting
ToastUI(Context context, CommandQueue commandQueue, INotificationManager notificationManager,
- @Nullable IAccessibilityManager accessibilityManager) {
+ @Nullable IAccessibilityManager accessibilityManager,
+ ToastFactory toastFactory, DelayableExecutor mainExecutor, ToastLogger toastLogger
+ ) {
super(context);
mCommandQueue = commandQueue;
mNotificationManager = notificationManager;
- mAccessibilityManager = accessibilityManager;
- Resources resources = mContext.getResources();
- mGravity = resources.getInteger(R.integer.config_toastDefaultGravity);
- mY = resources.getDimensionPixelSize(R.dimen.toast_y_offset);
+ mIAccessibilityManager = accessibilityManager;
+ mToastFactory = toastFactory;
+ mMainExecutor = mainExecutor;
+ mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
+ mToastLogger = toastLogger;
}
@Override
@@ -88,12 +108,31 @@ public class ToastUI extends SystemUI implements CommandQueue.Callbacks {
if (mPresenter != null) {
hideCurrentToast();
}
- Context context = mContext.createContextAsUser(UserHandle.getUserHandleForUid(uid), 0);
- View view = ToastPresenter.getTextToastView(context, text);
+ UserHandle userHandle = UserHandle.getUserHandleForUid(uid);
+ Context context = mContext.createContextAsUser(userHandle, 0);
+ mToast = mToastFactory.createToast(context, text, packageName, userHandle.getIdentifier());
+
+ if (mToast.hasCustomAnimation()) {
+ if (mToast.getInAnimation() != null) {
+ mToast.getInAnimation().start();
+ }
+ final Animator hideAnimator = mToast.getOutAnimation();
+ if (hideAnimator != null) {
+ final long durationMillis = duration == Toast.LENGTH_LONG
+ ? TOAST_LONG_TIME : TOAST_SHORT_TIME;
+ final long updatedDuration = mAccessibilityManager.getRecommendedTimeoutMillis(
+ (int) durationMillis, AccessibilityManager.FLAG_CONTENT_TEXT);
+ mMainExecutor.executeDelayed(() -> hideAnimator.start(),
+ updatedDuration - hideAnimator.getTotalDuration());
+ }
+ }
mCallback = callback;
- mPresenter = new ToastPresenter(context, mAccessibilityManager, mNotificationManager,
+ mPresenter = new ToastPresenter(context, mIAccessibilityManager, mNotificationManager,
packageName);
- mPresenter.show(view, token, windowToken, duration, mGravity, 0, mY, 0, 0, mCallback);
+ mToastLogger.logOnShowToast(uid, packageName, text.toString(), token.toString());
+ mPresenter.show(mToast.getView(), token, windowToken, duration, mToast.getGravity(),
+ mToast.getXOffset(), mToast.getYOffset(), mToast.getHorizontalMargin(),
+ mToast.getVerticalMargin(), mCallback, mToast.hasCustomAnimation());
}
@Override
@@ -104,6 +143,7 @@ public class ToastUI extends SystemUI implements CommandQueue.Callbacks {
Log.w(TAG, "Attempt to hide non-current toast from package " + packageName);
return;
}
+ mToastLogger.logOnHideToast(packageName, token.toString());
hideCurrentToast();
}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
index 0070dcf9a604..4c724aeea9ae 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
@@ -15,6 +15,7 @@
*/
package com.android.systemui.tuner;
+import android.app.ActivityManager;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
@@ -22,6 +23,7 @@ import android.content.DialogInterface;
import android.hardware.display.AmbientDisplayConfiguration;
import android.os.Build;
import android.os.Bundle;
+import android.os.UserHandle;
import android.provider.Settings;
import android.view.Menu;
import android.view.MenuInflater;
@@ -122,7 +124,8 @@ public class TunerFragment extends PreferenceFragment {
getActivity().finish();
return true;
case MENU_REMOVE:
- TunerService.showResetRequest(getContext(), new Runnable() {
+ UserHandle user = new UserHandle(ActivityManager.getCurrentUser());
+ TunerService.showResetRequest(getContext(), user, new Runnable() {
@Override
public void run() {
if (getActivity() != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
index 338e1781abd0..70bba263ab90 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
@@ -14,7 +14,6 @@
package com.android.systemui.tuner;
-import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -51,25 +50,24 @@ public abstract class TunerService {
void onTuningChanged(String key, String newValue);
}
- private static Context userContext(Context context) {
+ private static Context userContext(Context context, UserHandle user) {
try {
- return context.createPackageContextAsUser(context.getPackageName(), 0,
- new UserHandle(ActivityManager.getCurrentUser()));
+ return context.createPackageContextAsUser(context.getPackageName(), 0, user);
} catch (NameNotFoundException e) {
return context;
}
}
- public static final void setTunerEnabled(Context context, boolean enabled) {
- userContext(context).getPackageManager().setComponentEnabledSetting(
+ public static final void setTunerEnabled(Context context, UserHandle user, boolean enabled) {
+ userContext(context, user).getPackageManager().setComponentEnabledSetting(
new ComponentName(context, TunerActivity.class),
enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
: PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
}
- public static final boolean isTunerEnabled(Context context) {
- return userContext(context).getPackageManager().getComponentEnabledSetting(
+ public static final boolean isTunerEnabled(Context context, UserHandle user) {
+ return userContext(context, user).getPackageManager().getComponentEnabledSetting(
new ComponentName(context, TunerActivity.class))
== PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
}
@@ -83,7 +81,8 @@ public abstract class TunerService {
}
}
- public static final void showResetRequest(final Context context, final Runnable onDisabled) {
+ public static final void showResetRequest(final Context context, UserHandle user,
+ final Runnable onDisabled) {
SystemUIDialog dialog = new SystemUIDialog(context);
dialog.setShowForAllUsers(true);
dialog.setMessage(R.string.remove_from_settings_prompt);
@@ -91,20 +90,20 @@ public abstract class TunerService {
(OnClickListener) null);
dialog.setButton(DialogInterface.BUTTON_POSITIVE,
context.getString(R.string.guest_exit_guest_dialog_remove), new OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- // Tell the tuner (in main SysUI process) to clear all its settings.
- context.sendBroadcast(new Intent(TunerService.ACTION_CLEAR));
- // Disable access to tuner.
- TunerService.setTunerEnabled(context, false);
- // Make them sit through the warning dialog again.
- Settings.Secure.putInt(context.getContentResolver(),
- TunerFragment.SETTING_SEEN_TUNER_WARNING, 0);
- if (onDisabled != null) {
- onDisabled.run();
- }
- }
- });
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ // Tell the tuner (in main SysUI process) to clear all its settings.
+ context.sendBroadcast(new Intent(TunerService.ACTION_CLEAR));
+ // Disable access to tuner.
+ TunerService.setTunerEnabled(context, user, false);
+ // Make them sit through the warning dialog again.
+ Settings.Secure.putInt(context.getContentResolver(),
+ TunerFragment.SETTING_SEEN_TUNER_WARNING, 0);
+ if (onDisabled != null) {
+ onDisabled.run();
+ }
+ }
+ });
dialog.show();
}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
index d9727a73f651..22f03e074b06 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
@@ -15,13 +15,13 @@
*/
package com.android.systemui.tuner;
-import android.app.ActivityManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.Looper;
import android.os.UserManager;
import android.provider.Settings;
@@ -37,7 +37,7 @@ import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.qs.QSTileHost;
-import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.util.leak.LeakDetector;
@@ -81,7 +81,8 @@ public class TunerServiceImpl extends TunerService {
private ContentResolver mContentResolver;
private int mCurrentUser;
- private CurrentUserTracker mUserTracker;
+ private UserTracker.Callback mCurrentUserTracker;
+ private UserTracker mUserTracker;
/**
*/
@@ -91,11 +92,13 @@ public class TunerServiceImpl extends TunerService {
@Main Handler mainHandler,
LeakDetector leakDetector,
DemoModeController demoModeController,
- BroadcastDispatcher broadcastDispatcher) {
+ BroadcastDispatcher broadcastDispatcher,
+ UserTracker userTracker) {
mContext = context;
mContentResolver = mContext.getContentResolver();
mLeakDetector = leakDetector;
mDemoModeController = demoModeController;
+ mUserTracker = userTracker;
for (UserInfo user : UserManager.get(mContext).getUsers()) {
mCurrentUser = user.getUserHandle().getIdentifier();
@@ -104,21 +107,22 @@ public class TunerServiceImpl extends TunerService {
}
}
- mCurrentUser = ActivityManager.getCurrentUser();
- mUserTracker = new CurrentUserTracker(broadcastDispatcher) {
+ mCurrentUser = mUserTracker.getUserId();
+ mCurrentUserTracker = new UserTracker.Callback() {
@Override
- public void onUserSwitched(int newUserId) {
- mCurrentUser = newUserId;
+ public void onUserChanged(int newUser, Context userContext) {
+ mCurrentUser = newUser;
reloadAll();
reregisterAll();
}
};
- mUserTracker.startTracking();
+ mUserTracker.addCallback(mCurrentUserTracker,
+ new HandlerExecutor(mainHandler));
}
@Override
public void destroy() {
- mUserTracker.stopTracking();
+ mUserTracker.removeCallback(mCurrentUserTracker);
}
private void upgradeTuner(int oldVersion, int newVersion, Handler mainHandler) {
@@ -137,7 +141,7 @@ public class TunerServiceImpl extends TunerService {
}
}
if (oldVersion < 2) {
- setTunerEnabled(mContext, false);
+ setTunerEnabled(mContext, mUserTracker.getUserHandle(), false);
}
// 3 Removed because of a revert.
if (oldVersion < 4) {
@@ -272,7 +276,7 @@ public class TunerServiceImpl extends TunerService {
@Override
public void onChange(boolean selfChange, java.util.Collection<Uri> uris,
int flags, int userId) {
- if (userId == ActivityManager.getCurrentUser()) {
+ if (userId == mUserTracker.getUserId()) {
for (Uri u : uris) {
reloadSetting(u);
}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java
index 22fa0106795a..bde88b1b5533 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java
@@ -17,11 +17,12 @@
package com.android.systemui.tv;
import com.android.systemui.dagger.GlobalRootComponent;
+import com.android.systemui.wmshell.TvPipModule;
import dagger.Binds;
import dagger.Module;
-@Module()
+@Module(includes = TvPipModule.class)
interface TvSystemUIBinder {
@Binds
GlobalRootComponent bindGlobalRootComponent(TvGlobalRootComponent globalRootComponent);
diff --git a/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java b/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java
new file mode 100644
index 000000000000..890ee5f45309
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java
@@ -0,0 +1,161 @@
+/*
+ * 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.user;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.app.IActivityManager;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.settingslib.users.EditUserInfoController;
+import com.android.systemui.R;
+
+import javax.inject.Inject;
+
+/**
+ * This screen shows a Dialog for choosing nickname and photo for a new user, and then delegates the
+ * user creation to a UserCreator.
+ */
+public class CreateUserActivity extends Activity {
+
+ /**
+ * Creates an intent to start this activity.
+ */
+ public static Intent createIntentForStart(Context context) {
+ return new Intent(context, CreateUserActivity.class);
+ }
+
+ private static final String TAG = "CreateUserActivity";
+ private static final String DIALOG_STATE_KEY = "create_user_dialog_state";
+
+ private final UserCreator mUserCreator;
+ private final EditUserInfoController mEditUserInfoController;
+ private final IActivityManager mActivityManager;
+
+ private Dialog mSetupUserDialog;
+
+ @Inject
+ public CreateUserActivity(UserCreator userCreator,
+ EditUserInfoController editUserInfoController, IActivityManager activityManager) {
+ mUserCreator = userCreator;
+ mEditUserInfoController = editUserInfoController;
+ mActivityManager = activityManager;
+ }
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setShowWhenLocked(true);
+ setContentView(R.layout.activity_create_new_user);
+
+ if (savedInstanceState != null) {
+ mEditUserInfoController.onRestoreInstanceState(savedInstanceState);
+ }
+
+ mSetupUserDialog = createDialog();
+ mSetupUserDialog.show();
+ }
+
+ @Override
+ protected void onSaveInstanceState(@NonNull Bundle outState) {
+ if (mSetupUserDialog != null && mSetupUserDialog.isShowing()) {
+ outState.putBundle(DIALOG_STATE_KEY, mSetupUserDialog.onSaveInstanceState());
+ }
+
+ mEditUserInfoController.onSaveInstanceState(outState);
+ super.onSaveInstanceState(outState);
+ }
+
+ @Override
+ protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
+ super.onRestoreInstanceState(savedInstanceState);
+ Bundle savedDialogState = savedInstanceState.getBundle(DIALOG_STATE_KEY);
+ if (savedDialogState != null && mSetupUserDialog != null) {
+ mSetupUserDialog.onRestoreInstanceState(savedDialogState);
+ }
+ }
+
+ private Dialog createDialog() {
+ String defaultUserName = getString(com.android.settingslib.R.string.user_new_user_name);
+
+ return mEditUserInfoController.createDialog(
+ this,
+ (intent, requestCode) -> {
+ mEditUserInfoController.startingActivityForResult();
+ startActivityForResult(intent, requestCode);
+ },
+ null,
+ defaultUserName,
+ getString(R.string.user_add_user),
+ this::addUserNow,
+ this::finish
+ );
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ mEditUserInfoController.onActivityResult(requestCode, resultCode, data);
+ }
+
+ @Override
+ public void onBackPressed() {
+ super.onBackPressed();
+ if (mSetupUserDialog != null) {
+ mSetupUserDialog.dismiss();
+ }
+ }
+
+ private void addUserNow(String userName, Drawable userIcon) {
+ mSetupUserDialog.dismiss();
+
+ userName = (userName == null || userName.trim().isEmpty())
+ ? getString(R.string.user_new_user_name)
+ : userName;
+
+ mUserCreator.createUser(userName, userIcon,
+ userInfo -> {
+ switchToUser(userInfo.id);
+ finishIfNeeded();
+ }, () -> {
+ Log.e(TAG, "Unable to create user");
+ finishIfNeeded();
+ });
+ }
+
+ private void finishIfNeeded() {
+ if (!isFinishing() && !isDestroyed()) {
+ finish();
+ }
+ }
+
+ private void switchToUser(int userId) {
+ try {
+ mActivityManager.switchUser(userId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Couldn't switch user.", e);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserCreator.java b/packages/SystemUI/src/com/android/systemui/user/UserCreator.java
new file mode 100644
index 000000000000..3a270bb77e46
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/UserCreator.java
@@ -0,0 +1,82 @@
+/*
+ * 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.user;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.graphics.drawable.Drawable;
+import android.os.UserManager;
+
+import com.android.internal.util.UserIcons;
+import com.android.settingslib.users.UserCreatingDialog;
+import com.android.settingslib.utils.ThreadUtils;
+
+import java.util.function.Consumer;
+
+import javax.inject.Inject;
+
+/**
+ * A class to do the user creation process. It shows a progress dialog, and manages the user
+ * creation
+ */
+public class UserCreator {
+
+ private final Context mContext;
+ private final UserManager mUserManager;
+
+ @Inject
+ public UserCreator(Context context, UserManager userManager) {
+ mContext = context;
+ mUserManager = userManager;
+ }
+
+ /**
+ * Shows a progress dialog then starts the user creation process on the main thread.
+ *
+ * @param successCallback is called when the user creation is successful.
+ * @param errorCallback is called when userManager.createUser returns null.
+ * (Exceptions are not handled by this class)
+ */
+ public void createUser(String userName, Drawable userIcon, Consumer<UserInfo> successCallback,
+ Runnable errorCallback) {
+
+ Dialog userCreationProgressDialog = new UserCreatingDialog(mContext);
+ userCreationProgressDialog.show();
+
+ // userManager.createUser will block the thread so post is needed for the dialog to show
+ ThreadUtils.postOnMainThread(() -> {
+ UserInfo user =
+ mUserManager.createUser(userName, UserManager.USER_TYPE_FULL_SECONDARY, 0);
+ if (user == null) {
+ // Couldn't create user for some reason
+ userCreationProgressDialog.dismiss();
+ errorCallback.run();
+ return;
+ }
+
+ Drawable newUserIcon = userIcon;
+ if (newUserIcon == null) {
+ newUserIcon = UserIcons.getDefaultUserIcon(mContext.getResources(), user.id, false);
+ }
+ mUserManager.setUserIcon(user.id, UserIcons.convertToBitmap(newUserIcon));
+
+ userCreationProgressDialog.dismiss();
+ successCallback.accept(user);
+ });
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserModule.java b/packages/SystemUI/src/com/android/systemui/user/UserModule.java
new file mode 100644
index 000000000000..0ad0984e8231
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/UserModule.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.user;
+
+import com.android.settingslib.users.EditUserInfoController;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Dagger module for User related classes.
+ */
+@Module
+public class UserModule {
+
+ private static final String FILE_PROVIDER_AUTHORITY = "com.android.systemui.fileprovider";
+
+ @Provides
+ EditUserInfoController provideEditUserInfoController() {
+ return new EditUserInfoController(FILE_PROVIDER_AUTHORITY);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/DismissCircleView.java b/packages/SystemUI/src/com/android/systemui/util/DismissCircleView.java
deleted file mode 100644
index 8946c97a4b58..000000000000
--- a/packages/SystemUI/src/com/android/systemui/util/DismissCircleView.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.util;
-
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.view.Gravity;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-
-import com.android.systemui.R;
-
-/**
- * Circular view with a semitransparent, circular background with an 'X' inside it.
- *
- * This is used by both Bubbles and PIP as the dismiss target.
- */
-public class DismissCircleView extends FrameLayout {
-
- private final ImageView mIconView = new ImageView(getContext());
-
- public DismissCircleView(Context context) {
- super(context);
- final Resources res = getResources();
-
- setBackground(res.getDrawable(R.drawable.dismiss_circle_background));
-
- mIconView.setImageDrawable(res.getDrawable(R.drawable.pip_ic_close_white));
- addView(mIconView);
-
- setViewSizes();
- }
-
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- setViewSizes();
- }
-
- /** Retrieves the current dimensions for the icon and circle and applies them. */
- private void setViewSizes() {
- final Resources res = getResources();
- final int iconSize = res.getDimensionPixelSize(R.dimen.dismiss_target_x_size);
- mIconView.setLayoutParams(
- new FrameLayout.LayoutParams(iconSize, iconSize, Gravity.CENTER));
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt
deleted file mode 100644
index bcfb2afeeda1..000000000000
--- a/packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt
+++ /dev/null
@@ -1,350 +0,0 @@
-package com.android.systemui.util
-
-import android.graphics.Rect
-import android.util.Log
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.util.FloatingContentCoordinator.FloatingContent
-import java.util.HashMap
-
-/** Tag for debug logging. */
-private const val TAG = "FloatingCoordinator"
-
-/**
- * Coordinates the positions and movement of floating content, such as PIP and Bubbles, to ensure
- * that they don't overlap. If content does overlap due to content appearing or moving, the
- * coordinator will ask content to move to resolve the conflict.
- *
- * After implementing [FloatingContent], content should call [onContentAdded] to begin coordination.
- * Subsequently, call [onContentMoved] whenever the content moves, and the coordinator will move
- * other content out of the way. [onContentRemoved] should be called when the content is removed or
- * no longer visible.
- */
-
-@SysUISingleton
-class FloatingContentCoordinator constructor() {
- /**
- * Represents a piece of floating content, such as PIP or the Bubbles stack. Provides methods
- * that allow the [FloatingContentCoordinator] to determine the current location of the content,
- * as well as the ability to ask it to move out of the way of other content.
- *
- * The default implementation of [calculateNewBoundsOnOverlap] moves the content up or down,
- * depending on the position of the conflicting content. You can override this method if you
- * want your own custom conflict resolution logic.
- */
- interface FloatingContent {
-
- /**
- * Return the bounds claimed by this content. This should include the bounds occupied by the
- * content itself, as well as any padding, if desired. The coordinator will ensure that no
- * other content is located within these bounds.
- *
- * If the content is animating, this method should return the bounds to which the content is
- * animating. If that animation is cancelled, or updated, be sure that your implementation
- * of this method returns the appropriate bounds, and call [onContentMoved] so that the
- * coordinator moves other content out of the way.
- */
- fun getFloatingBoundsOnScreen(): Rect
-
- /**
- * Return the area within which this floating content is allowed to move. When resolving
- * conflicts, the coordinator will never ask your content to move to a position where any
- * part of the content would be out of these bounds.
- */
- fun getAllowedFloatingBoundsRegion(): Rect
-
- /**
- * Called when the coordinator needs this content to move to the given bounds. It's up to
- * you how to do that.
- *
- * Note that if you start an animation to these bounds, [getFloatingBoundsOnScreen] should
- * return the destination bounds, not the in-progress animated bounds. This is so the
- * coordinator knows where floating content is going to be and can resolve conflicts
- * accordingly.
- */
- fun moveToBounds(bounds: Rect)
-
- /**
- * Called by the coordinator when it needs to find a new home for this floating content,
- * because a new or moving piece of content is now overlapping with it.
- *
- * [findAreaForContentVertically] and [findAreaForContentAboveOrBelow] are helpful utility
- * functions that will find new bounds for your content automatically. Unless you require
- * specific conflict resolution logic, these should be sufficient. By default, this method
- * delegates to [findAreaForContentVertically].
- *
- * @param overlappingContentBounds The bounds of the other piece of content, which
- * necessitated this content's relocation. Your new position must not overlap with these
- * bounds.
- * @param otherContentBounds The bounds of any other pieces of floating content. Your new
- * position must not overlap with any of these either. These bounds are guaranteed to be
- * non-overlapping.
- * @return The new bounds for this content.
- */
- @JvmDefault
- fun calculateNewBoundsOnOverlap(
- overlappingContentBounds: Rect,
- otherContentBounds: List<Rect>
- ): Rect {
- return findAreaForContentVertically(
- getFloatingBoundsOnScreen(),
- overlappingContentBounds,
- otherContentBounds,
- getAllowedFloatingBoundsRegion())
- }
- }
-
- /** The bounds of all pieces of floating content added to the coordinator. */
- private val allContentBounds: MutableMap<FloatingContent, Rect> = HashMap()
-
- /**
- * Whether we are currently resolving conflicts by asking content to move. If we are, we'll
- * temporarily ignore calls to [onContentMoved] - those calls are from the content that is
- * moving to new, conflict-free bounds, so we don't need to perform conflict detection
- * calculations in response.
- */
- private var currentlyResolvingConflicts = false
-
- /**
- * Makes the coordinator aware of a new piece of floating content, and moves any existing
- * content out of the way, if necessary.
- *
- * If you don't want your new content to move existing content, use [getOccupiedBounds] to find
- * an unoccupied area, and move the content there before calling this method.
- */
- fun onContentAdded(newContent: FloatingContent) {
- updateContentBounds()
- allContentBounds[newContent] = newContent.getFloatingBoundsOnScreen()
- maybeMoveConflictingContent(newContent)
- }
-
- /**
- * Called to notify the coordinator that a piece of floating content has moved (or is animating)
- * to a new position, and that any conflicting floating content should be moved out of the way.
- *
- * The coordinator will call [FloatingContent.getFloatingBoundsOnScreen] to find the new bounds
- * for the moving content. If you're animating the content, be sure that your implementation of
- * getFloatingBoundsOnScreen returns the bounds to which it's animating, not the content's
- * current bounds.
- *
- * If the animation moving this content is cancelled or updated, you'll need to call this method
- * again, to ensure that content is moved out of the way of the latest bounds.
- *
- * @param content The content that has moved.
- */
- fun onContentMoved(content: FloatingContent) {
-
- // Ignore calls when we are currently resolving conflicts, since those calls are from
- // content that is moving to new, conflict-free bounds.
- if (currentlyResolvingConflicts) {
- return
- }
-
- if (!allContentBounds.containsKey(content)) {
- Log.wtf(TAG, "Received onContentMoved call before onContentAdded! " +
- "This should never happen.")
- return
- }
-
- updateContentBounds()
- maybeMoveConflictingContent(content)
- }
-
- /**
- * Called to notify the coordinator that a piece of floating content has been removed or is no
- * longer visible.
- */
- fun onContentRemoved(removedContent: FloatingContent) {
- allContentBounds.remove(removedContent)
- }
-
- /**
- * Returns a set of Rects that represent the bounds of all of the floating content on the
- * screen.
- *
- * [onContentAdded] will move existing content out of the way if the added content intersects
- * existing content. That's fine - but if your specific starting position is not important, you
- * can use this function to find unoccupied space for your content before calling
- * [onContentAdded], so that moving existing content isn't necessary.
- */
- fun getOccupiedBounds(): Collection<Rect> {
- return allContentBounds.values
- }
-
- /**
- * Identifies any pieces of content that are now overlapping with the given content, and asks
- * them to move out of the way.
- */
- private fun maybeMoveConflictingContent(fromContent: FloatingContent) {
- currentlyResolvingConflicts = true
-
- val conflictingNewBounds = allContentBounds[fromContent]!!
- allContentBounds
- // Filter to content that intersects with the new bounds. That's content that needs
- // to move.
- .filter { (content, bounds) ->
- content != fromContent && Rect.intersects(conflictingNewBounds, bounds) }
- // Tell that content to get out of the way, and save the bounds it says it's moving
- // (or animating) to.
- .forEach { (content, bounds) ->
- 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
- }
-
- /**
- * Update [allContentBounds] by calling [FloatingContent.getFloatingBoundsOnScreen] for all
- * content and saving the result.
- */
- private fun updateContentBounds() {
- allContentBounds.keys.forEach { allContentBounds[it] = it.getFloatingBoundsOnScreen() }
- }
-
- companion object {
- /**
- * Finds new bounds for the given content, either above or below its current position. The
- * new bounds won't intersect with the newly overlapping rect or the exclusion rects, and
- * will be within the allowed bounds unless no possible position exists.
- *
- * You can use this method to help find a new position for your content when the coordinator
- * calls [FloatingContent.moveToAreaExcluding].
- *
- * @param contentRect The bounds of the content for which we're finding a new home.
- * @param newlyOverlappingRect The bounds of the content that forced this relocation by
- * intersecting with the content we now need to move. If the overlapping content is
- * overlapping the top half of this content, we'll try to move this content downward if
- * possible (since the other content is 'pushing' it down), and vice versa.
- * @param exclusionRects Any other areas that we need to avoid when finding a new home for
- * the content. These areas must be non-overlapping with each other.
- * @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 - or an empty Rect if no in-bounds
- * position exists.
- */
- @JvmStatic
- fun findAreaForContentVertically(
- contentRect: Rect,
- newlyOverlappingRect: Rect,
- exclusionRects: Collection<Rect>,
- allowedBounds: Rect
- ): Rect {
- // If the newly overlapping Rect's center is above the content's center, we'll prefer to
- // find a space for this content that is below the overlapping content, since it's
- // 'pushing' it down. This may not be possible due to to screen bounds, in which case
- // we'll find space in the other direction.
- val overlappingContentPushingDown =
- newlyOverlappingRect.centerY() < contentRect.centerY()
-
- // Filter to exclusion rects that are above or below the content that we're finding a
- // place for. Then, split into two lists - rects above the content, and rects below it.
- var (rectsToAvoidAbove, rectsToAvoidBelow) = exclusionRects
- .filter { rectToAvoid -> rectsIntersectVertically(rectToAvoid, contentRect) }
- .partition { rectToAvoid -> rectToAvoid.top < contentRect.top }
-
- // Lazily calculate the closest possible new tops for the content, above and below its
- // current location.
- val newContentBoundsAbove by lazy { findAreaForContentAboveOrBelow(
- contentRect,
- exclusionRects = rectsToAvoidAbove.plus(newlyOverlappingRect),
- findAbove = true) }
- val newContentBoundsBelow by lazy { findAreaForContentAboveOrBelow(
- contentRect,
- exclusionRects = rectsToAvoidBelow.plus(newlyOverlappingRect),
- findAbove = false) }
-
- val positionAboveInBounds by lazy { allowedBounds.contains(newContentBoundsAbove) }
- val positionBelowInBounds by lazy { allowedBounds.contains(newContentBoundsBelow) }
-
- // Use the 'below' position if the content is being overlapped from the top, unless it's
- // out of bounds. Also use it if the content is being overlapped from the bottom, but
- // the 'above' position is out of bounds. Otherwise, use the 'above' position.
- val usePositionBelow =
- overlappingContentPushingDown && positionBelowInBounds ||
- !overlappingContentPushingDown && !positionAboveInBounds
-
- // Return the content rect, but offset to reflect the new position.
- 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()
- }
-
- /**
- * Finds a new position for the given content, either above or below its current position
- * depending on whether [findAbove] is true or false, respectively. This new position will
- * not intersect with any of the [exclusionRects].
- *
- * This method is useful as a helper method for implementing your own conflict resolution
- * logic. Otherwise, you'd want to use [findAreaForContentVertically], which takes screen
- * bounds and conflicting bounds' location into account when deciding whether to move to new
- * bounds above or below the current bounds.
- *
- * @param contentRect The content we're finding an area for.
- * @param exclusionRects The areas we need to avoid when finding a new area for the content.
- * These areas must be non-overlapping with each other.
- * @param findAbove Whether we are finding an area above the content's current position,
- * rather than an area below it.
- */
- fun findAreaForContentAboveOrBelow(
- contentRect: Rect,
- exclusionRects: Collection<Rect>,
- findAbove: Boolean
- ): Rect {
- // Sort the rects, since we want to move the content as little as possible. We'll
- // start with the rects closest to the content and move outward. If we're finding an
- // area above the content, that means we sort in reverse order to search the rects
- // from highest to lowest y-value.
- val sortedExclusionRects =
- exclusionRects.sortedBy { if (findAbove) -it.top else it.top }
-
- val proposedNewBounds = Rect(contentRect)
- for (exclusionRect in sortedExclusionRects) {
- // If the proposed new bounds don't intersect with this exclusion rect, that
- // means there's room for the content here. We know this because the rects are
- // sorted and non-overlapping, so any subsequent exclusion rects would be higher
- // (or lower) than this one and can't possibly intersect if this one doesn't.
- if (!Rect.intersects(proposedNewBounds, exclusionRect)) {
- break
- } else {
- // Otherwise, we need to keep searching for new bounds. If we're finding an
- // area above, propose new bounds that place the content just above the
- // exclusion rect. If we're finding an area below, propose new bounds that
- // place the content just below the exclusion rect.
- val verticalOffset =
- if (findAbove) -contentRect.height() else exclusionRect.height()
- proposedNewBounds.offsetTo(
- proposedNewBounds.left,
- exclusionRect.top + verticalOffset)
- }
- }
-
- return proposedNewBounds
- }
-
- /** Returns whether or not the two Rects share any of the same space on the X axis. */
- private fun rectsIntersectVertically(r1: Rect, r2: Rect): Boolean {
- return (r1.left >= r2.left && r1.left <= r2.right) ||
- (r1.right <= r2.right && r1.right >= r2.left)
- }
- }
-} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
index a6cd350b33ce..344f0d2f5506 100644
--- a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
+++ b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
@@ -27,7 +27,6 @@ import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.qs.QSFooterImpl;
import com.android.systemui.qs.QSPanel;
import com.android.systemui.qs.QuickQSPanel;
-import com.android.systemui.qs.QuickStatusBarHeader;
import com.android.systemui.qs.customize.QSCustomizer;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
@@ -93,10 +92,6 @@ public class InjectionInflationController {
}
/**
- * Creates the QuickStatusBarHeader.
- */
- QuickStatusBarHeader createQsHeader();
- /**
* Creates the QSFooterImpl.
*/
QSFooterImpl createQsFooter();
diff --git a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
index 90b95ea9562e..0d63324966fd 100644
--- a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
+++ b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
@@ -25,6 +25,7 @@ import android.provider.Settings;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
+import com.android.wm.shell.pip.tv.PipNotification;
import java.util.Arrays;
@@ -34,8 +35,8 @@ public class NotificationChannels extends SystemUI {
public static String SCREENSHOTS_HEADSUP = "SCN_HEADSUP";
public static String GENERAL = "GEN";
public static String STORAGE = "DSK";
- public static String TVPIP = "TPP";
public static String BATTERY = "BAT";
+ public static String TVPIP = PipNotification.NOTIFICATION_CHANNEL_TVPIP;
public static String HINTS = "HNT";
public NotificationChannels(Context context) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/FloatProperties.kt b/packages/SystemUI/src/com/android/systemui/util/animation/FloatProperties.kt
deleted file mode 100644
index a284a747da21..000000000000
--- a/packages/SystemUI/src/com/android/systemui/util/animation/FloatProperties.kt
+++ /dev/null
@@ -1,141 +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.util.animation
-
-import android.graphics.Rect
-import android.graphics.RectF
-import androidx.dynamicanimation.animation.FloatPropertyCompat
-
-/**
- * Helpful extra properties to use with the [PhysicsAnimator]. These allow you to animate objects
- * such as [Rect] and [RectF].
- *
- * There are additional, more basic properties available in [DynamicAnimation].
- */
-class FloatProperties {
- companion object {
- /**
- * Represents the x-coordinate of a [Rect]. Typically used to animate moving a Rect
- * horizontally.
- *
- * This property's getter returns [Rect.left], and its setter uses [Rect.offsetTo], which
- * sets [Rect.left] to the new value and offsets [Rect.right] so that the width of the Rect
- * does not change.
- */
- @JvmField
- val RECT_X = object : FloatPropertyCompat<Rect>("RectX") {
- override fun setValue(rect: Rect?, value: Float) {
- rect?.offsetTo(value.toInt(), rect.top)
- }
-
- override fun getValue(rect: Rect?): Float {
- return rect?.left?.toFloat() ?: -Float.MAX_VALUE
- }
- }
-
- /**
- * Represents the y-coordinate of a [Rect]. Typically used to animate moving a Rect
- * vertically.
- *
- * This property's getter returns [Rect.top], and its setter uses [Rect.offsetTo], which
- * sets [Rect.top] to the new value and offsets [Rect.bottom] so that the height of the Rect
- * does not change.
- */
- @JvmField
- val RECT_Y = object : FloatPropertyCompat<Rect>("RectY") {
- override fun setValue(rect: Rect?, value: Float) {
- rect?.offsetTo(rect.left, value.toInt())
- }
-
- override fun getValue(rect: Rect?): Float {
- return rect?.top?.toFloat() ?: -Float.MAX_VALUE
- }
- }
-
- /**
- * Represents the width of a [Rect]. Typically used to animate resizing a Rect horizontally.
- *
- * This property's getter returns [Rect.width], and its setter changes the value of
- * [Rect.right] by adding the animated width value to [Rect.left].
- */
- @JvmField
- val RECT_WIDTH = object : FloatPropertyCompat<Rect>("RectWidth") {
- override fun getValue(rect: Rect): Float {
- return rect.width().toFloat()
- }
-
- override fun setValue(rect: Rect, value: Float) {
- rect.right = rect.left + value.toInt()
- }
- }
-
- /**
- * Represents the height of a [Rect]. Typically used to animate resizing a Rect vertically.
- *
- * This property's getter returns [Rect.height], and its setter changes the value of
- * [Rect.bottom] by adding the animated height value to [Rect.top].
- */
- @JvmField
- val RECT_HEIGHT = object : FloatPropertyCompat<Rect>("RectHeight") {
- override fun getValue(rect: Rect): Float {
- return rect.height().toFloat()
- }
-
- override fun setValue(rect: Rect, value: Float) {
- rect.bottom = rect.top + value.toInt()
- }
- }
-
- /**
- * Represents the x-coordinate of a [RectF]. Typically used to animate moving a RectF
- * horizontally.
- *
- * This property's getter returns [RectF.left], and its setter uses [RectF.offsetTo], which
- * sets [RectF.left] to the new value and offsets [RectF.right] so that the width of the
- * RectF does not change.
- */
- @JvmField
- val RECTF_X = object : FloatPropertyCompat<RectF>("RectFX") {
- override fun setValue(rect: RectF?, value: Float) {
- rect?.offsetTo(value, rect.top)
- }
-
- override fun getValue(rect: RectF?): Float {
- return rect?.left ?: -Float.MAX_VALUE
- }
- }
-
- /**
- * Represents the y-coordinate of a [RectF]. Typically used to animate moving a RectF
- * vertically.
- *
- * This property's getter returns [RectF.top], and its setter uses [RectF.offsetTo], which
- * sets [RectF.top] to the new value and offsets [RectF.bottom] so that the height of the
- * RectF does not change.
- */
- @JvmField
- val RECTF_Y = object : FloatPropertyCompat<RectF>("RectFY") {
- override fun setValue(rect: RectF?, value: Float) {
- rect?.offsetTo(rect.left, value)
- }
-
- override fun getValue(rect: RectF?): Float {
- return rect?.top ?: -Float.MAX_VALUE
- }
- }
- }
-} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
deleted file mode 100644
index 2a5424ce4ef7..000000000000
--- a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
+++ /dev/null
@@ -1,1071 +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.util.animation
-
-import android.os.Looper
-import android.util.ArrayMap
-import android.util.Log
-import android.view.View
-import androidx.dynamicanimation.animation.AnimationHandler
-import androidx.dynamicanimation.animation.DynamicAnimation
-import androidx.dynamicanimation.animation.FlingAnimation
-import androidx.dynamicanimation.animation.FloatPropertyCompat
-import androidx.dynamicanimation.animation.SpringAnimation
-import androidx.dynamicanimation.animation.SpringForce
-import com.android.systemui.util.animation.PhysicsAnimator.Companion.getInstance
-import java.lang.ref.WeakReference
-import java.util.WeakHashMap
-import kotlin.math.abs
-import kotlin.math.max
-import kotlin.math.min
-
-/**
- * Extension function for all objects which will return a PhysicsAnimator instance for that object.
- */
-val <T : View> T.physicsAnimator: PhysicsAnimator<T> get() { return getInstance(this) }
-
-private const val TAG = "PhysicsAnimator"
-
-private val UNSET = -Float.MAX_VALUE
-
-/**
- * [FlingAnimation] multiplies the friction set via [FlingAnimation.setFriction] by 4.2f, which is
- * where this number comes from. We use it in [PhysicsAnimator.flingThenSpring] to calculate the
- * minimum velocity for a fling to reach a certain value, given the fling's friction.
- */
-private const val FLING_FRICTION_SCALAR_MULTIPLIER = 4.2f
-
-typealias EndAction = () -> Unit
-
-/** A map of Property -> AnimationUpdate, which is provided to update listeners on each frame. */
-typealias UpdateMap<T> =
- ArrayMap<FloatPropertyCompat<in T>, PhysicsAnimator.AnimationUpdate>
-
-/**
- * Map of the animators associated with a given object. This ensures that only one animator
- * per object exists.
- */
-internal val animators = WeakHashMap<Any, PhysicsAnimator<*>>()
-
-/**
- * Default spring configuration to use for animations where stiffness and/or damping ratio
- * were not provided, and a default spring was not set via [PhysicsAnimator.setDefaultSpringConfig].
- */
-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, 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. */
-private var verboseLogging = false
-
-/**
- * Animator that uses physics-based animations to animate properties on views and objects. Physics
- * animations use real-world physical concepts, such as momentum and mass, to realistically simulate
- * motion. PhysicsAnimator is heavily inspired by [android.view.ViewPropertyAnimator], and
- * also uses the builder pattern to configure and start animations.
- *
- * The physics animations are backed by [DynamicAnimation].
- *
- * @param T The type of the object being animated.
- */
-class PhysicsAnimator<T> private constructor (target: T) {
- /** Weak reference to the animation target. */
- val weakTarget = WeakReference(target)
-
- /** Data class for representing animation frame updates. */
- data class AnimationUpdate(val value: Float, val velocity: Float)
-
- /** [DynamicAnimation] instances for the given properties. */
- private val springAnimations = ArrayMap<FloatPropertyCompat<in T>, SpringAnimation>()
- private val flingAnimations = ArrayMap<FloatPropertyCompat<in T>, FlingAnimation>()
-
- /**
- * Spring and fling configurations for the properties to be animated on the target. We'll
- * configure and start the DynamicAnimations for these properties according to the provided
- * configurations.
- */
- private val springConfigs = ArrayMap<FloatPropertyCompat<in T>, SpringConfig>()
- private val flingConfigs = ArrayMap<FloatPropertyCompat<in T>, FlingConfig>()
-
- /**
- * Animation listeners for the animation. These will be notified when each property animation
- * updates or ends.
- */
- private val updateListeners = ArrayList<UpdateListener<T>>()
- private val endListeners = ArrayList<EndListener<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
-
- /**
- * AnimationHandler to use if it need custom AnimationHandler, if this is null, it will use
- * the default AnimationHandler in the DynamicAnimation.
- */
- private var customAnimationHandler: AnimationHandler? = null
-
- /**
- * Internal listeners that respond to DynamicAnimations updating and ending, and dispatch to
- * the listeners provided via [addUpdateListener] and [addEndListener]. This allows us to add
- * just one permanent update and end listener to the DynamicAnimations.
- */
- internal var internalListeners = ArrayList<InternalListener>()
-
- /**
- * Action to run when [start] is called. This can be changed by
- * [PhysicsAnimatorTestUtils.prepareForTest] to enable animators to run under test and provide
- * helpful test utilities.
- */
- internal var startAction: () -> Unit = ::startInternal
-
- /**
- * Action to run when [cancel] is called. This can be changed by
- * [PhysicsAnimatorTestUtils.prepareForTest] to cancel animations from the main thread, which
- * is required.
- */
- internal var cancelAction: (Set<FloatPropertyCompat<in T>>) -> Unit = ::cancelInternal
-
- /**
- * Springs a property to the given value, using the provided configuration settings.
- *
- * Springs are used when you know the exact value to which you want to animate. They can be
- * configured with a start velocity (typically used when the spring is initiated by a touch
- * event), but this velocity will be realistically attenuated as forces are applied to move the
- * property towards the end value.
- *
- * If you find yourself repeating the same stiffness and damping ratios many times, consider
- * storing a single [SpringConfig] instance and passing that in instead of individual values.
- *
- * @param property The property to spring to the given value. The property must be an instance
- * of FloatPropertyCompat&lt;? super T&gt;. For example, if this is a
- * PhysicsAnimator&lt;FrameLayout&gt;, you can use a FloatPropertyCompat&lt;FrameLayout&gt;, as
- * well as a FloatPropertyCompat&lt;ViewGroup&gt;, and so on.
- * @param toPosition The value to spring the given property to.
- * @param startVelocity The initial velocity to use for the animation.
- * @param stiffness The stiffness to use for the spring. Higher stiffness values result in
- * faster animations, while lower stiffness means a slower animation. Reasonable values for
- * low, medium, and high stiffness can be found as constants in [SpringForce].
- * @param dampingRatio The damping ratio (bounciness) to use for the spring. Higher values
- * result in a less 'springy' animation, while lower values allow the animation to bounce
- * back and forth for a longer time after reaching the final position. Reasonable values for
- * low, medium, and high damping can be found in [SpringForce].
- */
- fun spring(
- property: FloatPropertyCompat<in T>,
- toPosition: Float,
- startVelocity: Float = 0f,
- stiffness: Float = defaultSpring.stiffness,
- dampingRatio: Float = defaultSpring.dampingRatio
- ): PhysicsAnimator<T> {
- if (verboseLogging) {
- Log.d(TAG, "Springing ${getReadablePropertyName(property)} to $toPosition.")
- }
-
- springConfigs[property] =
- SpringConfig(stiffness, dampingRatio, startVelocity, toPosition)
- return this
- }
-
- /**
- * Springs a property to a given value using the provided start velocity and configuration
- * options.
- *
- * @see spring
- */
- fun spring(
- property: FloatPropertyCompat<in T>,
- toPosition: Float,
- startVelocity: Float,
- config: SpringConfig = defaultSpring
- ): PhysicsAnimator<T> {
- return spring(
- property, toPosition, startVelocity, config.stiffness, config.dampingRatio)
- }
-
- /**
- * 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,
- config: SpringConfig = defaultSpring
- ): PhysicsAnimator<T> {
- return spring(property, toPosition, 0f, config)
- }
-
- /**
- * 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.
- *
- * Flings are used when you have a start velocity, and want the property value to realistically
- * decrease as friction is applied until the velocity reaches zero. Flings do not have a
- * deterministic end value. If you are attempting to animate to a specific end value, use
- * [spring].
- *
- * If you find yourself repeating the same friction/min/max values, consider storing a single
- * [FlingConfig] and passing that in instead.
- *
- * @param property The property to fling using the given start velocity.
- * @param startVelocity The start velocity (in pixels per second) with which to start the fling.
- * @param friction Friction value applied to slow down the animation over time. Higher values
- * will more quickly slow the animation. Typical friction values range from 1f to 10f.
- * @param min The minimum value allowed for the animation. If this value is reached, the
- * animation will end abruptly.
- * @param max The maximum value allowed for the animation. If this value is reached, the
- * animation will end abruptly.
- */
- fun fling(
- property: FloatPropertyCompat<in T>,
- startVelocity: Float,
- friction: Float = defaultFling.friction,
- min: Float = defaultFling.min,
- max: Float = defaultFling.max
- ): PhysicsAnimator<T> {
- if (verboseLogging) {
- Log.d(TAG, "Flinging ${getReadablePropertyName(property)} " +
- "with velocity $startVelocity.")
- }
-
- flingConfigs[property] = FlingConfig(friction, min, max, startVelocity)
- return this
- }
-
- /**
- * Flings a property using the given start velocity, using a [FlingAnimation] configured using
- * the provided configuration settings.
- *
- * @see fling
- */
- fun fling(
- property: FloatPropertyCompat<in T>,
- startVelocity: Float,
- config: FlingConfig = defaultFling
- ): PhysicsAnimator<T> {
- return fling(property, startVelocity, config.friction, config.min, config.max)
- }
-
- /**
- * Flings a property using the given start velocity. If the fling animation reaches the min/max
- * bounds (from the [flingConfig]) with velocity remaining, it'll overshoot it and spring back.
- *
- * If the object is already out of the fling bounds, it will immediately spring back within
- * bounds.
- *
- * This is useful for animating objects that are bounded by constraints such as screen edges,
- * since otherwise the fling animation would end abruptly upon reaching the min/max bounds.
- *
- * @param property The property to animate.
- * @param startVelocity The velocity, in pixels/second, with which to start the fling. If the
- * object is already outside the fling bounds, this velocity will be used as the start velocity
- * of the spring that will spring it back within bounds.
- * @param flingMustReachMinOrMax If true, the fling animation is guaranteed to reach either its
- * minimum bound (if [startVelocity] is negative) or maximum bound (if it's positive). The
- * animator will use startVelocity if it's sufficient, or add more velocity if necessary. This
- * is useful when fling's deceleration-based physics are preferable to the acceleration-based
- * forces used by springs - typically, when you're allowing the user to move an object somewhere
- * on the screen, but it needs to be along an edge.
- * @param flingConfig The configuration to use for the fling portion of the animation.
- * @param springConfig The configuration to use for the spring portion of the animation.
- */
- @JvmOverloads
- fun flingThenSpring(
- property: FloatPropertyCompat<in T>,
- startVelocity: Float,
- flingConfig: FlingConfig,
- springConfig: SpringConfig,
- flingMustReachMinOrMax: Boolean = false
- ): PhysicsAnimator<T> {
- val target = weakTarget.get()
- if (target == null) {
- Log.w(TAG, "Trying to animate a GC-ed target.")
- return this
- }
- val flingConfigCopy = flingConfig.copy()
- val springConfigCopy = springConfig.copy()
- val toAtLeast = if (startVelocity < 0) flingConfig.min else flingConfig.max
-
- if (flingMustReachMinOrMax && isValidValue(toAtLeast)) {
- val currentValue = property.getValue(target)
- val flingTravelDistance =
- startVelocity / (flingConfig.friction * FLING_FRICTION_SCALAR_MULTIPLIER)
- val projectedFlingEndValue = currentValue + flingTravelDistance
- val midpoint = (flingConfig.min + flingConfig.max) / 2
-
- // If fling velocity is too low to push the target past the midpoint between min and
- // max, then spring back towards the nearest edge, starting with the current velocity.
- if ((startVelocity < 0 && projectedFlingEndValue > midpoint) ||
- (startVelocity > 0 && projectedFlingEndValue < midpoint)) {
- val toPosition =
- if (projectedFlingEndValue < midpoint) flingConfig.min else flingConfig.max
- if (isValidValue(toPosition)) {
- return spring(property, toPosition, startVelocity, springConfig)
- }
- }
-
- // Projected fling end value is past the midpoint, so fling forward.
- val distanceToDestination = toAtLeast - property.getValue(target)
-
- // The minimum velocity required for the fling to end up at the given destination,
- // taking the provided fling friction value.
- val velocityToReachDestination = distanceToDestination *
- (flingConfig.friction * FLING_FRICTION_SCALAR_MULTIPLIER)
-
- // If there's distance to cover, and the provided velocity is moving in the correct
- // direction, ensure that the velocity is high enough to reach the destination.
- // Otherwise, just use startVelocity - this means that the fling is at or out of bounds.
- // The fling will immediately end and a spring will bring the object back into bounds
- // with this startVelocity.
- flingConfigCopy.startVelocity = when {
- distanceToDestination > 0f && startVelocity >= 0f ->
- max(velocityToReachDestination, startVelocity)
- distanceToDestination < 0f && startVelocity <= 0f ->
- min(velocityToReachDestination, startVelocity)
- else -> startVelocity
- }
-
- springConfigCopy.finalPosition = toAtLeast
- } else {
- flingConfigCopy.startVelocity = startVelocity
- }
-
- flingConfigs[property] = flingConfigCopy
- springConfigs[property] = springConfigCopy
- return this
- }
-
- private fun isValidValue(value: Float) = value < Float.MAX_VALUE && value > -Float.MAX_VALUE
-
- /**
- * Adds a listener that will be called whenever any property on the animated object is updated.
- * This will be called on every animation frame, with the current value of the animated object
- * and the new property values.
- */
- fun addUpdateListener(listener: UpdateListener<T>): PhysicsAnimator<T> {
- updateListeners.add(listener)
- return this
- }
-
- /**
- * Adds a listener that will be called when a property stops animating. This is useful if
- * you care about a specific property ending, or want to use the end value/end velocity from a
- * particular property's animation. If you just want to run an action when all property
- * animations have ended, use [withEndActions].
- */
- fun addEndListener(listener: EndListener<T>): PhysicsAnimator<T> {
- endListeners.add(listener)
- return this
- }
-
- /**
- * Adds end actions that will be run sequentially when animations for every property involved in
- * this specific animation have ended (unless they were explicitly canceled). For example, if
- * you call:
- *
- * animator
- * .spring(TRANSLATION_X, ...)
- * .spring(TRANSLATION_Y, ...)
- * .withEndAction(action)
- * .start()
- *
- * 'action' will be run when both TRANSLATION_X and TRANSLATION_Y end.
- *
- * Other properties may still be animating, if those animations were not started in the same
- * call. For example:
- *
- * animator
- * .spring(ALPHA, ...)
- * .start()
- *
- * animator
- * .spring(TRANSLATION_X, ...)
- * .spring(TRANSLATION_Y, ...)
- * .withEndAction(action)
- * .start()
- *
- * 'action' will still be run as soon as TRANSLATION_X and TRANSLATION_Y end, even if ALPHA is
- * still animating.
- *
- * If you want to run actions as soon as a subset of property animations have ended, you want
- * access to the animation's end value/velocity, or you want to run these actions even if the
- * animation is explicitly canceled, use [addEndListener]. End listeners have an allEnded param,
- * which indicates that all relevant animations have ended.
- */
- fun withEndActions(vararg endActions: EndAction?): PhysicsAnimator<T> {
- this.endActions.addAll(endActions.filterNotNull())
- return this
- }
-
- /**
- * Helper overload so that callers from Java can use Runnables or method references as end
- * actions without having to explicitly return Unit.
- */
- fun withEndActions(vararg endActions: Runnable?): PhysicsAnimator<T> {
- this.endActions.addAll(endActions.filterNotNull().map { it::run })
- return this
- }
-
- fun setDefaultSpringConfig(defaultSpring: SpringConfig) {
- this.defaultSpring = defaultSpring
- }
-
- fun setDefaultFlingConfig(defaultFling: FlingConfig) {
- this.defaultFling = defaultFling
- }
-
- /**
- * Set the custom AnimationHandler for all aniatmion in this animator. Set this with null for
- * restoring to default AnimationHandler.
- */
- fun setCustomAnimationHandler(handler: AnimationHandler) {
- this.customAnimationHandler = handler
- }
-
- /** Starts the animations! */
- fun start() {
- startAction()
- }
-
- /**
- * Starts the animations for real! This is typically called immediately by [start] unless this
- * animator is under test.
- */
- internal fun startInternal() {
- if (!Looper.getMainLooper().isCurrentThread) {
- Log.e(TAG, "Animations can only be started on the main thread. If you are seeing " +
- "this message in a test, call PhysicsAnimatorTestUtils#prepareForTest in " +
- "your test setup.")
- }
- val target = weakTarget.get()
- if (target == null) {
- Log.w(TAG, "Trying to animate a GC-ed object.")
- return
- }
-
- // Functions that will actually start the animations. These are run after we build and add
- // the InternalListener, since some animations might update/end immediately and we don't
- // want to miss those updates.
- val animationStartActions = ArrayList<() -> Unit>()
-
- for (animatedProperty in getAnimatedProperties()) {
- val flingConfig = flingConfigs[animatedProperty]
- val springConfig = springConfigs[animatedProperty]
-
- // The property's current value on the object.
- val currentValue = animatedProperty.getValue(target)
-
- // Start by checking for a fling configuration. If one is present, we're either flinging
- // or flinging-then-springing. Either way, we'll want to start the fling first.
- if (flingConfig != null) {
- animationStartActions.add {
- // When the animation is starting, adjust the min/max bounds to include the
- // current value of the property, if necessary. This is required to allow a
- // fling to bring an out-of-bounds object back into bounds. For example, if an
- // object was dragged halfway off the left side of the screen, but then flung to
- // the right, we don't want the animation to end instantly just because the
- // object started out of bounds. If the fling is in the direction that would
- // take it farther out of bounds, it will end instantly as expected.
- flingConfig.apply {
- min = min(currentValue, this.min)
- max = max(currentValue, this.max)
- }
-
- // Flings can't be updated to a new position while maintaining velocity, because
- // we're using the explicitly provided start velocity. Cancel any flings (or
- // springs) on this property before flinging.
- cancel(animatedProperty)
-
- // Apply the custom animation handler if it not null
- val flingAnim = getFlingAnimation(animatedProperty, target)
- flingAnim.animationHandler =
- customAnimationHandler ?: flingAnim.animationHandler
-
- // Apply the configuration and start the animation.
- flingAnim.also { flingConfig.applyToAnimation(it) }.start()
- }
- }
-
- // Check for a spring configuration. If one is present, we're either springing, or
- // flinging-then-springing.
- if (springConfig != null) {
-
- // If there is no corresponding fling config, we're only springing.
- if (flingConfig == null) {
- // Apply the configuration and start the animation.
- val springAnim = getSpringAnimation(animatedProperty, target)
-
- // If customAnimationHander is exist and has not been set to the animation,
- // it should set here.
- if (customAnimationHandler != null &&
- springAnim.animationHandler != customAnimationHandler) {
- // Cancel the animation before set animation handler
- if (springAnim.isRunning) {
- cancel(animatedProperty)
- }
- // Apply the custom animation handler if it not null
- springAnim.animationHandler =
- customAnimationHandler ?: springAnim.animationHandler
- }
-
- // Apply the configuration and start the animation.
- springConfig.applyToAnimation(springAnim)
- animationStartActions.add(springAnim::start)
- } else {
- // If there's a corresponding fling config, we're flinging-then-springing. Save
- // the fling's original bounds so we can spring to them when the fling ends.
- val flingMin = flingConfig.min
- val flingMax = flingConfig.max
-
- // Add an end listener that will start the spring when the fling ends.
- endListeners.add(0, object : EndListener<T> {
- override fun onAnimationEnd(
- target: T,
- property: FloatPropertyCompat<in T>,
- wasFling: Boolean,
- canceled: Boolean,
- finalValue: Float,
- finalVelocity: Float,
- allRelevantPropertyAnimsEnded: Boolean
- ) {
- // If this isn't the relevant property, it wasn't a fling, or the fling
- // was explicitly cancelled, don't spring.
- if (property != animatedProperty || !wasFling || canceled) {
- return
- }
-
- val endedWithVelocity = abs(finalVelocity) > 0
-
- // If the object was out of bounds when the fling animation started, it
- // will immediately end. In that case, we'll spring it back in bounds.
- val endedOutOfBounds = finalValue !in flingMin..flingMax
-
- // If the fling ended either out of bounds or with remaining velocity,
- // it's time to spring.
- if (endedWithVelocity || endedOutOfBounds) {
- springConfig.startVelocity = finalVelocity
-
- // If the spring's final position isn't set, this is a
- // flingThenSpring where flingMustReachMinOrMax was false. We'll
- // need to set the spring's final position here.
- if (springConfig.finalPosition == UNSET) {
- if (endedWithVelocity) {
- // If the fling ended with negative velocity, that means it
- // hit the min bound, so spring to that bound (and vice
- // versa).
- springConfig.finalPosition =
- if (finalVelocity < 0) flingMin else flingMax
- } else if (endedOutOfBounds) {
- // If the fling ended out of bounds, spring it to the
- // nearest bound.
- springConfig.finalPosition =
- if (finalValue < flingMin) flingMin else flingMax
- }
- }
-
- // Apply the custom animation handler if it not null
- val springAnim = getSpringAnimation(animatedProperty, target)
- springAnim.animationHandler =
- customAnimationHandler ?: springAnim.animationHandler
-
- // Apply the configuration and start the spring animation.
- springAnim.also { springConfig.applyToAnimation(it) }.start()
- }
- }
- })
- }
- }
- }
-
- // Add an internal listener that will dispatch animation events to the provided listeners.
- internalListeners.add(InternalListener(
- target,
- getAnimatedProperties(),
- ArrayList(updateListeners),
- ArrayList(endListeners),
- ArrayList(endActions)))
-
- // Actually start the DynamicAnimations. This is delayed until after the InternalListener is
- // constructed and added so that we don't miss the end listener firing for any animations
- // that immediately end.
- animationStartActions.forEach { it.invoke() }
-
- clearAnimator()
- }
-
- /** Clear the animator's builder variables. */
- private fun clearAnimator() {
- springConfigs.clear()
- flingConfigs.clear()
-
- updateListeners.clear()
- endListeners.clear()
- endActions.clear()
- }
-
- /** Retrieves a spring animation for the given property, building one if needed. */
- private fun getSpringAnimation(
- property: FloatPropertyCompat<in T>,
- target: T
- ): SpringAnimation {
- return springAnimations.getOrPut(
- property,
- { configureDynamicAnimation(SpringAnimation(target, property), property)
- as SpringAnimation })
- }
-
- /** Retrieves a fling animation for the given property, building one if needed. */
- private fun getFlingAnimation(property: FloatPropertyCompat<in T>, target: T): FlingAnimation {
- return flingAnimations.getOrPut(
- property,
- { configureDynamicAnimation(FlingAnimation(target, property), property)
- as FlingAnimation })
- }
-
- /**
- * Adds update and end listeners to the DynamicAnimation which will dispatch to the internal
- * listeners.
- */
- private fun configureDynamicAnimation(
- anim: DynamicAnimation<*>,
- property: FloatPropertyCompat<in T>
- ): DynamicAnimation<*> {
- anim.addUpdateListener { _, value, velocity ->
- for (i in 0 until internalListeners.size) {
- internalListeners[i].onInternalAnimationUpdate(property, value, velocity)
- }
- }
- anim.addEndListener { _, canceled, value, velocity ->
- internalListeners.removeAll {
- it.onInternalAnimationEnd(
- property, canceled, value, velocity, anim is FlingAnimation)
- }
- if (springAnimations[property] == anim) {
- springAnimations.remove(property)
- }
- if (flingAnimations[property] == anim) {
- flingAnimations.remove(property)
- }
- }
- return anim
- }
-
- /**
- * Internal listener class that receives updates from DynamicAnimation listeners, and dispatches
- * them to the appropriate update/end listeners. This class is also aware of which properties
- * were being animated when the end listeners were passed in, so that we can provide the
- * appropriate value for allEnded to [EndListener.onAnimationEnd].
- */
- internal inner class InternalListener constructor(
- private val target: T,
- private var properties: Set<FloatPropertyCompat<in T>>,
- private var updateListeners: List<UpdateListener<T>>,
- private var endListeners: List<EndListener<T>>,
- private var endActions: List<EndAction>
- ) {
-
- /** The number of properties whose animations haven't ended. */
- private var numPropertiesAnimating = properties.size
-
- /**
- * Update values that haven't yet been dispatched because not all property animations have
- * updated yet.
- */
- private val undispatchedUpdates =
- ArrayMap<FloatPropertyCompat<in T>, AnimationUpdate>()
-
- /** Called when a DynamicAnimation updates. */
- internal fun onInternalAnimationUpdate(
- property: FloatPropertyCompat<in T>,
- value: Float,
- velocity: Float
- ) {
-
- // If this property animation isn't relevant to this listener, ignore it.
- if (!properties.contains(property)) {
- return
- }
-
- undispatchedUpdates[property] = AnimationUpdate(value, velocity)
- maybeDispatchUpdates()
- }
-
- /**
- * Called when a DynamicAnimation ends.
- *
- * @return True if this listener should be removed from the list of internal listeners, so
- * it no longer receives updates from DynamicAnimations.
- */
- internal fun onInternalAnimationEnd(
- property: FloatPropertyCompat<in T>,
- canceled: Boolean,
- finalValue: Float,
- finalVelocity: Float,
- isFling: Boolean
- ): Boolean {
-
- // If this property animation isn't relevant to this listener, ignore it.
- if (!properties.contains(property)) {
- return false
- }
-
- // Dispatch updates if we have one for each property.
- numPropertiesAnimating--
- maybeDispatchUpdates()
-
- // If we didn't have an update for each property, dispatch the update for the ending
- // property. This guarantees that an update isn't sent for this property *after* we call
- // onAnimationEnd for that property.
- if (undispatchedUpdates.contains(property)) {
- updateListeners.forEach { updateListener ->
- updateListener.onAnimationUpdateForProperty(
- target,
- UpdateMap<T>().also { it[property] = undispatchedUpdates[property] })
- }
-
- undispatchedUpdates.remove(property)
- }
-
- val allEnded = !arePropertiesAnimating(properties)
- endListeners.forEach {
- it.onAnimationEnd(
- target, property, isFling, canceled, finalValue, finalVelocity,
- allEnded)
-
- // Check that the end listener didn't restart this property's animation.
- if (isPropertyAnimating(property)) {
- return false
- }
- }
-
- // If all of the animations that this listener cares about have ended, run the end
- // actions unless the animation was canceled.
- if (allEnded && !canceled) {
- endActions.forEach { it() }
- }
-
- return allEnded
- }
-
- /**
- * Dispatch undispatched values if we've received an update from each of the animating
- * properties.
- */
- private fun maybeDispatchUpdates() {
- if (undispatchedUpdates.size >= numPropertiesAnimating &&
- undispatchedUpdates.size > 0) {
- updateListeners.forEach {
- it.onAnimationUpdateForProperty(target, ArrayMap(undispatchedUpdates))
- }
-
- undispatchedUpdates.clear()
- }
- }
- }
-
- /** Return true if any animations are running on the object. */
- fun isRunning(): Boolean {
- return arePropertiesAnimating(springAnimations.keys.union(flingAnimations.keys))
- }
-
- /** Returns whether the given property is animating. */
- fun isPropertyAnimating(property: FloatPropertyCompat<in T>): Boolean {
- return springAnimations[property]?.isRunning ?: false ||
- flingAnimations[property]?.isRunning ?: false
- }
-
- /** Returns whether any of the given properties are animating. */
- fun arePropertiesAnimating(properties: Set<FloatPropertyCompat<in T>>): Boolean {
- return properties.any { isPropertyAnimating(it) }
- }
-
- /** Return the set of properties that will begin animating upon calling [start]. */
- internal fun getAnimatedProperties(): Set<FloatPropertyCompat<in T>> {
- return springConfigs.keys.union(flingConfigs.keys)
- }
-
- /**
- * Cancels the given properties. This is typically called immediately by [cancel], unless this
- * animator is under test.
- */
- internal fun cancelInternal(properties: Set<FloatPropertyCompat<in T>>) {
- for (property in properties) {
- flingAnimations[property]?.cancel()
- springAnimations[property]?.cancel()
- }
- }
-
- /** Cancels all in progress animations on all properties. */
- fun cancel() {
- cancelAction(flingAnimations.keys)
- cancelAction(springAnimations.keys)
- }
-
- /** Cancels in progress animations on the provided properties only. */
- fun cancel(vararg properties: FloatPropertyCompat<in T>) {
- cancelAction(properties.toSet())
- }
-
- /**
- * Container object for spring animation configuration settings. This allows you to store
- * default stiffness and damping ratio values in a single configuration object, which you can
- * pass to [spring].
- */
- data class SpringConfig internal constructor(
- internal var stiffness: Float,
- internal var dampingRatio: Float,
- internal var startVelocity: Float = 0f,
- internal var finalPosition: Float = UNSET
- ) {
-
- constructor() :
- this(globalDefaultSpring.stiffness, globalDefaultSpring.dampingRatio)
-
- constructor(stiffness: Float, dampingRatio: Float) :
- this(stiffness = stiffness, dampingRatio = dampingRatio, startVelocity = 0f)
-
- /** Apply these configuration settings to the given SpringAnimation. */
- internal fun applyToAnimation(anim: SpringAnimation) {
- val springForce = anim.spring ?: SpringForce()
- anim.spring = springForce.apply {
- stiffness = this@SpringConfig.stiffness
- dampingRatio = this@SpringConfig.dampingRatio
- finalPosition = this@SpringConfig.finalPosition
- }
-
- if (startVelocity != 0f) anim.setStartVelocity(startVelocity)
- }
- }
-
- /**
- * Container object for fling animation configuration settings. This allows you to store default
- * friction values (as well as optional min/max values) in a single configuration object, which
- * you can pass to [fling] and related methods.
- */
- data class FlingConfig internal constructor(
- internal var friction: Float,
- internal var min: Float,
- internal var max: Float,
- internal var startVelocity: Float
- ) {
-
- constructor() : this(globalDefaultFling.friction)
-
- constructor(friction: Float) :
- this(friction, globalDefaultFling.min, globalDefaultFling.max)
-
- constructor(friction: Float, min: Float, max: Float) :
- this(friction, min, max, startVelocity = 0f)
-
- /** Apply these configuration settings to the given FlingAnimation. */
- internal fun applyToAnimation(anim: FlingAnimation) {
- anim.apply {
- friction = this@FlingConfig.friction
- setMinValue(min)
- setMaxValue(max)
- setStartVelocity(startVelocity)
- }
- }
- }
-
- /**
- * Listener for receiving values from in progress animations. Used with
- * [PhysicsAnimator.addUpdateListener].
- *
- * @param <T> The type of the object being animated.
- </T> */
- interface UpdateListener<T> {
-
- /**
- * Called on each animation frame with the target object, and a map of FloatPropertyCompat
- * -> AnimationUpdate, containing the latest value and velocity for that property. When
- * multiple properties are animating together, the map will typically contain one entry for
- * each property. However, you should never assume that this is the case - when a property
- * animation ends earlier than the others, you'll receive an UpdateMap containing only that
- * property's final update. Subsequently, you'll only receive updates for the properties
- * that are still animating.
- *
- * Always check that the map contains an update for the property you're interested in before
- * accessing it.
- *
- * @param target The animated object itself.
- * @param values Map of property to AnimationUpdate, which contains that property
- * animation's latest value and velocity. You should never assume that a particular property
- * is present in this map.
- */
- fun onAnimationUpdateForProperty(
- target: T,
- values: UpdateMap<T>
- )
- }
-
- /**
- * Listener for receiving callbacks when animations end.
- *
- * @param <T> The type of the object being animated.
- </T> */
- interface EndListener<T> {
-
- /**
- * Called with the final animation values as each property animation ends. This can be used
- * to respond to specific property animations concluding (such as hiding a view when ALPHA
- * ends, even if the corresponding TRANSLATION animations have not ended).
- *
- * If you just want to run an action when all of the property animations have ended, you can
- * use [PhysicsAnimator.withEndActions].
- *
- * @param target The animated object itself.
- * @param property The property whose animation has just ended.
- * @param wasFling Whether this property ended after a fling animation (as opposed to a
- * spring animation). If this property was animated via [flingThenSpring], this will be true
- * if the fling animation did not reach the min/max bounds, decelerating to a stop
- * naturally. It will be false if it hit the bounds and was sprung back.
- * @param canceled Whether the animation was explicitly canceled before it naturally ended.
- * @param finalValue The final value of the animated property.
- * @param finalVelocity The final velocity (in pixels per second) of the ended animation.
- * This is typically zero, unless this was a fling animation which ended abruptly due to
- * reaching its configured min/max values.
- * @param allRelevantPropertyAnimsEnded Whether all properties relevant to this end listener
- * have ended. Relevant properties are those which were animated alongside the
- * [addEndListener] call where this animator was passed in. For example:
- *
- * animator
- * .spring(TRANSLATION_X, 100f)
- * .spring(TRANSLATION_Y, 200f)
- * .withEndListener(firstEndListener)
- * .start()
- *
- * firstEndListener will be called first for TRANSLATION_X, with allEnded = false,
- * because TRANSLATION_Y is still running. When TRANSLATION_Y ends, it'll be called with
- * allEnded = true.
- *
- * If a subsequent call to start() is made with other properties, those properties are not
- * considered relevant and allEnded will still equal true when only TRANSLATION_X and
- * TRANSLATION_Y end. For example, if immediately after the prior example, while
- * TRANSLATION_X and TRANSLATION_Y are still animating, we called:
- *
- * animator.
- * .spring(SCALE_X, 2f, stiffness = 10f) // That will take awhile...
- * .withEndListener(secondEndListener)
- * .start()
- *
- * firstEndListener will still be called with allEnded = true when TRANSLATION_X/Y end, even
- * though SCALE_X is still animating. Similarly, secondEndListener will be called with
- * allEnded = true as soon as SCALE_X ends, even if the translation animations are still
- * running.
- */
- fun onAnimationEnd(
- target: T,
- property: FloatPropertyCompat<in T>,
- wasFling: Boolean,
- canceled: Boolean,
- finalValue: Float,
- finalVelocity: Float,
- allRelevantPropertyAnimsEnded: Boolean
- )
- }
-
- companion object {
-
- /**
- * Constructor to use to for new physics animator instances in [getInstance]. This is
- * typically the default constructor, but [PhysicsAnimatorTestUtils] can change it so that
- * all code using the physics animator is given testable instances instead.
- */
- internal var instanceConstructor: (Any) -> PhysicsAnimator<*> = ::PhysicsAnimator
-
- @JvmStatic
- @Suppress("UNCHECKED_CAST")
- fun <T : Any> getInstance(target: T): PhysicsAnimator<T> {
- if (!animators.containsKey(target)) {
- animators[target] = instanceConstructor(target)
- }
-
- return animators[target] as PhysicsAnimator<T>
- }
-
- /**
- * Set whether all physics animators should log a lot of information about animations.
- * Useful for debugging!
- */
- @JvmStatic
- fun setVerboseLogging(debug: Boolean) {
- verboseLogging = debug
- }
-
- /**
- * Estimates the end value of a fling that starts at the given value using the provided
- * start velocity and fling configuration.
- *
- * This is only an estimate. Fling animations use a timing-based physics simulation that is
- * non-deterministic, so this exact value may not be reached.
- */
- @JvmStatic
- fun estimateFlingEndValue(
- startValue: Float,
- startVelocity: Float,
- flingConfig: FlingConfig
- ): Float {
- val distance = startVelocity / (flingConfig.friction * FLING_FRICTION_SCALAR_MULTIPLIER)
- return Math.min(flingConfig.max, Math.max(flingConfig.min, startValue + distance))
- }
-
- @JvmStatic
- fun getReadablePropertyName(property: FloatPropertyCompat<*>): String {
- return when (property) {
- DynamicAnimation.TRANSLATION_X -> "translationX"
- DynamicAnimation.TRANSLATION_Y -> "translationY"
- DynamicAnimation.TRANSLATION_Z -> "translationZ"
- DynamicAnimation.SCALE_X -> "scaleX"
- DynamicAnimation.SCALE_Y -> "scaleY"
- DynamicAnimation.ROTATION -> "rotation"
- DynamicAnimation.ROTATION_X -> "rotationX"
- DynamicAnimation.ROTATION_Y -> "rotationY"
- DynamicAnimation.SCROLL_X -> "scrollX"
- DynamicAnimation.SCROLL_Y -> "scrollY"
- DynamicAnimation.ALPHA -> "alpha"
- else -> "Custom FloatPropertyCompat instance"
- }
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt
deleted file mode 100644
index c50eeac80d7a..000000000000
--- a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt
+++ /dev/null
@@ -1,473 +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.util.animation
-
-import android.os.Handler
-import android.os.Looper
-import android.util.ArrayMap
-import androidx.dynamicanimation.animation.FloatPropertyCompat
-import com.android.systemui.util.animation.PhysicsAnimatorTestUtils.prepareForTest
-import java.util.ArrayDeque
-import java.util.concurrent.CountDownLatch
-import java.util.concurrent.TimeUnit
-
-typealias UpdateMatcher = (PhysicsAnimator.AnimationUpdate) -> Boolean
-typealias UpdateFramesPerProperty<T> =
- ArrayMap<FloatPropertyCompat<in T>, ArrayList<PhysicsAnimator.AnimationUpdate>>
-
-/**
- * Utilities for testing code that uses [PhysicsAnimator].
- *
- * Start by calling [prepareForTest] at the beginning of each test - this will modify the behavior
- * of all PhysicsAnimator instances so that they post animations to the main thread (so they don't
- * crash). It'll also enable the use of the other static helper methods in this class, which you can
- * use to do things like block the test until animations complete (so you can test end states), or
- * verify keyframes.
- */
-object PhysicsAnimatorTestUtils {
- var timeoutMs: Long = 2000
- private var startBlocksUntilAnimationsEnd = false
- private val animationThreadHandler = Handler(Looper.getMainLooper())
- private val allAnimatedObjects = HashSet<Any>()
- private val animatorTestHelpers = HashMap<PhysicsAnimator<*>, AnimatorTestHelper<*>>()
-
- /**
- * Modifies the behavior of all [PhysicsAnimator] instances so that they post animations to the
- * main thread, and report all of their
- */
- @JvmStatic
- fun prepareForTest() {
- val defaultConstructor = PhysicsAnimator.instanceConstructor
- PhysicsAnimator.instanceConstructor = fun(target: Any): PhysicsAnimator<*> {
- val animator = defaultConstructor(target)
- allAnimatedObjects.add(target)
- animatorTestHelpers[animator] = AnimatorTestHelper(animator)
- return animator
- }
-
- timeoutMs = 2000
- startBlocksUntilAnimationsEnd = false
- allAnimatedObjects.clear()
- }
-
- @JvmStatic
- fun tearDown() {
- val latch = CountDownLatch(1)
- animationThreadHandler.post {
- animatorTestHelpers.keys.forEach { it.cancel() }
- latch.countDown()
- }
-
- latch.await()
-
- animatorTestHelpers.clear()
- animators.clear()
- allAnimatedObjects.clear()
- }
-
- /**
- * Sets the maximum time (in milliseconds) to block the test thread while waiting for animations
- * before throwing an exception.
- */
- @JvmStatic
- fun setBlockTimeout(timeoutMs: Long) {
- this.timeoutMs = timeoutMs
- }
-
- /**
- * Sets whether all animations should block the test thread until they end. This is typically
- * the desired behavior, since you can invoke code that runs an animation and then assert things
- * about its end state.
- */
- @JvmStatic
- fun setAllAnimationsBlock(block: Boolean) {
- startBlocksUntilAnimationsEnd = block
- }
-
- /**
- * Blocks the calling thread until animations of the given property on the target object end.
- */
- @JvmStatic
- @Throws(InterruptedException::class)
- fun <T : Any> blockUntilAnimationsEnd(
- animator: PhysicsAnimator<T>,
- vararg properties: FloatPropertyCompat<in T>
- ) {
- val animatingProperties = HashSet<FloatPropertyCompat<in T>>()
- for (property in properties) {
- if (animator.isPropertyAnimating(property)) {
- animatingProperties.add(property)
- }
- }
-
- if (animatingProperties.size > 0) {
- val latch = CountDownLatch(animatingProperties.size)
- getAnimationTestHelper(animator).addTestEndListener(
- object : PhysicsAnimator.EndListener<T> {
- override fun onAnimationEnd(
- target: T,
- property: FloatPropertyCompat<in T>,
- wasFling: Boolean,
- canceled: Boolean,
- finalValue: Float,
- finalVelocity: Float,
- allRelevantPropertyAnimsEnded: Boolean
- ) {
- if (animatingProperties.contains(property)) {
- latch.countDown()
- }
- }
- })
-
- latch.await(timeoutMs, TimeUnit.MILLISECONDS)
- }
- }
-
- /**
- * Blocks the calling thread until all animations of the given property (on all target objects)
- * have ended. Useful when you don't have access to the objects being animated, but still need
- * to wait for them to end so that other testable side effects occur (such as update/end
- * listeners).
- */
- @JvmStatic
- @Throws(InterruptedException::class)
- @Suppress("UNCHECKED_CAST")
- fun <T : Any> blockUntilAnimationsEnd(
- properties: FloatPropertyCompat<in T>
- ) {
- for (target in allAnimatedObjects) {
- try {
- blockUntilAnimationsEnd(
- PhysicsAnimator.getInstance(target) as PhysicsAnimator<T>, properties)
- } catch (e: ClassCastException) {
- // Keep checking the other objects for ones whose types match the provided
- // properties.
- }
- }
- }
-
- /**
- * Blocks the calling thread until the first animation frame in which predicate returns true. If
- * the given object isn't animating, returns without blocking.
- */
- @JvmStatic
- @Throws(InterruptedException::class)
- fun <T : Any> blockUntilFirstAnimationFrameWhereTrue(
- animator: PhysicsAnimator<T>,
- predicate: (T) -> Boolean
- ) {
- if (animator.isRunning()) {
- val latch = CountDownLatch(1)
- getAnimationTestHelper(animator).addTestUpdateListener(object : PhysicsAnimator
- .UpdateListener<T> {
- override fun onAnimationUpdateForProperty(
- target: T,
- values: UpdateMap<T>
- ) {
- if (predicate(target)) {
- latch.countDown()
- }
- }
- })
-
- latch.await(timeoutMs, TimeUnit.MILLISECONDS)
- }
- }
-
- /**
- * Verifies that the animator reported animation frame values to update listeners that satisfy
- * the given matchers, in order. Not all frames need to satisfy a matcher - we'll run through
- * all animation frames, and check them against the current predicate. If it returns false, we
- * continue through the frames until it returns true, and then move on to the next matcher.
- * Verification fails if we run out of frames while unsatisfied matchers remain.
- *
- * If verification is successful, all frames to this point are considered 'verified' and will be
- * cleared. Subsequent calls to this method will start verification at the next animation frame.
- *
- * Example: Verify that an animation surpassed x = 50f before going negative.
- * verifyAnimationUpdateFrames(
- * animator, TRANSLATION_X,
- * { u -> u.value > 50f },
- * { u -> u.value < 0f })
- *
- * Example: verify that an animation went backwards at some point while still being on-screen.
- * verifyAnimationUpdateFrames(
- * animator, TRANSLATION_X,
- * { u -> u.velocity < 0f && u.value >= 0f })
- *
- * This method is intended to help you test longer, more complicated animations where it's
- * critical that certain values were reached. Using this method to test short animations can
- * fail due to the animation having fewer frames than provided matchers. For example, an
- * animation from x = 1f to x = 5f might only have two frames, at x = 3f and x = 5f. The
- * following would then fail despite it seeming logically sound:
- *
- * verifyAnimationUpdateFrames(
- * animator, TRANSLATION_X,
- * { u -> u.value > 1f },
- * { u -> u.value > 2f },
- * { u -> u.value > 3f })
- *
- * Tests might also fail if your matchers are too granular, such as this example test after an
- * animation from x = 0f to x = 100f. It's unlikely there was a frame specifically between 2f
- * and 3f.
- *
- * verifyAnimationUpdateFrames(
- * animator, TRANSLATION_X,
- * { u -> u.value > 2f && u.value < 3f },
- * { u -> u.value >= 50f })
- *
- * Failures will print a helpful log of all animation frames so you can see what caused the test
- * to fail.
- */
- fun <T : Any> verifyAnimationUpdateFrames(
- animator: PhysicsAnimator<T>,
- property: FloatPropertyCompat<in T>,
- firstUpdateMatcher: UpdateMatcher,
- vararg additionalUpdateMatchers: UpdateMatcher
- ) {
- val updateFrames: UpdateFramesPerProperty<T> = getAnimationUpdateFrames(animator)
-
- if (!updateFrames.containsKey(property)) {
- error("No frames for given target object and property.")
- }
-
- // Copy the frames to avoid a ConcurrentModificationException if the animation update
- // listeners attempt to add a new frame while we're verifying these.
- val framesForProperty = ArrayList(updateFrames[property]!!)
- val matchers = ArrayDeque<UpdateMatcher>(
- additionalUpdateMatchers.toList())
- val frameTraceMessage = StringBuilder()
-
- var curMatcher = firstUpdateMatcher
-
- // Loop through the updates from the testable animator.
- for (update in framesForProperty) {
-
- // Check whether this frame satisfies the current matcher.
- if (curMatcher(update)) {
-
- // If that was the last unsatisfied matcher, we're good here. 'Verify' all remaining
- // frames and return without failing.
- if (matchers.size == 0) {
- getAnimationUpdateFrames(animator).remove(property)
- return
- }
-
- frameTraceMessage.append("$update\t(satisfied matcher)\n")
- curMatcher = matchers.pop() // Get the next matcher and keep going.
- } else {
- frameTraceMessage.append("${update}\n")
- }
- }
-
- val readablePropertyName = PhysicsAnimator.getReadablePropertyName(property)
- getAnimationUpdateFrames(animator).remove(property)
-
- throw RuntimeException(
- "Failed to verify animation frames for property $readablePropertyName: " +
- "Provided ${additionalUpdateMatchers.size + 1} matchers, " +
- "however ${matchers.size + 1} remained unsatisfied.\n\n" +
- "All frames:\n$frameTraceMessage")
- }
-
- /**
- * Overload of [verifyAnimationUpdateFrames] that builds matchers for you, from given float
- * values. For example, to verify that an animations passed from 0f to 50f to 100f back to 50f:
- *
- * verifyAnimationUpdateFrames(animator, TRANSLATION_X, 0f, 50f, 100f, 50f)
- *
- * This verifies that update frames were received with values of >= 0f, >= 50f, >= 100f, and
- * <= 50f.
- *
- * The same caveats apply: short animations might not have enough frames to satisfy all of the
- * matchers, and overly specific calls (such as 0f, 1f, 2f, 3f, etc. for an animation from
- * x = 0f to x = 100f) might fail as the animation only had frames at 0f, 25f, 50f, 75f, and
- * 100f. As with [verifyAnimationUpdateFrames], failures will print a helpful log of all frames
- * so you can see what caused the test to fail.
- */
- fun <T : Any> verifyAnimationUpdateFrames(
- animator: PhysicsAnimator<T>,
- property: FloatPropertyCompat<in T>,
- startValue: Float,
- firstTargetValue: Float,
- vararg additionalTargetValues: Float
- ) {
- val matchers = ArrayList<UpdateMatcher>()
-
- val values = ArrayList<Float>().also {
- it.add(firstTargetValue)
- it.addAll(additionalTargetValues.toList())
- }
-
- var prevVal = startValue
- for (value in values) {
- if (value > prevVal) {
- matchers.add { update -> update.value >= value }
- } else {
- matchers.add { update -> update.value <= value }
- }
-
- prevVal = value
- }
-
- verifyAnimationUpdateFrames(
- animator, property, matchers[0], *matchers.drop(0).toTypedArray())
- }
-
- /**
- * Returns all of the values that have ever been reported to update listeners, per property.
- */
- @Suppress("UNCHECKED_CAST")
- fun <T : Any> getAnimationUpdateFrames(animator: PhysicsAnimator<T>):
- UpdateFramesPerProperty<T> {
- return animatorTestHelpers[animator]?.getUpdates() as UpdateFramesPerProperty<T>
- }
-
- /**
- * Clears animation frame updates from the given animator so they aren't used the next time its
- * passed to [verifyAnimationUpdateFrames].
- */
- fun <T : Any> clearAnimationUpdateFrames(animator: PhysicsAnimator<T>) {
- animatorTestHelpers[animator]?.clearUpdates()
- }
-
- @Suppress("UNCHECKED_CAST")
- private fun <T> getAnimationTestHelper(animator: PhysicsAnimator<T>): AnimatorTestHelper<T> {
- return animatorTestHelpers[animator] as AnimatorTestHelper<T>
- }
-
- /**
- * Helper class for testing an animator. This replaces the animator's start action with
- * [startForTest] and adds test listeners to enable other test utility behaviors. We build one
- * these for each Animator and keep them around so we can access the updates.
- */
- class AnimatorTestHelper<T> (private val animator: PhysicsAnimator<T>) {
-
- /** All updates received for each property animation. */
- private val allUpdates =
- ArrayMap<FloatPropertyCompat<in T>, ArrayList<PhysicsAnimator.AnimationUpdate>>()
-
- private val testEndListeners = ArrayList<PhysicsAnimator.EndListener<T>>()
- private val testUpdateListeners = ArrayList<PhysicsAnimator.UpdateListener<T>>()
-
- /** Whether we're currently in the middle of executing startInternal(). */
- private var currentlyRunningStartInternal = false
-
- init {
- animator.startAction = ::startForTest
- animator.cancelAction = ::cancelForTest
- }
-
- internal fun addTestEndListener(listener: PhysicsAnimator.EndListener<T>) {
- testEndListeners.add(listener)
- }
-
- internal fun addTestUpdateListener(listener: PhysicsAnimator.UpdateListener<T>) {
- testUpdateListeners.add(listener)
- }
-
- internal fun getUpdates(): UpdateFramesPerProperty<T> {
- return allUpdates
- }
-
- internal fun clearUpdates() {
- allUpdates.clear()
- }
-
- private fun startForTest() {
- // The testable animator needs to block the main thread until super.start() has been
- // called, since callers expect .start() to be synchronous but we're posting it to a
- // handler here. We may also continue blocking until all animations end, if
- // startBlocksUntilAnimationsEnd = true.
- val unblockLatch = CountDownLatch(if (startBlocksUntilAnimationsEnd) 2 else 1)
-
- animationThreadHandler.post {
- // Add an update listener that dispatches to any test update listeners added by
- // tests.
- animator.addUpdateListener(object : PhysicsAnimator.UpdateListener<T> {
- override fun onAnimationUpdateForProperty(
- target: T,
- values: ArrayMap<FloatPropertyCompat<in T>, PhysicsAnimator.AnimationUpdate>
- ) {
- values.forEach { (property, value) ->
- allUpdates.getOrPut(property, { ArrayList() }).add(value)
- }
-
- for (listener in testUpdateListeners) {
- listener.onAnimationUpdateForProperty(target, values)
- }
- }
- })
-
- // Add an end listener that dispatches to any test end listeners added by tests, and
- // unblocks the main thread if required.
- animator.addEndListener(object : PhysicsAnimator.EndListener<T> {
- override fun onAnimationEnd(
- target: T,
- property: FloatPropertyCompat<in T>,
- wasFling: Boolean,
- canceled: Boolean,
- finalValue: Float,
- finalVelocity: Float,
- allRelevantPropertyAnimsEnded: Boolean
- ) {
- for (listener in testEndListeners) {
- listener.onAnimationEnd(
- target, property, wasFling, canceled, finalValue, finalVelocity,
- allRelevantPropertyAnimsEnded)
- }
-
- if (allRelevantPropertyAnimsEnded) {
- testEndListeners.clear()
- testUpdateListeners.clear()
-
- if (startBlocksUntilAnimationsEnd) {
- unblockLatch.countDown()
- }
- }
- }
- })
-
- currentlyRunningStartInternal = true
- animator.startInternal()
- currentlyRunningStartInternal = false
- unblockLatch.countDown()
- }
-
- unblockLatch.await(timeoutMs, TimeUnit.MILLISECONDS)
- }
-
- private fun cancelForTest(properties: Set<FloatPropertyCompat<in T>>) {
- // If this was called from startInternal, we are already on the animation thread, and
- // should just call cancelInternal rather than posting it. If we post it, the
- // cancellation will occur after the rest of startInternal() and we'll immediately
- // cancel the animation we worked so hard to start!
- if (currentlyRunningStartInternal) {
- animator.cancelInternal(properties)
- return
- }
-
- val unblockLatch = CountDownLatch(1)
-
- animationThreadHandler.post {
- animator.cancelInternal(properties)
- unblockLatch.countDown()
- }
-
- unblockLatch.await(timeoutMs, TimeUnit.MILLISECONDS)
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt
index 3347cf6ca2a4..603d423143ce 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt
@@ -46,6 +46,7 @@ class TransitionLayout @JvmOverloads constructor(
private var measureAsConstraint: Boolean = false
private var currentState: TransitionViewState = TransitionViewState()
private var updateScheduled = false
+ private var isPreDrawApplicatorRegistered = false
private var desiredMeasureWidth = 0
private var desiredMeasureHeight = 0
@@ -74,6 +75,7 @@ class TransitionLayout @JvmOverloads constructor(
override fun onPreDraw(): Boolean {
updateScheduled = false
viewTreeObserver.removeOnPreDrawListener(this)
+ isPreDrawApplicatorRegistered = false
applyCurrentState()
return true
}
@@ -94,6 +96,14 @@ class TransitionLayout @JvmOverloads constructor(
}
}
+ override fun onDetachedFromWindow() {
+ super.onDetachedFromWindow()
+ if (isPreDrawApplicatorRegistered) {
+ viewTreeObserver.removeOnPreDrawListener(preDrawApplicator)
+ isPreDrawApplicatorRegistered = false
+ }
+ }
+
/**
* Apply the current state to the view and its widgets
*/
@@ -158,7 +168,10 @@ class TransitionLayout @JvmOverloads constructor(
private fun applyCurrentStateOnPredraw() {
if (!updateScheduled) {
updateScheduled = true
- viewTreeObserver.addOnPreDrawListener(preDrawApplicator)
+ if (!isPreDrawApplicatorRegistered) {
+ viewTreeObserver.addOnPreDrawListener(preDrawApplicator)
+ isPreDrawApplicatorRegistered = true
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt b/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt
deleted file mode 100644
index f441049feefb..000000000000
--- a/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt
+++ /dev/null
@@ -1,699 +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.util.magnetictarget
-
-import android.annotation.SuppressLint
-import android.content.Context
-import android.database.ContentObserver
-import android.graphics.PointF
-import android.os.Handler
-import android.os.UserHandle
-import android.os.VibrationEffect
-import android.os.Vibrator
-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
-import com.android.systemui.util.animation.PhysicsAnimator
-import kotlin.math.abs
-import kotlin.math.hypot
-
-/**
- * Utility class for creating 'magnetized' objects that are attracted to one or more magnetic
- * targets. Magnetic targets attract objects that are dragged near them, and hold them there unless
- * they're moved away or released. Releasing objects inside a magnetic target typically performs an
- * action on the object.
- *
- * MagnetizedObject also supports flinging to targets, which will result in the object being pulled
- * into the target and released as if it was dragged into it.
- *
- * To use this class, either construct an instance with an object of arbitrary type, or use the
- * [MagnetizedObject.magnetizeView] shortcut method if you're magnetizing a view. Then, set
- * [magnetListener] to receive event callbacks. In your touch handler, pass all MotionEvents
- * that move this object to [maybeConsumeMotionEvent]. If that method returns true, consider the
- * event consumed by the MagnetizedObject and don't move the object unless it begins returning false
- * again.
- *
- * @param context Context, used to retrieve a Vibrator instance for vibration effects.
- * @param underlyingObject The actual object that we're magnetizing.
- * @param xProperty Property that sets the x value of the object's position.
- * @param yProperty Property that sets the y value of the object's position.
- */
-abstract class MagnetizedObject<T : Any>(
- val context: Context,
-
- /** The actual object that is animated. */
- val underlyingObject: T,
-
- /** Property that gets/sets the object's X value. */
- val xProperty: FloatPropertyCompat<in T>,
-
- /** Property that gets/sets the object's Y value. */
- val yProperty: FloatPropertyCompat<in T>
-) {
-
- /** Return the width of the object. */
- abstract fun getWidth(underlyingObject: T): Float
-
- /** Return the height of the object. */
- abstract fun getHeight(underlyingObject: T): Float
-
- /**
- * Fill the provided array with the location of the top-left of the object, relative to the
- * entire screen. Compare to [View.getLocationOnScreen].
- */
- abstract fun getLocationOnScreen(underlyingObject: T, loc: IntArray)
-
- /** Methods for listening to events involving a magnetized object. */
- interface MagnetListener {
-
- /**
- * Called when touch events move within the magnetic field of a target, causing the
- * object to animate to the target and become 'stuck' there. The animation happens
- * automatically here - you should not move the object. You can, however, change its state
- * to indicate to the user that it's inside the target and releasing it will have an effect.
- *
- * [maybeConsumeMotionEvent] is now returning true and will continue to do so until a call
- * to [onUnstuckFromTarget] or [onReleasedInTarget].
- *
- * @param target The target that the object is now stuck to.
- */
- fun onStuckToTarget(target: MagneticTarget)
-
- /**
- * Called when the object is no longer stuck to a target. This means that either touch
- * events moved outside of the magnetic field radius, or that a forceful fling out of the
- * target was detected.
- *
- * The object won't be automatically animated out of the target, since you're responsible
- * for moving the object again. You should move it (or animate it) using your own
- * movement/animation logic.
- *
- * Reverse any effects applied in [onStuckToTarget] here.
- *
- * If [wasFlungOut] is true, [maybeConsumeMotionEvent] returned true for the ACTION_UP event
- * that concluded the fling. If [wasFlungOut] is false, that means a drag gesture is ongoing
- * and [maybeConsumeMotionEvent] is now returning false.
- *
- * @param target The target that this object was just unstuck from.
- * @param velX The X velocity of the touch gesture when it exited the magnetic field.
- * @param velY The Y velocity of the touch gesture when it exited the magnetic field.
- * @param wasFlungOut Whether the object was unstuck via a fling gesture. This means that
- * an ACTION_UP event was received, and that the gesture velocity was sufficient to conclude
- * that the user wants to un-stick the object despite no touch events occurring outside of
- * the magnetic field radius.
- */
- fun onUnstuckFromTarget(
- target: MagneticTarget,
- velX: Float,
- velY: Float,
- wasFlungOut: Boolean
- )
-
- /**
- * Called when the object is released inside a target, or flung towards it with enough
- * velocity to reach it.
- *
- * @param target The target that the object was released in.
- */
- fun onReleasedInTarget(target: MagneticTarget)
- }
-
- private val animator: PhysicsAnimator<T> = PhysicsAnimator.getInstance(underlyingObject)
- private val objectLocationOnScreen = IntArray(2)
-
- /**
- * Targets that have been added to this object. These will all be considered when determining
- * magnetic fields and fling trajectories.
- */
- private val associatedTargets = ArrayList<MagneticTarget>()
-
- 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
-
- /** The target the object is stuck to, or null if the object is not stuck to any target. */
- private var targetObjectIsStuckTo: MagneticTarget? = null
-
- /**
- * Sets the listener to receive events. This must be set, or [maybeConsumeMotionEvent]
- * will always return false and no magnetic effects will occur.
- */
- lateinit var magnetListener: MagnetizedObject.MagnetListener
-
- /**
- * Optional update listener to provide to the PhysicsAnimator that is used to spring the object
- * into the target.
- */
- var physicsAnimatorUpdateListener: PhysicsAnimator.UpdateListener<T>? = null
-
- /**
- * Optional end listener to provide to the PhysicsAnimator that is used to spring the object
- * into the target.
- */
- var physicsAnimatorEndListener: PhysicsAnimator.EndListener<T>? = null
-
- /**
- * Method that is called when the object should be animated stuck to the target. The default
- * implementation uses the object's x and y properties to animate the object centered inside the
- * target. You can override this if you need custom animation.
- *
- * The method is invoked with the MagneticTarget that the object is sticking to, the X and Y
- * velocities of the gesture that brought the object into the magnetic radius, whether or not it
- * was flung, and a callback you must call after your animation completes.
- */
- var animateStuckToTarget: (MagneticTarget, Float, Float, Boolean, (() -> Unit)?) -> Unit =
- ::animateStuckToTargetInternal
-
- /**
- * Sets whether forcefully flinging the object vertically towards a target causes it to be
- * attracted to the target and then released immediately, despite never being dragged within the
- * magnetic field.
- */
- var flingToTargetEnabled = true
-
- /**
- * If fling to target is enabled, forcefully flinging the object towards a target will cause
- * it to be attracted to the target and then released immediately, despite never being dragged
- * within the magnetic field.
- *
- * This sets the width of the area considered 'near' enough a target to be considered a fling,
- * in terms of percent of the target view's width. For example, setting this to 3f means that
- * flings towards a 100px-wide target will be considered 'near' enough if they're towards the
- * 300px-wide area around the target.
- *
- * Flings whose trajectory intersects the area will be attracted and released - even if the
- * target view itself isn't intersected:
- *
- * | |
- * | 0 |
- * | / |
- * | / |
- * | X / |
- * |.....###.....|
- *
- *
- * Flings towards the target whose trajectories do not intersect the area will be treated as
- * normal flings and the magnet will leave the object alone:
- *
- * | |
- * | |
- * | 0 |
- * | / |
- * | / X |
- * |.....###.....|
- *
- */
- var flingToTargetWidthPercent = 3f
-
- /**
- * Sets the minimum velocity (in pixels per second) required to fling an object to the target
- * without dragging it into the magnetic field.
- */
- var flingToTargetMinVelocity = 4000f
-
- /**
- * Sets the minimum velocity (in pixels per second) required to fling un-stuck an object stuck
- * to the target. If this velocity is reached, the object will be freed even if it wasn't moved
- * outside the magnetic field radius.
- */
- var flingUnstuckFromTargetMinVelocity = 4000f
-
- /**
- * Sets the maximum X velocity above which the object will not stick to the target. Even if the
- * object is dragged through the magnetic field, it will not stick to the target until the
- * horizontal velocity is below this value.
- */
- var stickToTargetMaxXVelocity = 2000f
-
- /**
- * Enable or disable haptic vibration effects when the object interacts with the magnetic field.
- *
- * If you're experiencing crashes when the object enters targets, ensure that you have the
- * android.permission.VIBRATE permission!
- */
- var hapticsEnabled = true
-
- /** Default spring configuration to use for animating the object into a target. */
- var springConfig = PhysicsAnimator.SpringConfig(
- SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_NO_BOUNCY)
-
- /**
- * Spring configuration to use to spring the object into a target specifically when it's flung
- * towards (rather than dragged near) it.
- */
- var flungIntoTargetSpringConfig = springConfig
-
- init {
- initHapticSettingObserver(context)
- }
-
- /**
- * Adds the provided MagneticTarget to this object. The object will now be attracted to the
- * target if it strays within its magnetic field or is flung towards it.
- *
- * If this target (or its magnetic field) overlaps another target added to this object, the
- * prior target will take priority.
- */
- fun addTarget(target: MagneticTarget) {
- associatedTargets.add(target)
- target.updateLocationOnScreen()
- }
-
- /**
- * Shortcut that accepts a View and a magnetic field radius and adds it as a magnetic target.
- *
- * @return The MagneticTarget instance for the given View. This can be used to change the
- * target's magnetic field radius after it's been added. It can also be added to other
- * magnetized objects.
- */
- fun addTarget(target: View, magneticFieldRadiusPx: Int): MagneticTarget {
- return MagneticTarget(target, magneticFieldRadiusPx).also { addTarget(it) }
- }
-
- /**
- * Removes the given target from this object. The target will no longer attract the object.
- */
- fun removeTarget(target: MagneticTarget) {
- associatedTargets.remove(target)
- }
-
- /**
- * Provide this method with all motion events that move the magnetized object. If the
- * location of the motion events moves within the magnetic field of a target, or indicate a
- * fling-to-target gesture, this method will return true and you should not move the object
- * yourself until it returns false again.
- *
- * Note that even when this method returns true, you should continue to pass along new motion
- * events so that we know when the events move back outside the magnetic field area.
- *
- * This method will always return false if you haven't set a [magnetListener].
- */
- fun maybeConsumeMotionEvent(ev: MotionEvent): Boolean {
- // Short-circuit if we don't have a listener or any targets, since those are required.
- if (associatedTargets.size == 0) {
- return false
- }
-
- // 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) {
- updateTargetViews()
-
- // 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,
- ev.rawY - target.centerOnScreen.y)
- distanceFromTargetCenter < target.magneticFieldRadiusPx
- }
-
- // If we aren't currently stuck to a target, and we're in the magnetic field of a target,
- // we're newly stuck.
- val objectNewlyStuckToTarget =
- !objectStuckToTarget && targetObjectIsInMagneticFieldOf != null
-
- // If we are currently stuck to a target, we're in the magnetic field of a target, and that
- // target isn't the one we're currently stuck to, then touch events have moved into a
- // adjacent target's magnetic field.
- val objectMovedIntoDifferentTarget =
- objectStuckToTarget &&
- targetObjectIsInMagneticFieldOf != null &&
- targetObjectIsStuckTo != targetObjectIsInMagneticFieldOf
-
- if (objectNewlyStuckToTarget || objectMovedIntoDifferentTarget) {
- velocityTracker.computeCurrentVelocity(1000)
- val velX = velocityTracker.xVelocity
- val velY = velocityTracker.yVelocity
-
- // If the object is moving too quickly within the magnetic field, do not stick it. This
- // only applies to objects newly stuck to a target. If the object is moved into a new
- // target, it wasn't moving at all (since it was stuck to the previous one).
- if (objectNewlyStuckToTarget && abs(velX) > stickToTargetMaxXVelocity) {
- return false
- }
-
- // This touch event is newly within the magnetic field - let the listener know, and
- // animate sticking to the magnet.
- targetObjectIsStuckTo = targetObjectIsInMagneticFieldOf
- cancelAnimations()
- magnetListener.onStuckToTarget(targetObjectIsInMagneticFieldOf!!)
- animateStuckToTarget(targetObjectIsInMagneticFieldOf, velX, velY, false, null)
-
- vibrateIfEnabled(VibrationEffect.EFFECT_HEAVY_CLICK)
- } else if (targetObjectIsInMagneticFieldOf == null && objectStuckToTarget) {
- velocityTracker.computeCurrentVelocity(1000)
-
- // This touch event is newly outside the magnetic field - let the listener know. It will
- // move the object out of the target using its own movement logic.
- cancelAnimations()
- magnetListener.onUnstuckFromTarget(
- targetObjectIsStuckTo!!, velocityTracker.xVelocity, velocityTracker.yVelocity,
- wasFlungOut = false)
- targetObjectIsStuckTo = null
-
- vibrateIfEnabled(VibrationEffect.EFFECT_TICK)
- }
-
- // First, check for relevant gestures concluding with an ACTION_UP.
- if (ev.action == MotionEvent.ACTION_UP) {
-
- velocityTracker.computeCurrentVelocity(1000 /* units */)
- val velX = velocityTracker.xVelocity
- val velY = velocityTracker.yVelocity
-
- // Cancel the magnetic animation since we might still be springing into the magnetic
- // target, but we're about to fling away or release.
- cancelAnimations()
-
- if (objectStuckToTarget) {
- if (-velY > flingUnstuckFromTargetMinVelocity) {
- // If the object is stuck, but it was forcefully flung away from the target in
- // the upward direction, tell the listener so the object can be animated out of
- // the target.
- magnetListener.onUnstuckFromTarget(
- targetObjectIsStuckTo!!, velX, velY, wasFlungOut = true)
- } else {
- // If the object is stuck and not flung away, it was released inside the target.
- magnetListener.onReleasedInTarget(targetObjectIsStuckTo!!)
- vibrateIfEnabled(VibrationEffect.EFFECT_HEAVY_CLICK)
- }
-
- // Either way, we're no longer stuck.
- targetObjectIsStuckTo = null
- return true
- }
-
- // The target we're flinging towards, or null if we're not flinging towards any target.
- val flungToTarget = associatedTargets.firstOrNull { target ->
- isForcefulFlingTowardsTarget(target, ev.rawX, ev.rawY, velX, velY)
- }
-
- if (flungToTarget != null) {
- // If this is a fling-to-target, animate the object to the magnet and then release
- // it.
- magnetListener.onStuckToTarget(flungToTarget)
- targetObjectIsStuckTo = flungToTarget
-
- animateStuckToTarget(flungToTarget, velX, velY, true) {
- magnetListener.onReleasedInTarget(flungToTarget)
- targetObjectIsStuckTo = null
- vibrateIfEnabled(VibrationEffect.EFFECT_HEAVY_CLICK)
- }
-
- return true
- }
-
- // If it's not either of those things, we are not interested.
- return false
- }
-
- return objectStuckToTarget // Always consume touch events if the object is stuck.
- }
-
- /** Plays the given vibration effect if haptics are enabled. */
- @SuppressLint("MissingPermission")
- private fun vibrateIfEnabled(effect: Int) {
- if (hapticsEnabled && systemHapticsEnabled) {
- vibrator.vibrate(effect.toLong())
- }
- }
-
- /** Adds the movement to the velocity tracker using raw coordinates. */
- private fun addMovement(event: MotionEvent) {
- // Add movement to velocity tracker using raw screen X and Y coordinates instead
- // of window coordinates because the window frame may be moving at the same time.
- val deltaX = event.rawX - event.x
- val deltaY = event.rawY - event.y
- event.offsetLocation(deltaX, deltaY)
- velocityTracker.addMovement(event)
- event.offsetLocation(-deltaX, -deltaY)
- }
-
- /** Animates sticking the object to the provided target with the given start velocities. */
- private fun animateStuckToTargetInternal(
- target: MagneticTarget,
- velX: Float,
- velY: Float,
- flung: Boolean,
- after: (() -> Unit)? = null
- ) {
- target.updateLocationOnScreen()
- getLocationOnScreen(underlyingObject, objectLocationOnScreen)
-
- // Calculate the difference between the target's center coordinates and the object's.
- // Animating the object's x/y properties by these values will center the object on top
- // of the magnetic target.
- val xDiff = target.centerOnScreen.x -
- getWidth(underlyingObject) / 2f - objectLocationOnScreen[0]
- val yDiff = target.centerOnScreen.y -
- getHeight(underlyingObject) / 2f - objectLocationOnScreen[1]
-
- val springConfig = if (flung) flungIntoTargetSpringConfig else springConfig
-
- cancelAnimations()
-
- // Animate to the center of the target.
- animator
- .spring(xProperty, xProperty.getValue(underlyingObject) + xDiff, velX,
- springConfig)
- .spring(yProperty, yProperty.getValue(underlyingObject) + yDiff, velY,
- springConfig)
-
- if (physicsAnimatorUpdateListener != null) {
- animator.addUpdateListener(physicsAnimatorUpdateListener!!)
- }
-
- if (physicsAnimatorEndListener != null) {
- animator.addEndListener(physicsAnimatorEndListener!!)
- }
-
- if (after != null) {
- animator.withEndActions(after)
- }
-
- animator.start()
- }
-
- /**
- * Whether or not the provided values match a 'fast fling' towards the provided target. If it
- * does, we consider it a fling-to-target gesture.
- */
- private fun isForcefulFlingTowardsTarget(
- target: MagneticTarget,
- rawX: Float,
- rawY: Float,
- velX: Float,
- velY: Float
- ): Boolean {
- if (!flingToTargetEnabled) {
- return false
- }
-
- // Whether velocity is sufficient, depending on whether we're flinging into a target at the
- // top or the bottom of the screen.
- val velocitySufficient =
- if (rawY < target.centerOnScreen.y) velY > flingToTargetMinVelocity
- else velY < flingToTargetMinVelocity
-
- if (!velocitySufficient) {
- return false
- }
-
- // Whether the trajectory of the fling intersects the target area.
- var targetCenterXIntercept = rawX
-
- // Only do math if the X velocity is non-zero, otherwise X won't change.
- if (velX != 0f) {
- // Rise over run...
- val slope = velY / velX
- // ...y = mx + b, b = y / mx...
- val yIntercept = rawY - slope * rawX
-
- // ...calculate the x value when y = the target's y-coordinate.
- targetCenterXIntercept = (target.centerOnScreen.y - yIntercept) / slope
- }
-
- // The width of the area we're looking for a fling towards.
- val targetAreaWidth = target.targetView.width * flingToTargetWidthPercent
-
- // Velocity was sufficient, so return true if the intercept is within the target area.
- return targetCenterXIntercept > target.centerOnScreen.x - targetAreaWidth / 2 &&
- targetCenterXIntercept < target.centerOnScreen.x + targetAreaWidth / 2
- }
-
- /** Cancel animations on this object's x/y properties. */
- internal fun cancelAnimations() {
- animator.cancel(xProperty, yProperty)
- }
-
- /** Updates the locations on screen of all of the [associatedTargets]. */
- 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
- }
- }
-
- /**
- * Represents a target view with a magnetic field radius and cached center-on-screen
- * coordinates.
- *
- * Instances of MagneticTarget are passed to a MagnetizedObject's [addTarget], and can then
- * attract the object if it's dragged near or flung towards it. MagneticTargets can be added to
- * multiple objects.
- */
- class MagneticTarget(
- val targetView: View,
- var magneticFieldRadiusPx: Int
- ) {
- val centerOnScreen = PointF()
-
- private val tempLoc = IntArray(2)
-
- fun updateLocationOnScreen() {
- targetView.post {
- targetView.getLocationOnScreen(tempLoc)
-
- // Add half of the target size to get the center, and subtract translation since the
- // target could be animating in while we're doing this calculation.
- centerOnScreen.set(
- tempLoc[0] + targetView.width / 2f - targetView.translationX,
- tempLoc[1] + targetView.height / 2f - targetView.translationY)
- }
- }
- }
-
- companion object {
-
- /**
- * Whether the HAPTIC_FEEDBACK_ENABLED setting is true.
- *
- * We put it in the companion object because we need to register a settings observer and
- * [MagnetizedObject] doesn't have an obvious lifecycle so we don't have a good time to
- * remove that observer. Since this settings is shared among all instances we just let all
- * instances read from this value.
- */
- private var systemHapticsEnabled = false
- private var hapticSettingObserverInitialized = false
-
- private fun initHapticSettingObserver(context: Context) {
- if (hapticSettingObserverInitialized) {
- return
- }
-
- val hapticSettingObserver =
- object : ContentObserver(Handler.getMain()) {
- override fun onChange(selfChange: Boolean) {
- systemHapticsEnabled =
- Settings.System.getIntForUser(
- context.contentResolver,
- Settings.System.HAPTIC_FEEDBACK_ENABLED,
- 0,
- UserHandle.USER_CURRENT) != 0
- }
- }
-
- context.contentResolver.registerContentObserver(
- Settings.System.getUriFor(Settings.System.HAPTIC_FEEDBACK_ENABLED),
- true /* notifyForDescendants */, hapticSettingObserver)
-
- // Trigger the observer once to initialize systemHapticsEnabled.
- hapticSettingObserver.onChange(false /* selfChange */)
- hapticSettingObserverInitialized = true
- }
-
- /**
- * Magnetizes the given view. Magnetized views are attracted to one or more magnetic
- * targets. Magnetic targets attract objects that are dragged near them, and hold them there
- * unless they're moved away or released. Releasing objects inside a magnetic target
- * typically performs an action on the object.
- *
- * Magnetized views can also be flung to targets, which will result in the view being pulled
- * into the target and released as if it was dragged into it.
- *
- * To use the returned MagnetizedObject<View> instance, first set [magnetListener] to
- * receive event callbacks. In your touch handler, pass all MotionEvents that move this view
- * to [maybeConsumeMotionEvent]. If that method returns true, consider the event consumed by
- * MagnetizedObject and don't move the view unless it begins returning false again.
- *
- * The view will be moved via translationX/Y properties, and its
- * width/height will be determined via getWidth()/getHeight(). If you are animating
- * something other than a view, or want to position your view using properties other than
- * translationX/Y, implement an instance of [MagnetizedObject].
- *
- * Note that the magnetic library can't re-order your view automatically. If the view
- * renders on top of the target views, it will obscure the target when it sticks to it.
- * You'll want to bring the view to the front in [MagnetListener.onStuckToTarget].
- */
- @JvmStatic
- fun <T : View> magnetizeView(view: T): MagnetizedObject<T> {
- return object : MagnetizedObject<T>(
- view.context,
- view,
- DynamicAnimation.TRANSLATION_X,
- DynamicAnimation.TRANSLATION_Y) {
- override fun getWidth(underlyingObject: T): Float {
- return underlyingObject.width.toFloat()
- }
-
- override fun getHeight(underlyingObject: T): Float {
- return underlyingObject.height.toFloat() }
-
- override fun getLocationOnScreen(underlyingObject: T, loc: IntArray) {
- underlyingObject.getLocationOnScreen(loc)
- }
- }
- }
- }
-} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorImpl.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorImpl.java
index aa50292edbf7..71b255229c8f 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorImpl.java
@@ -199,7 +199,7 @@ class ThresholdSensorImpl implements ThresholdSensor {
@Override
public String toString() {
- return String.format("{registered=%s, paused=%s, threshold=%s, sensor=%s}",
+ return String.format("{isLoaded=%s, registered=%s, paused=%s, threshold=%s, sensor=%s}",
isLoaded(), mRegistered, mPaused, mThreshold, mSensor);
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 78f83d3c09b4..735338f84267 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -90,6 +90,7 @@ import com.android.settingslib.Utils;
import com.android.systemui.Dependency;
import com.android.systemui.Prefs;
import com.android.systemui.R;
+import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.VolumeDialog;
import com.android.systemui.plugins.VolumeDialogController;
@@ -122,8 +123,11 @@ public class VolumeDialogImpl implements VolumeDialog,
static final int DIALOG_SAFETYWARNING_TIMEOUT_MILLIS = 5000;
static final int DIALOG_ODI_CAPTIONS_TOOLTIP_TIMEOUT_MILLIS = 5000;
static final int DIALOG_HOVERING_TIMEOUT_MILLIS = 16000;
- static final int DIALOG_SHOW_ANIMATION_DURATION = 300;
- static final int DIALOG_HIDE_ANIMATION_DURATION = 250;
+
+ private final int mDialogShowAnimationDurationMs;
+ private final int mDialogHideAnimationDurationMs;
+ private final boolean mShowLowMediaVolumeIcon;
+ private final boolean mChangeVolumeRowTintWhenInactive;
private final Context mContext;
private final H mHandler = new H();
@@ -154,8 +158,6 @@ public class VolumeDialogImpl implements VolumeDialog,
private boolean mShowing;
private boolean mShowA11yStream;
- private final boolean mShowLowMediaVolumeIcon;
-
private int mActiveStream;
private int mPrevActiveStream;
private boolean mAutomute = VolumePrefs.DEFAULT_ENABLE_AUTOMUTE;
@@ -183,6 +185,12 @@ public class VolumeDialogImpl implements VolumeDialog,
Prefs.getBoolean(context, Prefs.Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP, false);
mShowLowMediaVolumeIcon =
mContext.getResources().getBoolean(R.bool.config_showLowMediaVolumeIcon);
+ mChangeVolumeRowTintWhenInactive =
+ mContext.getResources().getBoolean(R.bool.config_changeVolumeRowTintWhenInactive);
+ mDialogShowAnimationDurationMs =
+ mContext.getResources().getInteger(R.integer.config_dialogShowAnimationDurationMs);
+ mDialogHideAnimationDurationMs =
+ mContext.getResources().getInteger(R.integer.config_dialogHideAnimationDurationMs);
}
@Override
@@ -272,7 +280,7 @@ public class VolumeDialogImpl implements VolumeDialog,
mDialogView.animate()
.alpha(1)
.translationX(0)
- .setDuration(DIALOG_SHOW_ANIMATION_DURATION)
+ .setDuration(mDialogShowAnimationDurationMs)
.setInterpolator(new SystemUIInterpolators.LogDecelerateInterpolator())
.withEndAction(() -> {
if (!Prefs.getBoolean(mContext, Prefs.Key.TOUCHED_RINGER_TOGGLE, false)) {
@@ -512,6 +520,7 @@ public class VolumeDialogImpl implements VolumeDialog,
Events.writeEvent(Events.EVENT_SETTINGS_CLICK);
Intent intent = new Intent(Settings.Panel.ACTION_VOLUME);
dismissH(DISMISS_REASON_SETTINGS_CLICKED);
+ Dependency.get(MediaOutputDialogFactory.class).dismiss();
Dependency.get(ActivityStarter.class).startActivity(intent,
true /* dismissShade */);
});
@@ -592,7 +601,7 @@ public class VolumeDialogImpl implements VolumeDialog,
mODICaptionsTooltipView.setAlpha(0.f);
mODICaptionsTooltipView.animate()
.alpha(1.f)
- .setStartDelay(DIALOG_SHOW_ANIMATION_DURATION)
+ .setStartDelay(mDialogShowAnimationDurationMs)
.withEndAction(() -> {
if (D.BUG) Log.d(TAG, "tool:checkODICaptionsTooltip() putBoolean true");
Prefs.putBoolean(mContext,
@@ -614,7 +623,7 @@ public class VolumeDialogImpl implements VolumeDialog,
mODICaptionsTooltipView.animate()
.alpha(0.f)
.setStartDelay(0)
- .setDuration(DIALOG_HIDE_ANIMATION_DURATION)
+ .setDuration(mDialogHideAnimationDurationMs)
.withEndAction(() -> mODICaptionsTooltipView.setVisibility(INVISIBLE))
.start();
}
@@ -793,7 +802,7 @@ public class VolumeDialogImpl implements VolumeDialog,
mDialogView.setAlpha(1);
ViewPropertyAnimator animator = mDialogView.animate()
.alpha(0)
- .setDuration(DIALOG_HIDE_ANIMATION_DURATION)
+ .setDuration(mDialogHideAnimationDurationMs)
.setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator())
.withEndAction(() -> mHandler.postDelayed(() -> {
mDialog.dismiss();
@@ -1076,7 +1085,7 @@ public class VolumeDialogImpl implements VolumeDialog,
iconRes = isStreamMuted(ss) ? R.drawable.ic_volume_media_bt_mute
: R.drawable.ic_volume_media_bt;
} else if (isStreamMuted(ss)) {
- iconRes = row.iconMuteRes;
+ iconRes = ss.muted ? R.drawable.ic_volume_media_off : row.iconMuteRes;
} else {
iconRes = mShowLowMediaVolumeIcon && ss.level * 2 < (ss.levelMax + ss.levelMin)
? R.drawable.ic_volume_media_low : row.iconRes;
@@ -1154,6 +1163,9 @@ public class VolumeDialogImpl implements VolumeDialog,
row.slider.requestFocus();
}
boolean useActiveColoring = isActive && row.slider.isEnabled();
+ if (!useActiveColoring && !mChangeVolumeRowTintWhenInactive) {
+ return;
+ }
final ColorStateList tint = useActiveColoring
? Utils.getColorAccent(mContext)
: Utils.getColorAttr(mContext, android.R.attr.colorForeground);
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
new file mode 100644
index 000000000000..f55445ca1de3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
@@ -0,0 +1,106 @@
+/*
+ * 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.wmshell;
+
+import android.content.Context;
+import android.os.Handler;
+import android.view.LayoutInflater;
+
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.WindowManagerShellWrapper;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.pip.PipBoundsHandler;
+import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
+import com.android.wm.shell.pip.PipTaskOrganizer;
+import com.android.wm.shell.pip.PipUiEventLogger;
+import com.android.wm.shell.pip.tv.PipController;
+import com.android.wm.shell.pip.tv.PipControlsView;
+import com.android.wm.shell.pip.tv.PipControlsViewController;
+import com.android.wm.shell.pip.tv.PipNotification;
+import com.android.wm.shell.splitscreen.SplitScreen;
+
+import java.util.Optional;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Dagger module for TV Pip.
+ */
+@Module
+public abstract class TvPipModule {
+
+ @SysUISingleton
+ @Provides
+ static Pip providePipController(Context context,
+ PipBoundsHandler pipBoundsHandler,
+ PipTaskOrganizer pipTaskOrganizer,
+ WindowManagerShellWrapper windowManagerShellWrapper) {
+ return new PipController(context, pipBoundsHandler, pipTaskOrganizer,
+ windowManagerShellWrapper);
+ }
+
+ @SysUISingleton
+ @Provides
+ static PipControlsViewController providePipControlsViewContrller(
+ PipControlsView pipControlsView, PipController pipController,
+ LayoutInflater layoutInflater, Handler handler) {
+ return new PipControlsViewController(pipControlsView, pipController, layoutInflater,
+ handler);
+ }
+
+ @SysUISingleton
+ @Provides
+ static PipControlsView providePipControlsView(Context context) {
+ return new PipControlsView(context, null);
+ }
+
+ @SysUISingleton
+ @Provides
+ static PipNotification providePipNotification(Context context,
+ PipController pipController) {
+ return new PipNotification(context, pipController);
+ }
+
+ @SysUISingleton
+ @Provides
+ static PipBoundsHandler providePipBoundsHandler(Context context) {
+ return new PipBoundsHandler(context);
+ }
+
+ @SysUISingleton
+ @Provides
+ static PipBoundsState providePipBoundsState() {
+ return new PipBoundsState();
+ }
+
+ @SysUISingleton
+ @Provides
+ static PipTaskOrganizer providePipTaskOrganizer(Context context,
+ PipBoundsState pipBoundsState,
+ PipBoundsHandler pipBoundsHandler,
+ PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
+ Optional<SplitScreen> splitScreenOptional, DisplayController displayController,
+ PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer) {
+ return new PipTaskOrganizer(context, pipBoundsState, pipBoundsHandler,
+ pipSurfaceTransactionHelper, splitScreenOptional, displayController,
+ pipUiEventLogger, shellTaskOrganizer);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java
index 0869cf739d02..56efffc29d85 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java
@@ -20,26 +20,18 @@ import android.content.Context;
import android.os.Handler;
import android.view.IWindowManager;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.pip.Pip;
-import com.android.systemui.pip.PipBoundsHandler;
-import com.android.systemui.pip.PipSurfaceTransactionHelper;
-import com.android.systemui.pip.PipTaskOrganizer;
-import com.android.systemui.pip.PipUiEventLogger;
-import com.android.systemui.pip.tv.PipController;
-import com.android.systemui.pip.tv.PipNotification;
-import com.android.systemui.pip.tv.dagger.TvPipComponent;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.splitscreen.SplitScreenController;
-import java.util.Optional;
+import java.util.concurrent.Executor;
import dagger.Module;
import dagger.Provides;
@@ -49,60 +41,23 @@ import dagger.Provides;
* branches of SystemUI.
*/
// TODO(b/162923491): Move most of these dependencies into WMSingleton scope.
-@Module(includes = WMShellBaseModule.class, subcomponents = {TvPipComponent.class})
+@Module(includes = {WMShellBaseModule.class, TvPipModule.class})
public class TvWMShellModule {
@SysUISingleton
@Provides
static DisplayImeController provideDisplayImeController(IWindowManager wmService,
- DisplayController displayController, @Main Handler mainHandler,
+ DisplayController displayController, @Main Executor mainExecutor,
TransactionPool transactionPool) {
- return new DisplayImeController(wmService, displayController, mainHandler, transactionPool);
+ return new DisplayImeController(wmService, displayController, mainExecutor,
+ transactionPool);
}
- @SysUISingleton
- @Provides
- static Pip providePipController(Context context,
- BroadcastDispatcher broadcastDispatcher,
- PipBoundsHandler pipBoundsHandler,
- PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
- PipTaskOrganizer pipTaskOrganizer) {
- return new PipController(context, broadcastDispatcher, pipBoundsHandler,
- pipSurfaceTransactionHelper, pipTaskOrganizer);
- }
-
- @SysUISingleton
- @Provides
static SplitScreen provideSplitScreen(Context context,
DisplayController displayController, SystemWindows systemWindows,
DisplayImeController displayImeController, @Main Handler handler,
- TransactionPool transactionPool, ShellTaskOrganizer shellTaskOrganizer) {
+ TransactionPool transactionPool, ShellTaskOrganizer shellTaskOrganizer,
+ SyncTransactionQueue syncQueue) {
return new SplitScreenController(context, displayController, systemWindows,
- displayImeController, handler, transactionPool, shellTaskOrganizer);
- }
-
- @SysUISingleton
- @Provides
- static PipNotification providePipNotification(Context context,
- BroadcastDispatcher broadcastDispatcher,
- PipController pipController) {
- return new PipNotification(context, broadcastDispatcher, pipController);
- }
-
- @SysUISingleton
- @Provides
- static PipBoundsHandler providesPipBoundsHandler(Context context) {
- return new PipBoundsHandler(context);
- }
-
- @SysUISingleton
- @Provides
- static PipTaskOrganizer providesPipTaskOrganizer(Context context,
- PipBoundsHandler pipBoundsHandler,
- PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
- Optional<SplitScreen> splitScreenOptional, DisplayController displayController,
- PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer) {
- return new PipTaskOrganizer(context, pipBoundsHandler,
- pipSurfaceTransactionHelper, splitScreenOptional, displayController,
- pipUiEventLogger, shellTaskOrganizer);
+ displayImeController, handler, transactionPool, shellTaskOrganizer, syncQueue);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index ce125f3fdce0..9281a090fd97 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -16,34 +16,52 @@
package com.android.systemui.wmshell;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ONE_HANDED_ACTIVE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.ActivityTaskManager.RootTaskInfo;
import android.content.ComponentName;
import android.content.Context;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
import android.inputmethodservice.InputMethodService;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Pair;
import android.view.KeyEvent;
import com.android.internal.annotations.VisibleForTesting;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.Dependency;
import com.android.systemui.SystemUI;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationModeController;
-import com.android.systemui.pip.Pip;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.shared.tracing.ProtoTraceable;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.tracing.ProtoTracer;
import com.android.systemui.tracing.nano.SystemUiTraceProto;
import com.android.wm.shell.ShellTaskOrganizer;
@@ -53,11 +71,12 @@ import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedEvents;
import com.android.wm.shell.onehanded.OneHandedGestureHandler.OneHandedGestureEventCallback;
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
+import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.pip.phone.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogImpl;
import com.android.wm.shell.splitscreen.SplitScreen;
import java.io.FileDescriptor;
-import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Optional;
@@ -70,10 +89,22 @@ import javax.inject.Inject;
@SysUISingleton
public final class WMShell extends SystemUI
implements CommandQueue.Callbacks, ProtoTraceable<SystemUiTraceProto> {
+ private static final String TAG = WMShell.class.getName();
+ private static final int INVALID_SYSUI_STATE_MASK =
+ SYSUI_STATE_GLOBAL_ACTIONS_SHOWING
+ | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
+ | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED
+ | SYSUI_STATE_BOUNCER_SHOWING
+ | SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
+ | SYSUI_STATE_BUBBLES_EXPANDED
+ | SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
+
private final CommandQueue mCommandQueue;
+ private final ConfigurationController mConfigurationController;
private final DisplayImeController mDisplayImeController;
+ private final InputConsumerController mInputConsumerController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- private final ActivityManagerWrapper mActivityManagerWrapper;
+ private final TaskStackChangeListeners mTaskStackChangeListeners;
private final NavigationModeController mNavigationModeController;
private final ScreenLifecycle mScreenLifecycle;
private final SysUiState mSysUiState;
@@ -84,14 +115,17 @@ public final class WMShell extends SystemUI
// are non-optional windowing features like FULLSCREEN.
private final ShellTaskOrganizer mShellTaskOrganizer;
private final ProtoTracer mProtoTracer;
-
+ private boolean mIsSysUiStateValid;
private KeyguardUpdateMonitorCallback mSplitScreenKeyguardCallback;
+ private KeyguardUpdateMonitorCallback mPipKeyguardCallback;
private KeyguardUpdateMonitorCallback mOneHandedKeyguardCallback;
@Inject
public WMShell(Context context, CommandQueue commandQueue,
+ ConfigurationController configurationController,
+ InputConsumerController inputConsumerController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
- ActivityManagerWrapper activityManagerWrapper,
+ TaskStackChangeListeners taskStackChangeListeners,
DisplayImeController displayImeController,
NavigationModeController navigationModeController,
ScreenLifecycle screenLifecycle,
@@ -103,9 +137,10 @@ public final class WMShell extends SystemUI
ProtoTracer protoTracer) {
super(context);
mCommandQueue = commandQueue;
- mCommandQueue.addCallback(this);
+ mConfigurationController = configurationController;
+ mInputConsumerController = inputConsumerController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
- mActivityManagerWrapper = activityManagerWrapper;
+ mTaskStackChangeListeners = taskStackChangeListeners;
mDisplayImeController = displayImeController;
mNavigationModeController = navigationModeController;
mScreenLifecycle = screenLifecycle;
@@ -120,6 +155,7 @@ public final class WMShell extends SystemUI
@Override
public void start() {
+ mCommandQueue.addCallback(this);
// This is to prevent circular init problem by separating registration step out of its
// constructor. And make sure the initialization of DisplayImeController won't depend on
// specific feature anymore.
@@ -131,12 +167,99 @@ public final class WMShell extends SystemUI
@VisibleForTesting
void initPip(Pip pip) {
+ if (!PipUtils.hasSystemFeature(mContext)) {
+ return;
+ }
mCommandQueue.addCallback(new CommandQueue.Callbacks() {
@Override
public void showPictureInPictureMenu() {
pip.showPictureInPictureMenu();
}
});
+
+ mPipKeyguardCallback = new KeyguardUpdateMonitorCallback() {
+ @Override
+ public void onKeyguardVisibilityChanged(boolean showing) {
+ if (showing) {
+ pip.hidePipMenu(null, null);
+ }
+ }
+ };
+ mKeyguardUpdateMonitor.registerCallback(mPipKeyguardCallback);
+
+ mSysUiState.addCallback(sysUiStateFlag -> {
+ mIsSysUiStateValid = (sysUiStateFlag & INVALID_SYSUI_STATE_MASK) == 0;
+ pip.onSystemUiStateChanged(mIsSysUiStateValid, sysUiStateFlag);
+ });
+
+ mConfigurationController.addCallback(new ConfigurationController.ConfigurationListener() {
+ @Override
+ public void onDensityOrFontScaleChanged() {
+ pip.onDensityOrFontScaleChanged();
+ }
+
+ @Override
+ public void onOverlayChanged() {
+ pip.onOverlayChanged();
+ }
+ });
+
+ // Handle for system task stack changes.
+ mTaskStackChangeListeners.registerTaskStackListener(
+ new TaskStackChangeListener() {
+ @Override
+ public void onTaskStackChanged() {
+ pip.onTaskStackChanged();
+ }
+
+ @Override
+ public void onActivityPinned(String packageName, int userId, int taskId,
+ int stackId) {
+ pip.onActivityPinned(packageName);
+ mInputConsumerController.registerInputConsumer(true /* withSfVsync */);
+ }
+
+ @Override
+ public void onActivityUnpinned() {
+ final Pair<ComponentName, Integer> topPipActivityInfo =
+ PipUtils.getTopPipActivity(
+ mContext, ActivityManager.getService());
+ final ComponentName topActivity = topPipActivityInfo.first;
+ pip.onActivityUnpinned(topActivity);
+ mInputConsumerController.unregisterInputConsumer();
+ }
+
+ @Override
+ public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
+ boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
+ pip.onActivityRestartAttempt(task, clearedTask);
+ }
+ });
+
+ try {
+ RootTaskInfo taskInfo = ActivityTaskManager.getService().getRootTaskInfo(
+ WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
+ if (taskInfo != null) {
+ // If SystemUI restart, and it already existed a pinned stack,
+ // register the pip input consumer to ensure touch can send to it.
+ mInputConsumerController.registerInputConsumer(true /* withSfVsync */);
+ }
+ } catch (RemoteException | UnsupportedOperationException e) {
+ Log.e(TAG, "Failed to register pinned stack listener", e);
+ e.printStackTrace();
+ }
+
+ // Register the listener for input consumer touch events. Only for Phone
+ if (pip.getPipTouchHandler() != null) {
+ mInputConsumerController.setInputListener(pip.getPipTouchHandler()::handleTouchEvent);
+ mInputConsumerController.setRegistrationListener(
+ pip.getPipTouchHandler()::onRegistrationChanged);
+ }
+
+ // The media session listener needs to be re-registered when switching users
+ UserInfoController userInfoController = Dependency.get(UserInfoController.class);
+ userInfoController.addCallback((String name, Drawable picture, String userAccount) ->
+ pip.registerSessionListenerForCurrentUser());
}
@VisibleForTesting
@@ -153,7 +276,7 @@ public final class WMShell extends SystemUI
};
mKeyguardUpdateMonitor.registerCallback(mSplitScreenKeyguardCallback);
- mActivityManagerWrapper.registerTaskStackListener(
+ mTaskStackChangeListeners.registerTaskStackListener(
new TaskStackChangeListener() {
@Override
public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
@@ -189,10 +312,6 @@ public final class WMShell extends SystemUI
@VisibleForTesting
void initOneHanded(OneHanded oneHanded) {
- if (!oneHanded.hasOneHandedFeature()) {
- return;
- }
-
int currentMode = mNavigationModeController.addListener(mode ->
oneHanded.setThreeButtonModeEnabled(mode == NAV_BAR_MODE_3BUTTON));
oneHanded.setThreeButtonModeEnabled(currentMode == NAV_BAR_MODE_3BUTTON);
@@ -269,7 +388,7 @@ public final class WMShell extends SystemUI
}
});
- mActivityManagerWrapper.registerTaskStackListener(
+ mTaskStackChangeListeners.registerTaskStackListener(
new TaskStackChangeListener() {
@Override
public void onTaskCreated(int taskId, ComponentName componentName) {
@@ -300,8 +419,8 @@ public final class WMShell extends SystemUI
if (handleLoggingCommand(args, pw)) {
return;
}
-
// Dump WMShell stuff here if no commands were handled
+ mOneHandedOptional.ifPresent(oneHanded -> oneHanded.dump(pw));
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index 7c129ac92fe3..09678b5d1772 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -16,6 +16,7 @@
package com.android.systemui.wmshell;
+import android.app.IActivityManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Handler;
@@ -23,22 +24,33 @@ import android.util.DisplayMetrics;
import android.view.IWindowManager;
import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.pip.Pip;
-import com.android.systemui.pip.PipSurfaceTransactionHelper;
-import com.android.systemui.pip.PipUiEventLogger;
-import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.util.DeviceConfigProxy;
-import com.android.systemui.util.FloatingContentCoordinator;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.animation.FlingAnimationUtils;
+import com.android.wm.shell.common.AnimationThread;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.HandlerExecutor;
+import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.onehanded.OneHanded;
+import com.android.wm.shell.onehanded.OneHandedController;
+import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
+import com.android.wm.shell.pip.PipUiEventLogger;
+import com.android.wm.shell.pip.phone.PipAppOpsListener;
+import com.android.wm.shell.pip.phone.PipMediaController;
+import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.splitscreen.SplitScreen;
+import java.util.Optional;
+
import dagger.BindsOptionalOf;
import dagger.Module;
import dagger.Provides;
@@ -71,12 +83,33 @@ public abstract class WMShellBaseModule {
@SysUISingleton
@Provides
+ static InputConsumerController provideInputConsumerController() {
+ return InputConsumerController.getPipInputConsumer();
+ }
+
+ @SysUISingleton
+ @Provides
static FloatingContentCoordinator provideFloatingContentCoordinator() {
return new FloatingContentCoordinator();
}
@SysUISingleton
@Provides
+ static PipAppOpsListener providePipAppOpsListener(Context context,
+ IActivityManager activityManager,
+ PipTouchHandler pipTouchHandler) {
+ return new PipAppOpsListener(context, activityManager, pipTouchHandler.getMotionHelper());
+ }
+
+ @SysUISingleton
+ @Provides
+ static PipMediaController providePipMediaController(Context context,
+ IActivityManager activityManager) {
+ return new PipMediaController(context, activityManager);
+ }
+
+ @SysUISingleton
+ @Provides
static PipUiEventLogger providePipUiEventLogger(UiEventLogger uiEventLogger,
PackageManager packageManager) {
return new PipUiEventLogger(uiEventLogger, packageManager);
@@ -84,9 +117,8 @@ public abstract class WMShellBaseModule {
@SysUISingleton
@Provides
- static PipSurfaceTransactionHelper providesPipSurfaceTransactionHelper(Context context,
- ConfigurationController configController) {
- return new PipSurfaceTransactionHelper(context, configController);
+ static PipSurfaceTransactionHelper providesPipSurfaceTransactionHelper(Context context) {
+ return new PipSurfaceTransactionHelper(context);
}
@SysUISingleton
@@ -98,14 +130,29 @@ public abstract class WMShellBaseModule {
@SysUISingleton
@Provides
- static ShellTaskOrganizer provideShellTaskOrganizer(TransactionPool transactionPool) {
- ShellTaskOrganizer organizer = new ShellTaskOrganizer(transactionPool);
+ static SyncTransactionQueue provideSyncTransactionQueue(@Main Handler handler,
+ TransactionPool pool) {
+ return new SyncTransactionQueue(pool, handler);
+ }
+
+ @SysUISingleton
+ @Provides
+ static ShellTaskOrganizer provideShellTaskOrganizer(SyncTransactionQueue syncQueue,
+ @Main Handler handler, TransactionPool transactionPool) {
+ ShellTaskOrganizer organizer = new ShellTaskOrganizer(syncQueue, transactionPool,
+ new HandlerExecutor(handler), AnimationThread.instance().getExecutor());
organizer.registerOrganizer();
return organizer;
}
@SysUISingleton
@Provides
+ static WindowManagerShellWrapper provideWindowManagerShellWrapper() {
+ return new WindowManagerShellWrapper();
+ }
+
+ @SysUISingleton
+ @Provides
static FlingAnimationUtils.Builder provideFlingAnimationUtilsBuilder(
DisplayMetrics displayMetrics) {
return new FlingAnimationUtils.Builder(displayMetrics);
@@ -118,5 +165,12 @@ public abstract class WMShellBaseModule {
abstract SplitScreen optionalSplitScreen();
@BindsOptionalOf
- abstract OneHanded optionalOneHanded();
+ abstract Bubbles optionalBubbles();
+
+ @SysUISingleton
+ @Provides
+ static Optional<OneHanded> provideOneHandedController(Context context,
+ DisplayController displayController) {
+ return Optional.ofNullable(OneHandedController.create(context, displayController));
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
index 16fb2cacc950..975757a4c259 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
@@ -20,30 +20,32 @@ import android.content.Context;
import android.os.Handler;
import android.view.IWindowManager;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.model.SysUiState;
-import com.android.systemui.pip.Pip;
-import com.android.systemui.pip.PipBoundsHandler;
-import com.android.systemui.pip.PipSurfaceTransactionHelper;
-import com.android.systemui.pip.PipTaskOrganizer;
-import com.android.systemui.pip.PipUiEventLogger;
-import com.android.systemui.pip.phone.PipController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.util.DeviceConfigProxy;
-import com.android.systemui.util.FloatingContentCoordinator;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.common.TransactionPool;
-import com.android.wm.shell.onehanded.OneHanded;
-import com.android.wm.shell.onehanded.OneHandedController;
+import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.pip.PipBoundsHandler;
+import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
+import com.android.wm.shell.pip.PipTaskOrganizer;
+import com.android.wm.shell.pip.PipUiEventLogger;
+import com.android.wm.shell.pip.phone.PipAppOpsListener;
+import com.android.wm.shell.pip.phone.PipController;
+import com.android.wm.shell.pip.phone.PipMediaController;
+import com.android.wm.shell.pip.phone.PipMenuActivityController;
+import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.splitscreen.SplitScreenController;
import java.util.Optional;
+import java.util.concurrent.Executor;
import dagger.Module;
import dagger.Provides;
@@ -58,29 +60,28 @@ public class WMShellModule {
@SysUISingleton
@Provides
static DisplayImeController provideDisplayImeController(IWindowManager wmService,
- DisplayController displayController, @Main Handler mainHandler,
+ DisplayController displayController, @Main Executor mainExecutor,
TransactionPool transactionPool) {
- return new DisplayImeController(wmService, displayController, mainHandler, transactionPool);
+ return new DisplayImeController(wmService, displayController, mainExecutor,
+ transactionPool);
}
@SysUISingleton
@Provides
static Pip providePipController(Context context,
- BroadcastDispatcher broadcastDispatcher,
- ConfigurationController configController,
- DeviceConfigProxy deviceConfig,
DisplayController displayController,
- FloatingContentCoordinator floatingContentCoordinator,
- SysUiState sysUiState,
+ PipAppOpsListener pipAppOpsListener,
PipBoundsHandler pipBoundsHandler,
- PipSurfaceTransactionHelper surfaceTransactionHelper,
+ PipBoundsState pipBoundsState,
+ PipMediaController pipMediaController,
+ PipMenuActivityController pipMenuActivityController,
PipTaskOrganizer pipTaskOrganizer,
- PipUiEventLogger pipUiEventLogger) {
- return new PipController(context, broadcastDispatcher, configController, deviceConfig,
- displayController, floatingContentCoordinator, sysUiState, pipBoundsHandler,
- surfaceTransactionHelper,
- pipTaskOrganizer,
- pipUiEventLogger);
+ PipTouchHandler pipTouchHandler,
+ WindowManagerShellWrapper windowManagerShellWrapper) {
+ return new PipController(context, displayController,
+ pipAppOpsListener, pipBoundsHandler, pipBoundsState, pipMediaController,
+ pipMenuActivityController, pipTaskOrganizer, pipTouchHandler,
+ windowManagerShellWrapper);
}
@SysUISingleton
@@ -88,9 +89,16 @@ public class WMShellModule {
static SplitScreen provideSplitScreen(Context context,
DisplayController displayController, SystemWindows systemWindows,
DisplayImeController displayImeController, @Main Handler handler,
- TransactionPool transactionPool, ShellTaskOrganizer shellTaskOrganizer) {
+ TransactionPool transactionPool, ShellTaskOrganizer shellTaskOrganizer,
+ SyncTransactionQueue syncQueue) {
return new SplitScreenController(context, displayController, systemWindows,
- displayImeController, handler, transactionPool, shellTaskOrganizer);
+ displayImeController, handler, transactionPool, shellTaskOrganizer, syncQueue);
+ }
+
+ @SysUISingleton
+ @Provides
+ static PipBoundsState providePipBoundsState() {
+ return new PipBoundsState();
}
@SysUISingleton
@@ -101,20 +109,33 @@ public class WMShellModule {
@SysUISingleton
@Provides
+ static PipMenuActivityController providesPipMenuActivityController(Context context,
+ PipMediaController pipMediaController, PipTaskOrganizer pipTaskOrganizer) {
+ return new PipMenuActivityController(context, pipMediaController, pipTaskOrganizer);
+ }
+
+ @SysUISingleton
+ @Provides
+ static PipTouchHandler providesPipTouchHandler(Context context,
+ PipMenuActivityController menuActivityController, PipBoundsHandler pipBoundsHandler,
+ PipBoundsState pipBoundsState,
+ PipTaskOrganizer pipTaskOrganizer,
+ FloatingContentCoordinator floatingContentCoordinator,
+ PipUiEventLogger pipUiEventLogger) {
+ return new PipTouchHandler(context, menuActivityController, pipBoundsHandler,
+ pipBoundsState, pipTaskOrganizer, floatingContentCoordinator, pipUiEventLogger);
+ }
+
+ @SysUISingleton
+ @Provides
static PipTaskOrganizer providesPipTaskOrganizer(Context context,
+ PipBoundsState pipBoundsState,
PipBoundsHandler pipBoundsHandler,
PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
Optional<SplitScreen> splitScreenOptional, DisplayController displayController,
PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer) {
- return new PipTaskOrganizer(context, pipBoundsHandler,
+ return new PipTaskOrganizer(context, pipBoundsState, pipBoundsHandler,
pipSurfaceTransactionHelper, splitScreenOptional, displayController,
pipUiEventLogger, shellTaskOrganizer);
}
-
- @SysUISingleton
- @Provides
- static OneHanded provideOneHandedController(Context context,
- DisplayController displayController) {
- return OneHandedController.create(context, displayController);
- }
}