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/CarrierText.java12
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java16
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java22
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java7
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java2
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java12
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java2
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java18
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java2
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java2
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java8
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java8
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java332
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java98
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java63
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java22
-rw-r--r--packages/SystemUI/src/com/android/keyguard/LatencyTracker.java150
-rw-r--r--packages/SystemUI/src/com/android/keyguard/PasswordTextView.java53
-rw-r--r--packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/BatteryMeterView.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/ChargingView.java126
-rw-r--r--packages/SystemUI/src/com/android/systemui/DemoMode.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/Dependency.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/EventLogTags.logtags5
-rw-r--r--packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/ImageWallpaper.java520
-rw-r--r--packages/SystemUI/src/com/android/systemui/Interpolators.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/LatencyTester.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/OverviewProxyService.java267
-rw-r--r--packages/SystemUI/src/com/android/systemui/Prefs.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/RecentsComponent.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/RoundedCorners.java218
-rw-r--r--packages/SystemUI/src/com/android/systemui/ScreenDecorations.java415
-rw-r--r--packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java88
-rw-r--r--packages/SystemUI/src/com/android/systemui/SwipeHelper.java72
-rw-r--r--packages/SystemUI/src/com/android/systemui/SysUIToast.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIApplication.java70
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIFactory.java48
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIService.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/analytics/DataCollector.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java54
-rw-r--r--packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java216
-rw-r--r--packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java126
-rw-r--r--packages/SystemUI/src/com/android/systemui/charging/WirelessChargingView.java178
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java46
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeHost.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeService.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeUi.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java106
-rw-r--r--packages/SystemUI/src/com/android/systemui/fingerprint/FingerprintDialogImpl.java220
-rw-r--r--packages/SystemUI/src/com/android/systemui/fingerprint/FingerprintDialogView.java233
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java211
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java209
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java137
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/InputConsumerController.java161
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipAppOpsListener.java89
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java40
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java39
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipNotificationController.java231
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipUtils.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java29
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/EnhancedEstimates.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/Estimate.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java71
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/PowerUI.java171
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/CellTileView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java45
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java200
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFragment.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanel.java76
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSScrollLayout.java198
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java87
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/SignalTileView.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/TileLayout.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/car/CarQSFooter.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/car/CarQSFragment.java157
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java64
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java25
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.java102
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java119
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java47
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java61
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java75
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java40
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/touch/OverScroll.java57
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/touch/SwipeDetector.java356
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl4
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/IRecentsSystemUserCallbacks.aidl1
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/Recents.java125
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java194
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java40
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java65
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java397
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsImplProxy.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java255
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUser.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/events/activity/CancelEnterRecentsWindowAnimationEvent.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/events/activity/DebugFlagsChangedEvent.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/events/activity/IterateRecentsEvent.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/events/activity/LaunchTaskEvent.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/events/activity/MultiWindowStateChangedEvent.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/events/activity/PackagesChangedEvent.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/events/activity/TaskStackUpdatedEvent.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/events/component/ExpandPipEvent.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/events/ui/DeleteTaskDataEvent.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/events/ui/ShowApplicationInfoEvent.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/events/ui/TaskSnapshotChangedEvent.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/events/ui/TaskViewDismissedEvent.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/events/ui/UpdateFreeformTaskViewVisibilityEvent.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragDropTargetChangedEvent.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndCancelledEvent.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartInitializeDropTargetsEvent.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/misc/RectFEvaluator.java52
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/misc/SysUiTaskStackChangeListener.java (renamed from packages/SystemUI/src/com/android/systemui/recents/misc/NamedCounter.java)30
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java1025
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java325
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/model/HighResThumbnailLoader.java235
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java74
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java327
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java663
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/model/Task.java369
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/model/TaskGrouping.java106
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyCache.java89
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyLruCache.java94
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyStrongCache.java73
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java1142
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/model/ThumbnailData.java44
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java124
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/AnimationProps.java230
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/DockState.java351
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java170
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionComposer.java176
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java498
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java380
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java62
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java209
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java415
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java28
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java28
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskViewAccessibilityDelegate.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java89
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/grid/AnimateableGridViewBounds.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/grid/GridTaskView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskViewFocusFrame.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/lowram/TaskStackLowRamLayoutAlgorithm.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java131
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java98
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/ToggleSeekBar.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/ToggleSlider.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/ToggleSliderView.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java41
-rw-r--r--packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java38
-rw-r--r--packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java57
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java153
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java364
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java294
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java35
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java99
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java116
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java143
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java979
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java57
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java360
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java393
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationListContainer.java191
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java145
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java469
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationLogger.java227
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java267
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java120
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java451
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java57
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationUpdateHandler.java58
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java349
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java155
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java90
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java50
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java68
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java62
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/car/ConnectedDeviceSignalController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridView.java208
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java274
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java77
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingImageTransformState.java134
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java433
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java53
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/RowInflaterTask.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/TextViewTransformState.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java173
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java63
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java38
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/DoubleTapHelper.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java81
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java317
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java446
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java60
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java42
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java263
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java177
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java283
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java137
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java128
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java49
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java86
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java136
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubController.java431
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenPinningNotify.java75
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java783
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java234
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/SignalDrawable.java517
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java4162
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java108
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java180
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java582
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java47
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java99
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java56
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java55
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java29
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java42
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java95
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java68
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/TintedKeyButtonDrawable.java57
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java58
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationProperties.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/ExpandableViewState.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java414
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/ScrollContainer.java48
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java68
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java144
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java50
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/Utils.java53
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/Events.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/MediaRouterWrapper.java51
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java534
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/OutputChooserLayout.java256
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/SystemUIInterpolators.java78
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java107
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java778
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogMotion.java317
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeUiLayout.java153
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java180
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/ZenRadioLayout.java93
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/car/CarVolumeDialogController.java136
342 files changed, 21117 insertions, 16941 deletions
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierText.java b/packages/SystemUI/src/com/android/keyguard/CarrierText.java
index 13c48d0d7635..45d1aad8d3a4 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierText.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierText.java
@@ -268,6 +268,18 @@ public class CarrierText extends TextView {
}
}
+ @Override
+ protected void onVisibilityChanged(View changedView, int visibility) {
+ super.onVisibilityChanged(changedView, visibility);
+
+ // Only show marquee when visible
+ if (visibility == VISIBLE) {
+ setEllipsize(TextUtils.TruncateAt.MARQUEE);
+ } else {
+ setEllipsize(TextUtils.TruncateAt.END);
+ }
+ }
+
/**
* Top-level function for creating carrier text. Makes text based on simState, PLMN
* and SPN as well as device capabilities, such as being emergency call capable.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
index abc3b94777b0..d63ad0840734 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
@@ -16,8 +16,8 @@
package com.android.keyguard;
-import static com.android.keyguard.LatencyTracker.ACTION_CHECK_CREDENTIAL;
-import static com.android.keyguard.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED;
+import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL;
+import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED;
import android.content.Context;
import android.os.AsyncTask;
@@ -29,6 +29,7 @@ import android.view.KeyEvent;
import android.view.View;
import android.widget.LinearLayout;
+import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternChecker;
import com.android.internal.widget.LockPatternUtils;
@@ -223,8 +224,9 @@ public abstract class KeyguardAbsKeyInputView extends LinearLayout
@Override
public void onTick(long millisUntilFinished) {
int secondsRemaining = (int) Math.round(millisUntilFinished / 1000.0);
- mSecurityMessageDisplay.formatMessage(
- R.string.kg_too_many_failed_attempts_countdown, secondsRemaining);
+ mSecurityMessageDisplay.setMessage(mContext.getResources().getQuantityString(
+ R.plurals.kg_too_many_failed_attempts_countdown,
+ secondsRemaining, secondsRemaining));
}
@Override
@@ -278,7 +280,7 @@ public abstract class KeyguardAbsKeyInputView extends LinearLayout
@Override
public void showPromptReason(int reason) {
if (reason != PROMPT_REASON_NONE) {
- int promtReasonStringRes = getPromtReasonStringRes(reason);
+ int promtReasonStringRes = getPromptReasonStringRes(reason);
if (promtReasonStringRes != 0) {
mSecurityMessageDisplay.setMessage(promtReasonStringRes);
}
@@ -286,12 +288,12 @@ public abstract class KeyguardAbsKeyInputView extends LinearLayout
}
@Override
- public void showMessage(String message, int color) {
+ public void showMessage(CharSequence message, int color) {
mSecurityMessageDisplay.setNextMessageColor(color);
mSecurityMessageDisplay.setMessage(message);
}
- protected abstract int getPromtReasonStringRes(int reason);
+ protected abstract int getPromptReasonStringRes(int reason);
// Cause a VIRTUAL_KEY vibration
public void doHapticKeyClick() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java b/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java
index cb5afec79073..b8a07cdbdc41 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java
@@ -16,14 +16,18 @@
package com.android.keyguard;
+import android.app.AlertDialog;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.HandlerThread;
import android.os.UserHandle;
import android.util.AttributeSet;
import android.view.View;
+import android.view.WindowManager;
import android.widget.Button;
import android.telephony.SubscriptionManager;
import android.telephony.SubscriptionInfo;
@@ -50,8 +54,17 @@ class KeyguardEsimArea extends Button implements View.OnClickListener {
if (ACTION_DISABLE_ESIM.equals(intent.getAction())) {
int resultCode = getResultCode();
if (resultCode != EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK) {
- // TODO (b/62680294): Surface more info. to the end users for this failure.
Log.e(TAG, "Error disabling esim, result code = " + resultCode);
+ AlertDialog.Builder builder =
+ new AlertDialog.Builder(mContext)
+ .setMessage(R.string.error_disable_esim_msg)
+ .setTitle(R.string.error_disable_esim_title)
+ .setCancelable(false /* cancelable */)
+ .setNeutralButton(R.string.ok, null /* listener */);
+ AlertDialog alertDialog = builder.create();
+ alertDialog.getWindow().setType(
+ WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+ alertDialog.show();
}
}
}
@@ -101,14 +114,13 @@ class KeyguardEsimArea extends Button implements View.OnClickListener {
@Override
public void onClick(View v) {
- Intent intent = new Intent(mContext, KeyguardEsimArea.class);
- intent.setAction(ACTION_DISABLE_ESIM);
+ Intent intent = new Intent(ACTION_DISABLE_ESIM);
intent.setPackage(mContext.getPackageName());
- PendingIntent callbackIntent = PendingIntent.getBroadcast(
+ PendingIntent callbackIntent = PendingIntent.getBroadcastAsUser(
mContext,
0 /* requestCode */,
intent,
- PendingIntent.FLAG_UPDATE_CURRENT);
+ PendingIntent.FLAG_UPDATE_CURRENT, UserHandle.SYSTEM);
mEuiccManager
.switchToSubscription(SubscriptionManager.INVALID_SUBSCRIPTION_ID, callbackIntent);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
index 27a3f7d44890..f1a5ca9fba93 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
@@ -34,6 +34,7 @@ import android.widget.FrameLayout;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityContainer.SecurityCallback;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.settingslib.Utils;
import java.io.File;
@@ -171,10 +172,14 @@ public class KeyguardHostView extends FrameLayout implements SecurityCallback {
mSecurityContainer.showPromptReason(reason);
}
- public void showMessage(String message, int color) {
+ public void showMessage(CharSequence message, int color) {
mSecurityContainer.showMessage(message, color);
}
+ public void showErrorMessage(CharSequence message) {
+ showMessage(message, Utils.getColorError(mContext));
+ }
+
/**
* Dismisses the keyguard by going to the next screen or making it gone.
* @param targetUserId a user that needs to be the foreground user at the dismissal completion.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index b6184a883210..ff5f5e77b3f9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -117,7 +117,7 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView
}
@Override
- protected int getPromtReasonStringRes(int reason) {
+ protected int getPromptReasonStringRes(int reason) {
switch (reason) {
case PROMPT_REASON_RESTART:
return R.string.kg_prompt_reason_restart_password;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
index 3c9a6b9dcdec..cb066a10a9c9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
@@ -15,8 +15,8 @@
*/
package com.android.keyguard;
-import static com.android.keyguard.LatencyTracker.ACTION_CHECK_CREDENTIAL;
-import static com.android.keyguard.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED;
+import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL;
+import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED;
import android.content.Context;
import android.graphics.Rect;
@@ -33,6 +33,7 @@ import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.LinearLayout;
+import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternChecker;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockPatternView;
@@ -332,8 +333,9 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit
@Override
public void onTick(long millisUntilFinished) {
final int secondsRemaining = (int) Math.round(millisUntilFinished / 1000.0);
- mSecurityMessageDisplay.formatMessage(
- R.string.kg_too_many_failed_attempts_countdown, secondsRemaining);
+ mSecurityMessageDisplay.setMessage(mContext.getResources().getQuantityString(
+ R.plurals.kg_too_many_failed_attempts_countdown,
+ secondsRemaining, secondsRemaining));
}
@Override
@@ -396,7 +398,7 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit
}
@Override
- public void showMessage(String message, int color) {
+ public void showMessage(CharSequence message, int color) {
mSecurityMessageDisplay.setNextMessageColor(color);
mSecurityMessageDisplay.setMessage(message);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index c04ae68dab36..6539ccffc61b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -103,7 +103,7 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView
}
@Override
- protected int getPromtReasonStringRes(int reason) {
+ protected int getPromptReasonStringRes(int reason) {
switch (reason) {
case PROMPT_REASON_RESTART:
return R.string.kg_prompt_reason_restart_pin;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 27bc599f7f52..8dc4609f1b9d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -49,6 +49,7 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
private boolean mIsVerifyUnlockOnly;
private SecurityMode mCurrentSecuritySelection = SecurityMode.Invalid;
private SecurityCallback mSecurityCallback;
+ private AlertDialog mAlertDialog;
private final KeyguardUpdateMonitor mUpdateMonitor;
@@ -95,6 +96,10 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
@Override
public void onPause() {
+ if (mAlertDialog != null) {
+ mAlertDialog.dismiss();
+ mAlertDialog = null;
+ }
if (mCurrentSecuritySelection != SecurityMode.None) {
getSecurityView(mCurrentSecuritySelection).onPause();
}
@@ -174,16 +179,20 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
}
private void showDialog(String title, String message) {
- final AlertDialog dialog = new AlertDialog.Builder(mContext)
+ if (mAlertDialog != null) {
+ mAlertDialog.dismiss();
+ }
+
+ mAlertDialog = new AlertDialog.Builder(mContext)
.setTitle(title)
.setMessage(message)
.setCancelable(false)
.setNeutralButton(R.string.ok, null)
.create();
if (!(mContext instanceof Activity)) {
- dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+ mAlertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
}
- dialog.show();
+ mAlertDialog.show();
}
private void showTimeoutDialog(int userId, int timeoutMs) {
@@ -534,8 +543,7 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
}
}
-
- public void showMessage(String message, int color) {
+ public void showMessage(CharSequence message, int color) {
if (mCurrentSecuritySelection != SecurityMode.None) {
getSecurityView(mCurrentSecuritySelection).showMessage(message, color);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
index 829084202f5a..360dba3bc0ea 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
@@ -106,7 +106,7 @@ public interface KeyguardSecurityView {
* @param message the message to show
* @param color the color to use
*/
- void showMessage(String message, int color);
+ void showMessage(CharSequence message, int color);
/**
* Instruct the view to show usability hints, if any.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
index 6012c4501412..a2ff8f7896aa 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
@@ -139,7 +139,7 @@ public class KeyguardSecurityViewFlipper extends ViewFlipper implements Keyguard
}
@Override
- public void showMessage(String message, int color) {
+ public void showMessage(CharSequence message, int color) {
KeyguardSecurityView ksv = getSecurityView();
if (ksv != null) {
ksv.showMessage(message, color);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
index 6e0b56e25a3f..703b20531390 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
@@ -131,7 +131,7 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView {
}
if (isEsimLocked) {
- msg = msg + " " + rez.getString(R.string.kg_sim_lock_instructions_esim);
+ msg = rez.getString(R.string.kg_sim_lock_esim_instructions, msg);
}
mSecurityMessageDisplay.setMessage(msg);
@@ -168,7 +168,7 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView {
}
@Override
- protected int getPromtReasonStringRes(int reason) {
+ protected int getPromptReasonStringRes(int reason) {
// No message on SIM Pin
return 0;
}
@@ -187,6 +187,10 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView {
msgId = isDefault ? R.string.kg_sim_pin_instructions : R.string.kg_password_pin_failed;
displayMessage = getContext().getString(msgId);
}
+ if (KeyguardEsimArea.isEsimLocked(mContext, mSubId)) {
+ displayMessage = getResources()
+ .getString(R.string.kg_sim_lock_esim_instructions, displayMessage);
+ }
if (DEBUG) Log.d(LOG_TAG, "getPinPasswordErrorMessage:"
+ " attemptsRemaining=" + attemptsRemaining + " displayMessage=" + displayMessage);
return displayMessage;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
index 876d170e08cd..347c9792ec95 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
@@ -181,7 +181,7 @@ public class KeyguardSimPukView extends KeyguardPinBasedInputView {
}
}
if (isEsimLocked) {
- msg = msg + " " + rez.getString(R.string.kg_sim_lock_instructions_esim);
+ msg = rez.getString(R.string.kg_sim_lock_esim_instructions, msg);
}
mSecurityMessageDisplay.setMessage(msg);
mSimImageView.setImageTintList(ColorStateList.valueOf(color));
@@ -211,7 +211,7 @@ public class KeyguardSimPukView extends KeyguardPinBasedInputView {
}
@Override
- protected int getPromtReasonStringRes(int reason) {
+ protected int getPromptReasonStringRes(int reason) {
// No message on SIM Puk
return 0;
}
@@ -231,6 +231,10 @@ public class KeyguardSimPukView extends KeyguardPinBasedInputView {
R.string.kg_password_puk_failed;
displayMessage = getContext().getString(msgId);
}
+ if (KeyguardEsimArea.isEsimLocked(mContext, mSubId)) {
+ displayMessage = getResources()
+ .getString(R.string.kg_sim_lock_esim_instructions, displayMessage);
+ }
if (DEBUG) Log.d(LOG_TAG, "getPukPasswordErrorMessage:"
+ " attemptsRemaining=" + attemptsRemaining + " displayMessage=" + displayMessage);
return displayMessage;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
new file mode 100644
index 000000000000..5d1bdabeeac8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -0,0 +1,332 @@
+/*
+ * 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.keyguard;
+
+import android.app.PendingIntent;
+import android.arch.lifecycle.LiveData;
+import android.arch.lifecycle.Observer;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.provider.Settings;
+import android.text.Layout;
+import android.text.TextUtils;
+import android.text.TextUtils.TruncateAt;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.internal.graphics.ColorUtils;
+import com.android.settingslib.Utils;
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+import com.android.systemui.keyguard.KeyguardSliceProvider;
+import com.android.systemui.tuner.TunerService;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.function.Consumer;
+
+import androidx.app.slice.Slice;
+import androidx.app.slice.SliceItem;
+import androidx.app.slice.core.SliceQuery;
+import androidx.app.slice.widget.ListContent;
+import androidx.app.slice.widget.RowContent;
+import androidx.app.slice.widget.SliceLiveData;
+
+/**
+ * View visible under the clock on the lock screen and AoD.
+ */
+public class KeyguardSliceView extends LinearLayout implements View.OnClickListener,
+ Observer<Slice>, TunerService.Tunable {
+
+ private static final String TAG = "KeyguardSliceView";
+ private final HashMap<View, PendingIntent> mClickActions;
+ private Uri mKeyguardSliceUri;
+ private TextView mTitle;
+ private LinearLayout mRow;
+ private int mTextColor;
+ private float mDarkAmount = 0;
+
+ private LiveData<Slice> mLiveData;
+ private int mIconSize;
+ private Consumer<Boolean> mListener;
+ private boolean mHasHeader;
+
+ public KeyguardSliceView(Context context) {
+ this(context, null, 0);
+ }
+
+ public KeyguardSliceView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public KeyguardSliceView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ TunerService tunerService = Dependency.get(TunerService.class);
+ tunerService.addTunable(this, Settings.Secure.KEYGUARD_SLICE_URI);
+
+ mClickActions = new HashMap<>();
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mTitle = findViewById(R.id.title);
+ mRow = findViewById(R.id.row);
+ mTextColor = Utils.getColorAttr(mContext, R.attr.wallpaperTextColor);
+ mIconSize = (int) mContext.getResources().getDimension(R.dimen.widget_icon_size);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ // Make sure we always have the most current slice
+ mLiveData.observeForever(this);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+
+ mLiveData.removeObserver(this);
+ }
+
+ private void showSlice(Slice slice) {
+
+ ListContent lc = new ListContent(slice);
+ mHasHeader = lc.hasHeader();
+ List<SliceItem> subItems = lc.getRowItems();
+ if (!mHasHeader) {
+ mTitle.setVisibility(GONE);
+ } else {
+ mTitle.setVisibility(VISIBLE);
+ // If there's a header it'll be the first subitem
+ RowContent header = new RowContent(subItems.get(0), true /* showStartItem */);
+ SliceItem mainTitle = header.getTitleItem();
+ CharSequence title = mainTitle != null ? mainTitle.getText() : null;
+ mTitle.setText(title);
+
+ // Check if we're already ellipsizing the text.
+ // We're going to figure out the best possible line break if not.
+ Layout layout = mTitle.getLayout();
+ if (layout != null){
+ final int lineCount = layout.getLineCount();
+ if (lineCount > 0) {
+ if (layout.getEllipsisCount(lineCount - 1) == 0) {
+ mTitle.setText(findBestLineBreak(title));
+ }
+ }
+ }
+ }
+
+ mClickActions.clear();
+ final int subItemsCount = subItems.size();
+ final int blendedColor = getTextColor();
+ final int startIndex = mHasHeader ? 1 : 0; // First item is header; skip it
+ for (int i = startIndex; i < subItemsCount; i++) {
+ SliceItem item = subItems.get(i);
+ RowContent rc = new RowContent(item, true /* showStartItem */);
+ final Uri itemTag = item.getSlice().getUri();
+ // Try to reuse the view if already exists in the layout
+ KeyguardSliceButton button = mRow.findViewWithTag(itemTag);
+ if (button == null) {
+ button = new KeyguardSliceButton(mContext);
+ button.setTextColor(blendedColor);
+ button.setTag(itemTag);
+ } else {
+ mRow.removeView(button);
+ }
+ mRow.addView(button);
+
+ PendingIntent pendingIntent = null;
+ if (rc.getContentIntent() != null) {
+ pendingIntent = rc.getContentIntent().getAction();
+ }
+ mClickActions.put(button, pendingIntent);
+
+ button.setText(rc.getTitleItem().getText());
+
+ Drawable iconDrawable = null;
+ SliceItem icon = SliceQuery.find(item.getSlice(),
+ android.app.slice.SliceItem.FORMAT_IMAGE);
+ if (icon != null) {
+ iconDrawable = icon.getIcon().loadDrawable(mContext);
+ final int width = (int) (iconDrawable.getIntrinsicWidth()
+ / (float) iconDrawable.getIntrinsicHeight() * mIconSize);
+ iconDrawable.setBounds(0, 0, Math.max(width, 1), mIconSize);
+ }
+ button.setCompoundDrawablesRelative(iconDrawable, null, null, null);
+ button.setOnClickListener(this);
+ }
+
+ // Removing old views
+ for (int i = 0; i < mRow.getChildCount(); i++) {
+ View child = mRow.getChildAt(i);
+ if (!mClickActions.containsKey(child)) {
+ mRow.removeView(child);
+ i--;
+ }
+ }
+
+ final int visibility = mHasHeader || subItemsCount > 0 ? VISIBLE : GONE;
+ if (visibility != getVisibility()) {
+ setVisibility(visibility);
+ }
+
+ mListener.accept(mHasHeader);
+ }
+
+ /**
+ * Breaks a string in 2 lines where both have similar character count
+ * but first line is always longer.
+ *
+ * @param charSequence Original text.
+ * @return Optimal string.
+ */
+ private CharSequence findBestLineBreak(CharSequence charSequence) {
+ if (TextUtils.isEmpty(charSequence)) {
+ return charSequence;
+ }
+
+ String source = charSequence.toString();
+ // Ignore if there is only 1 word,
+ // or if line breaks were manually set.
+ if (source.contains("\n") || !source.contains(" ")) {
+ return source;
+ }
+
+ final String[] words = source.split(" ");
+ final StringBuilder optimalString = new StringBuilder(source.length());
+ int current = 0;
+ while (optimalString.length() < source.length() - optimalString.length()) {
+ optimalString.append(words[current]);
+ if (current < words.length - 1) {
+ optimalString.append(" ");
+ }
+ current++;
+ }
+ optimalString.append("\n");
+ for (int i = current; i < words.length; i++) {
+ optimalString.append(words[i]);
+ if (current < words.length - 1) {
+ optimalString.append(" ");
+ }
+ }
+
+ return optimalString.toString();
+ }
+
+ public void setDark(float darkAmount) {
+ mDarkAmount = darkAmount;
+ updateTextColors();
+ }
+
+ private void updateTextColors() {
+ final int blendedColor = getTextColor();
+ mTitle.setTextColor(blendedColor);
+ int childCount = mRow.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View v = mRow.getChildAt(i);
+ if (v instanceof Button) {
+ ((Button) v).setTextColor(blendedColor);
+ }
+ }
+ }
+
+ @Override
+ public void onClick(View v) {
+ final PendingIntent action = mClickActions.get(v);
+ if (action != null) {
+ try {
+ action.send();
+ } catch (PendingIntent.CanceledException e) {
+ Log.i(TAG, "Pending intent cancelled, nothing to launch", e);
+ }
+ }
+ }
+
+ public void setListener(Consumer<Boolean> listener) {
+ mListener = listener;
+ }
+
+ public boolean hasHeader() {
+ return mHasHeader;
+ }
+
+ /**
+ * LiveData observer lifecycle.
+ * @param slice the new slice content.
+ */
+ @Override
+ public void onChanged(Slice slice) {
+ showSlice(slice);
+ }
+
+ @Override
+ public void onTuningChanged(String key, String newValue) {
+ setupUri(newValue);
+ }
+
+ public void setupUri(String uriString) {
+ if (uriString == null) {
+ uriString = KeyguardSliceProvider.KEYGUARD_SLICE_URI;
+ }
+
+ boolean wasObserving = false;
+ if (mLiveData != null && mLiveData.hasActiveObservers()) {
+ wasObserving = true;
+ mLiveData.removeObserver(this);
+ }
+
+ mKeyguardSliceUri = Uri.parse(uriString);
+ mLiveData = SliceLiveData.fromUri(mContext, mKeyguardSliceUri);
+
+ if (wasObserving) {
+ mLiveData.observeForever(this);
+ }
+ }
+
+ public int getTextColor() {
+ return ColorUtils.blendARGB(mTextColor, Color.WHITE, mDarkAmount);
+ }
+
+ /**
+ * Representation of an item that appears under the clock on main keyguard message.
+ */
+ private class KeyguardSliceButton extends Button {
+
+ public KeyguardSliceButton(Context context) {
+ super(context, null /* attrs */, 0 /* styleAttr */,
+ com.android.keyguard.R.style.TextAppearance_Keyguard_Secondary);
+ int horizontalPadding = (int) context.getResources()
+ .getDimension(R.dimen.widget_horizontal_padding);
+ setPadding(horizontalPadding / 2, 0, horizontalPadding / 2, 0);
+ setCompoundDrawablePadding((int) context.getResources()
+ .getDimension(R.dimen.widget_icon_padding));
+ setMaxWidth(KeyguardSliceView.this.getWidth() / 2);
+ setMaxLines(1);
+ setEllipsize(TruncateAt.END);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index bc2a59df42a6..e440731dcd47 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -16,20 +16,18 @@
package com.android.keyguard;
-import android.app.ActivityManager;
import android.app.AlarmManager;
import android.content.Context;
-import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
-import android.graphics.PorterDuff;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
import android.support.v4.graphics.ColorUtils;
import android.text.TextUtils;
import android.text.format.DateFormat;
+import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Slog;
@@ -40,10 +38,9 @@ import android.widget.GridLayout;
import android.widget.TextClock;
import android.widget.TextView;
-import com.android.internal.util.ArrayUtils;
import com.android.internal.widget.LockPatternUtils;
-import com.android.systemui.ChargingView;
-import com.android.systemui.statusbar.policy.DateView;
+
+import com.google.android.collect.Sets;
import java.util.Locale;
@@ -54,23 +51,21 @@ public class KeyguardStatusView extends GridLayout {
private final LockPatternUtils mLockPatternUtils;
private final AlarmManager mAlarmManager;
+ private final float mSmallClockScale;
+ private final float mWidgetPadding;
- private TextView mAlarmStatusView;
- private DateView mDateView;
private TextClock mClockView;
+ private View mClockSeparator;
private TextView mOwnerInfo;
private ViewGroup mClockContainer;
- private ChargingView mBatteryDoze;
- private View mKeyguardStatusArea;
+ private KeyguardSliceView mKeyguardSlice;
private Runnable mPendingMarqueeStart;
private Handler mHandler;
- private View[] mVisibleInDoze;
+ private ArraySet<View> mVisibleInDoze;
private boolean mPulsing;
private float mDarkAmount = 0;
private int mTextColor;
- private int mDateTextColor;
- private int mAlarmTextColor;
private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
@@ -118,6 +113,9 @@ public class KeyguardStatusView extends GridLayout {
mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
mLockPatternUtils = new LockPatternUtils(getContext());
mHandler = new Handler(Looper.myLooper());
+ mSmallClockScale = getResources().getDimension(R.dimen.widget_small_font_size)
+ / getResources().getDimension(R.dimen.widget_big_font_size);
+ mWidgetPadding = getResources().getDimension(R.dimen.widget_vertical_padding);
}
private void setEnableMarquee(boolean enabled) {
@@ -141,7 +139,6 @@ public class KeyguardStatusView extends GridLayout {
private void setEnableMarqueeImpl(boolean enabled) {
if (DEBUG) Log.v(TAG, (enabled ? "Enable" : "Disable") + " transport text marquee");
- if (mAlarmStatusView != null) mAlarmStatusView.setSelected(enabled);
if (mOwnerInfo != null) mOwnerInfo.setSelected(enabled);
}
@@ -149,20 +146,19 @@ public class KeyguardStatusView extends GridLayout {
protected void onFinishInflate() {
super.onFinishInflate();
mClockContainer = findViewById(R.id.keyguard_clock_container);
- mAlarmStatusView = findViewById(R.id.alarm_status);
- mDateView = findViewById(R.id.date_view);
mClockView = findViewById(R.id.clock_view);
mClockView.setShowCurrentUserTime(true);
if (KeyguardClockAccessibilityDelegate.isNeeded(mContext)) {
mClockView.setAccessibilityDelegate(new KeyguardClockAccessibilityDelegate(mContext));
}
mOwnerInfo = findViewById(R.id.owner_info);
- mBatteryDoze = findViewById(R.id.battery_doze);
- mKeyguardStatusArea = findViewById(R.id.keyguard_status_area);
- mVisibleInDoze = new View[]{mBatteryDoze, mClockView, mKeyguardStatusArea};
+ mKeyguardSlice = findViewById(R.id.keyguard_status_area);
+ mClockSeparator = findViewById(R.id.clock_separator);
+ mVisibleInDoze = Sets.newArraySet(mClockView, mKeyguardSlice, mClockSeparator);
mTextColor = mClockView.getCurrentTextColor();
- mDateTextColor = mDateView.getCurrentTextColor();
- mAlarmTextColor = mAlarmStatusView.getCurrentTextColor();
+
+ mKeyguardSlice.setListener(this::onSliceContentChanged);
+ onSliceContentChanged(mKeyguardSlice.hasHeader());
boolean shouldMarquee = KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive();
setEnableMarquee(shouldMarquee);
@@ -174,6 +170,19 @@ public class KeyguardStatusView extends GridLayout {
mClockView.setElegantTextHeight(false);
}
+ private void onSliceContentChanged(boolean hasHeader) {
+ final boolean smallClock = hasHeader || mPulsing;
+ final float clockScale = smallClock ? mSmallClockScale : 1;
+ float translation = (mClockView.getHeight() - (mClockView.getHeight() * clockScale)) / 2f;
+ if (smallClock) {
+ translation -= mWidgetPadding;
+ }
+ mClockView.setTranslationY(translation);
+ mClockView.setScaleX(clockScale);
+ mClockView.setScaleY(clockScale);
+ mClockSeparator.setVisibility(hasHeader && !mPulsing ? VISIBLE : GONE);
+ }
+
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
@@ -184,8 +193,6 @@ public class KeyguardStatusView extends GridLayout {
layoutParams.bottomMargin = getResources().getDimensionPixelSize(
R.dimen.bottom_text_spacing_digital);
mClockView.setLayoutParams(layoutParams);
- mDateView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
- getResources().getDimensionPixelSize(R.dimen.widget_label_font_size));
if (mOwnerInfo != null) {
mOwnerInfo.setTextSize(TypedValue.COMPLEX_UNIT_PX,
getResources().getDimensionPixelSize(R.dimen.widget_label_font_size));
@@ -193,8 +200,6 @@ public class KeyguardStatusView extends GridLayout {
}
public void refreshTime() {
- mDateView.setDatePattern(Patterns.dateViewSkel);
-
mClockView.setFormat12Hour(Patterns.clockView12);
mClockView.setFormat24Hour(Patterns.clockView24);
}
@@ -205,40 +210,17 @@ public class KeyguardStatusView extends GridLayout {
Patterns.update(mContext, nextAlarm != null);
refreshTime();
- refreshAlarmStatus(nextAlarm);
- }
-
- void refreshAlarmStatus(AlarmManager.AlarmClockInfo nextAlarm) {
- if (nextAlarm != null) {
- String alarm = formatNextAlarm(mContext, nextAlarm);
- mAlarmStatusView.setText(alarm);
- mAlarmStatusView.setContentDescription(
- getResources().getString(R.string.keyguard_accessibility_next_alarm, alarm));
- mAlarmStatusView.setVisibility(View.VISIBLE);
- } else {
- mAlarmStatusView.setVisibility(View.GONE);
- }
}
public int getClockBottom() {
- return mKeyguardStatusArea.getBottom();
+ return mKeyguardSlice.getVisibility() == VISIBLE ? mKeyguardSlice.getBottom()
+ : mClockView.getBottom();
}
public float getClockTextSize() {
return mClockView.getTextSize();
}
- public static String formatNextAlarm(Context context, AlarmManager.AlarmClockInfo info) {
- if (info == null) {
- return "";
- }
- String skeleton = DateFormat.is24HourFormat(context, ActivityManager.getCurrentUser())
- ? "EHm"
- : "Ehma";
- String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton);
- return DateFormat.format(pattern, info.getTriggerTime()).toString();
- }
-
private void updateOwnerInfo() {
if (mOwnerInfo == null) return;
String ownerInfo = getOwnerInfo();
@@ -320,7 +302,7 @@ public class KeyguardStatusView extends GridLayout {
}
}
- public void setDark(float darkAmount) {
+ public void setDarkAmount(float darkAmount) {
if (mDarkAmount == darkAmount) {
return;
}
@@ -330,7 +312,7 @@ public class KeyguardStatusView extends GridLayout {
final int N = mClockContainer.getChildCount();
for (int i = 0; i < N; i++) {
View child = mClockContainer.getChildAt(i);
- if (ArrayUtils.contains(mVisibleInDoze, child)) {
+ if (mVisibleInDoze.contains(child)) {
continue;
}
child.setAlpha(dark ? 0 : 1);
@@ -339,17 +321,17 @@ public class KeyguardStatusView extends GridLayout {
mOwnerInfo.setAlpha(dark ? 0 : 1);
}
+ final int blendedTextColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, darkAmount);
updateDozeVisibleViews();
- mBatteryDoze.setDark(dark);
- mClockView.setTextColor(ColorUtils.blendARGB(mTextColor, Color.WHITE, darkAmount));
- mDateView.setTextColor(ColorUtils.blendARGB(mDateTextColor, Color.WHITE, darkAmount));
- int blendedAlarmColor = ColorUtils.blendARGB(mAlarmTextColor, Color.WHITE, darkAmount);
- mAlarmStatusView.setTextColor(blendedAlarmColor);
- mAlarmStatusView.setCompoundDrawableTintList(ColorStateList.valueOf(blendedAlarmColor));
+ mKeyguardSlice.setDark(darkAmount);
+ mClockView.setTextColor(blendedTextColor);
+ mClockSeparator.setBackgroundColor(blendedTextColor);
}
public void setPulsing(boolean pulsing) {
mPulsing = pulsing;
+ mKeyguardSlice.setVisibility(pulsing ? INVISIBLE : VISIBLE);
+ onSliceContentChanged(mKeyguardSlice.hasHeader());
updateDozeVisibleViews();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 3a5d137d6d11..9e4b4055a289 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -16,6 +16,8 @@
package com.android.keyguard;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.Intent.ACTION_USER_UNLOCKED;
import static android.os.BatteryManager.BATTERY_HEALTH_UNKNOWN;
import static android.os.BatteryManager.BATTERY_STATUS_FULL;
@@ -50,7 +52,6 @@ import android.media.AudioManager;
import android.os.BatteryManager;
import android.os.CancellationSignal;
import android.os.Handler;
-import android.os.IBinder;
import android.os.IRemoteCallback;
import android.os.Message;
import android.os.RemoteException;
@@ -70,13 +71,15 @@ import android.util.Log;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.IccCardConstants;
import com.android.internal.telephony.IccCardConstants.State;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.widget.LockPatternUtils;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.google.android.collect.Lists;
@@ -345,6 +348,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
private SparseBooleanArray mUserFaceUnlockRunning = new SparseBooleanArray();
private static int sCurrentUser;
+ private Runnable mUpdateFingerprintListeningState = this::updateFingerprintListeningState;
public synchronized static void setCurrentUser(int currentUser) {
sCurrentUser = currentUser;
@@ -368,6 +372,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
}
}
+ @Override
+ public void onTrustError(CharSequence message) {
+ dispatchErrorMessage(message);
+ }
+
protected void handleSimSubscriptionInfoChanged() {
if (DEBUG_SIM_STATES) {
Log.v(TAG, "onSubscriptionInfoChanged()");
@@ -452,6 +461,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
*/
public void setKeyguardGoingAway(boolean goingAway) {
mKeyguardGoingAway = goingAway;
+ updateFingerprintListeningState();
}
/**
@@ -701,6 +711,15 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
return mScreenOn;
}
+ private void dispatchErrorMessage(CharSequence message) {
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onTrustAgentErrorMessage(message);
+ }
+ }
+ }
+
static class DisplayClientState {
public int clientGeneration;
public boolean clearing;
@@ -972,6 +991,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
maxChargingWattage > fastThreshold ? CHARGING_FAST :
CHARGING_REGULAR;
}
+
+ @Override
+ public String toString() {
+ return "BatteryStatus{status=" + status + ",level=" + level + ",plugged=" + plugged
+ + ",health=" + health + ",maxChargingWattage=" + maxChargingWattage + "}";
+ }
}
public class StrongAuthTracker extends LockPatternUtils.StrongAuthTracker {
@@ -1107,7 +1132,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
}
}
- private KeyguardUpdateMonitor(Context context) {
+ @VisibleForTesting
+ protected KeyguardUpdateMonitor(Context context) {
mContext = context;
mSubscriptionManager = SubscriptionManager.from(context);
mDeviceProvisioned = isDeviceProvisionedInSettingsDb();
@@ -1185,7 +1211,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
mFpm.addLockoutResetCallback(mLockoutResetCallback);
}
- SystemServicesProxy.getInstance(mContext).registerTaskStackListener(mTaskStackListener);
+ ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
mUserManager = context.getSystemService(UserManager.class);
}
@@ -1231,7 +1257,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
mFingerprintCancelSignal.cancel();
}
mFingerprintCancelSignal = new CancellationSignal();
- mFpm.authenticate(null, mFingerprintCancelSignal, 0, mAuthenticationCallback, null, userId);
+ mFpm.authenticate(null, mFingerprintCancelSignal, 0, mAuthenticationCallback, null,
+ userId);
setFingerprintRunningState(FINGERPRINT_STATE_RUNNING);
}
}
@@ -1593,11 +1620,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
}
}
- private static boolean isBatteryUpdateInteresting(BatteryStatus old, BatteryStatus current) {
+ private boolean isBatteryUpdateInteresting(BatteryStatus old, BatteryStatus current) {
final boolean nowPluggedIn = current.isPluggedIn();
final boolean wasPluggedIn = old.isPluggedIn();
- final boolean stateChangedWhilePluggedIn =
- wasPluggedIn == true && nowPluggedIn == true
+ final boolean stateChangedWhilePluggedIn = wasPluggedIn && nowPluggedIn
&& (old.status != current.status);
// change in plug state is always interesting
@@ -1605,13 +1631,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
return true;
}
- // change in battery level while plugged in
- if (nowPluggedIn && old.level != current.level) {
- return true;
- }
-
- // change where battery needs charging
- if (!nowPluggedIn && current.isBatteryLow() && current.level != old.level) {
+ // change in battery level
+ if (old.level != current.level) {
return true;
}
@@ -1663,7 +1684,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
public void setSwitchingUser(boolean switching) {
mSwitchingUser = switching;
- updateFingerprintListeningState();
+ // Since this comes in on a binder thread, we need to post if first
+ mHandler.post(mUpdateFingerprintListeningState);
}
private void sendUpdates(KeyguardUpdateMonitorCallback callback) {
@@ -1736,6 +1758,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
mFailedAttempts.delete(sCurrentUser);
}
+ public ServiceState getServiceState(int subId) {
+ return mServiceStates.get(subId);
+ }
+
public int getFailedUnlockAttempts(int userId) {
return mFailedAttempts.get(userId, 0);
}
@@ -1770,12 +1796,13 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
}
}
- private final TaskStackListener mTaskStackListener = new TaskStackListener() {
+ private final SysUiTaskStackChangeListener
+ mTaskStackListener = new SysUiTaskStackChangeListener() {
@Override
public void onTaskStackChangedBackground() {
try {
ActivityManager.StackInfo info = ActivityManager.getService().getStackInfo(
- ActivityManager.StackId.ASSISTANT_STACK_ID);
+ WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_ASSISTANT);
if (info == null) {
return;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 5a02178a5a7b..1afcca64de69 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -17,13 +17,14 @@ package com.android.keyguard;
import android.app.admin.DevicePolicyManager;
import android.graphics.Bitmap;
+import android.hardware.fingerprint.FingerprintManager;
import android.media.AudioManager;
import android.os.SystemClock;
-import android.hardware.fingerprint.FingerprintManager;
import android.telephony.TelephonyManager;
-import android.view.WindowManagerPolicy;
+import android.view.WindowManagerPolicyConstants;
import com.android.internal.telephony.IccCardConstants;
+import com.android.systemui.statusbar.KeyguardIndicationController;
/**
* Callback for general information relevant to lock screen.
@@ -171,9 +172,9 @@ public class KeyguardUpdateMonitorCallback {
/**
* Called when the device has finished going to sleep.
- * @param why either {@link WindowManagerPolicy#OFF_BECAUSE_OF_ADMIN},
- * {@link WindowManagerPolicy#OFF_BECAUSE_OF_USER}, or
- * {@link WindowManagerPolicy#OFF_BECAUSE_OF_TIMEOUT}.
+ * @param why either {@link WindowManagerPolicyConstants#OFF_BECAUSE_OF_ADMIN},
+ * {@link WindowManagerPolicyConstants#OFF_BECAUSE_OF_USER}, or
+ * {@link WindowManagerPolicyConstants#OFF_BECAUSE_OF_TIMEOUT}.
*
* @deprecated use {@link com.android.systemui.keyguard.WakefulnessLifecycle}.
*/
@@ -271,4 +272,15 @@ public class KeyguardUpdateMonitorCallback {
* @param dreaming true if the dream's window has been created and is visible
*/
public void onDreamingStateChanged(boolean dreaming) { }
+
+ /**
+ * Called when an error message needs to be presented on the keyguard.
+ * Message will be visible briefly, and might be overridden by other keyguard events,
+ * like fingerprint authentication errors.
+ *
+ * @param message Message that indicates an error.
+ * @see KeyguardIndicationController.BaseKeyguardCallback#HIDE_DELAY_MS
+ * @see KeyguardIndicationController#showTransientIndication(CharSequence)
+ */
+ public void onTrustAgentErrorMessage(CharSequence message) { }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/LatencyTracker.java b/packages/SystemUI/src/com/android/keyguard/LatencyTracker.java
deleted file mode 100644
index cee0afcd37d6..000000000000
--- a/packages/SystemUI/src/com/android/keyguard/LatencyTracker.java
+++ /dev/null
@@ -1,150 +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.keyguard;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Build;
-import android.os.SystemClock;
-import android.os.SystemProperties;
-import android.os.Trace;
-import android.util.EventLog;
-import android.util.Log;
-import android.util.SparseLongArray;
-
-import com.android.systemui.EventLogTags;
-
-/**
- * Class to track various latencies in SystemUI. It then outputs the latency to logcat so these
- * latencies can be captured by tests and then used for dashboards.
- * <p>
- * This is currently only in Keyguard so it can be shared between SystemUI and Keyguard, but
- * eventually we'd want to merge these two packages together so Keyguard can use common classes
- * that are shared with SystemUI.
- */
-public class LatencyTracker {
-
- private static final String ACTION_RELOAD_PROPERTY =
- "com.android.systemui.RELOAD_LATENCY_TRACKER_PROPERTY";
-
- private static final String TAG = "LatencyTracker";
-
- /**
- * Time it takes until the first frame of the notification panel to be displayed while expanding
- */
- public static final int ACTION_EXPAND_PANEL = 0;
-
- /**
- * Time it takes until the first frame of recents is drawn after invoking it with the button.
- */
- public static final int ACTION_TOGGLE_RECENTS = 1;
-
- /**
- * Time between we get a fingerprint acquired signal until we start with the unlock animation
- */
- public static final int ACTION_FINGERPRINT_WAKE_AND_UNLOCK = 2;
-
- /**
- * Time it takes to check PIN/Pattern/Password.
- */
- public static final int ACTION_CHECK_CREDENTIAL = 3;
-
- /**
- * Time it takes to check fully PIN/Pattern/Password, i.e. that's the time spent including the
- * actions to unlock a user.
- */
- public static final int ACTION_CHECK_CREDENTIAL_UNLOCKED = 4;
-
- /**
- * Time it takes to turn on the screen.
- */
- public static final int ACTION_TURN_ON_SCREEN = 5;
-
- private static final String[] NAMES = new String[] {
- "expand panel",
- "toggle recents",
- "fingerprint wake-and-unlock",
- "check credential",
- "check credential unlocked",
- "turn on screen" };
-
- private static LatencyTracker sLatencyTracker;
-
- private final SparseLongArray mStartRtc = new SparseLongArray();
- private boolean mEnabled;
-
- public static LatencyTracker getInstance(Context context) {
- if (sLatencyTracker == null) {
- sLatencyTracker = new LatencyTracker(context);
- }
- return sLatencyTracker;
- }
-
- private LatencyTracker(Context context) {
- context.registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- reloadProperty();
- }
- }, new IntentFilter(ACTION_RELOAD_PROPERTY));
- reloadProperty();
- }
-
- private void reloadProperty() {
- mEnabled = SystemProperties.getBoolean("debug.systemui.latency_tracking", false);
- }
-
- public static boolean isEnabled(Context ctx) {
- return Build.IS_DEBUGGABLE && getInstance(ctx).mEnabled;
- }
-
- /**
- * Notifies that an action is starting. This needs to be called from the main thread.
- *
- * @param action The action to start. One of the ACTION_* values.
- */
- public void onActionStart(int action) {
- if (!mEnabled) {
- return;
- }
- Trace.asyncTraceBegin(Trace.TRACE_TAG_APP, NAMES[action], 0);
- mStartRtc.put(action, SystemClock.elapsedRealtime());
- }
-
- /**
- * Notifies that an action has ended. This needs to be called from the main thread.
- *
- * @param action The action to end. One of the ACTION_* values.
- */
- public void onActionEnd(int action) {
- if (!mEnabled) {
- return;
- }
- long endRtc = SystemClock.elapsedRealtime();
- long startRtc = mStartRtc.get(action, -1);
- if (startRtc == -1) {
- return;
- }
- mStartRtc.delete(action);
- Trace.asyncTraceEnd(Trace.TRACE_TAG_APP, NAMES[action], 0);
- long duration = endRtc - startRtc;
- Log.i(TAG, "action=" + action + " latency=" + duration);
- EventLog.writeEvent(EventLogTags.SYSUI_LATENCY, action, (int) duration);
- }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
index 12f75bb2d56c..d3dded0e25b2 100644
--- a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
+++ b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
@@ -31,6 +31,7 @@ import android.os.PowerManager;
import android.os.SystemClock;
import android.provider.Settings;
import android.text.InputType;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
@@ -78,6 +79,8 @@ public class PasswordTextView extends View {
*/
private static final float OVERSHOOT_TIME_POSITION = 0.5f;
+ private static char DOT = '\u2022';
+
/**
* The raw text size, will be multiplied by the scaled density when drawn
*/
@@ -208,7 +211,7 @@ public class PasswordTextView extends View {
public void append(char c) {
int visibleChars = mTextChars.size();
- String textbefore = mText;
+ CharSequence textbefore = getTransformedText();
mText = mText + c;
int newLength = mText.length();
CharState charState;
@@ -245,7 +248,7 @@ public class PasswordTextView extends View {
public void deleteLastChar() {
int length = mText.length();
- String textbefore = mText;
+ CharSequence textbefore = getTransformedText();
if (length > 0) {
mText = mText.substring(0, length - 1);
CharState charState = mTextChars.get(length - 1);
@@ -259,6 +262,21 @@ public class PasswordTextView extends View {
return mText;
}
+ private CharSequence getTransformedText() {
+ int textLength = mTextChars.size();
+ StringBuilder stringBuilder = new StringBuilder(textLength);
+ for (int i = 0; i < textLength; i++) {
+ CharState charState = mTextChars.get(i);
+ // If the dot is disappearing, the character is disappearing entirely. Consider
+ // it gone.
+ if (charState.dotAnimator != null && !charState.dotAnimationIsGrowing) {
+ continue;
+ }
+ stringBuilder.append(charState.isCharVisibleForA11y() ? charState.whichChar : DOT);
+ }
+ return stringBuilder;
+ }
+
private CharState obtainCharState(char c) {
CharState charState;
if(mCharPool.isEmpty()) {
@@ -272,7 +290,7 @@ public class PasswordTextView extends View {
}
public void reset(boolean animated, boolean announce) {
- String textbefore = mText;
+ CharSequence textbefore = getTransformedText();
mText = "";
int length = mTextChars.size();
int middleIndex = (length - 1) / 2;
@@ -305,7 +323,7 @@ public class PasswordTextView extends View {
}
}
- void sendAccessibilityEventTypeViewTextChanged(String beforeText, int fromIndex,
+ void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, int fromIndex,
int removedCount, int addedCount) {
if (AccessibilityManager.getInstance(mContext).isEnabled() &&
(isFocused() || isSelected() && isShown())) {
@@ -315,6 +333,10 @@ public class PasswordTextView extends View {
event.setRemovedCount(removedCount);
event.setAddedCount(addedCount);
event.setBeforeText(beforeText);
+ CharSequence transformedText = getTransformedText();
+ if (!TextUtils.isEmpty(transformedText)) {
+ event.getText().add(transformedText);
+ }
event.setPassword(true);
sendAccessibilityEventUnchecked(event);
}
@@ -332,8 +354,9 @@ public class PasswordTextView extends View {
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(PasswordTextView.class.getName());
+ info.setClassName(EditText.class.getName());
info.setPassword(true);
+ info.setText(getTransformedText());
info.setEditable(true);
@@ -420,7 +443,19 @@ public class PasswordTextView extends View {
= new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
+ boolean textVisibleBefore = isCharVisibleForA11y();
+ float beforeTextSizeFactor = currentTextSizeFactor;
currentTextSizeFactor = (float) animation.getAnimatedValue();
+ if (textVisibleBefore != isCharVisibleForA11y()) {
+ currentTextSizeFactor = beforeTextSizeFactor;
+ CharSequence beforeText = getTransformedText();
+ currentTextSizeFactor = (float) animation.getAnimatedValue();
+ int indexOfThisChar = mTextChars.indexOf(CharState.this);
+ if (indexOfThisChar >= 0) {
+ sendAccessibilityEventTypeViewTextChanged(
+ beforeText, indexOfThisChar, 1, 1);
+ }
+ }
invalidate();
}
};
@@ -673,5 +708,13 @@ public class PasswordTextView extends View {
}
return charWidth + mCharPadding * currentWidthFactor;
}
+
+ public boolean isCharVisibleForA11y() {
+ // The text has size 0 when it is first added, but we want to count it as visible if
+ // it will become visible presently. Count text as visible if an animator
+ // is configured to make it grow.
+ boolean textIsGrowing = textAnimator != null && textAnimationIsGrowing;
+ return (currentTextSizeFactor > 0) || textIsGrowing;
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java b/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java
index b194de43a718..5c6812367a15 100644
--- a/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java
@@ -76,6 +76,12 @@ public interface ViewMediatorCallback {
void playTrustedSound();
/**
+ * When the bouncer is shown or hides
+ * @param shown
+ */
+ void onBouncerVisiblityChanged(boolean shown);
+
+ /**
* @return true if the screen is on
*/
boolean isScreenOn();
@@ -93,4 +99,10 @@ public interface ViewMediatorCallback {
* Invoked when the secondary display showing a keyguard window changes.
*/
void onSecondaryDisplayShowingChanged(int displayId);
+
+ /**
+ * Consumes a message that was enqueued to be displayed on the next time the bouncer shows up.
+ * @return Message that should be displayed above the challenge.
+ */
+ CharSequence consumeCustomMessage();
}
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
index 2b31967cea4f..1ae06d751255 100644
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -15,6 +15,8 @@
*/
package com.android.systemui;
+import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS;
+import static android.app.StatusBarManager.DISABLE_NONE;
import static android.provider.Settings.System.SHOW_BATTERY_PERCENT;
import android.animation.ArgbEvaluator;
@@ -52,6 +54,7 @@ import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver;
import com.android.systemui.statusbar.policy.IconLogger;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
+import com.android.systemui.util.Utils.DisableStateTracker;
import java.text.NumberFormat;
@@ -101,6 +104,9 @@ public class BatteryMeterView extends LinearLayout implements
mSettingObserver = new SettingObserver(new Handler(context.getMainLooper()));
+ addOnAttachStateChangeListener(
+ new DisableStateTracker(DISABLE_NONE, DISABLE2_SYSTEM_ICONS));
+
mSlotBattery = context.getString(
com.android.internal.R.string.status_bar_battery);
mBatteryIconView = new ImageView(context);
@@ -113,18 +119,10 @@ public class BatteryMeterView extends LinearLayout implements
addView(mBatteryIconView, mlp);
updateShowPercent();
-
- Context dualToneDarkTheme = new ContextThemeWrapper(context,
- Utils.getThemeAttr(context, R.attr.darkIconTheme));
- Context dualToneLightTheme = new ContextThemeWrapper(context,
- Utils.getThemeAttr(context, R.attr.lightIconTheme));
- mDarkModeBackgroundColor = Utils.getColorAttr(dualToneDarkTheme, R.attr.backgroundColor);
- mDarkModeFillColor = Utils.getColorAttr(dualToneDarkTheme, R.attr.fillColor);
- mLightModeBackgroundColor = Utils.getColorAttr(dualToneLightTheme, R.attr.backgroundColor);
- mLightModeFillColor = Utils.getColorAttr(dualToneLightTheme, R.attr.fillColor);
-
+ setColorsFromContext(context);
// Init to not dark at all.
onDarkChanged(new Rect(), 0, DarkIconDispatcher.DEFAULT_ICON_TINT);
+
mUserTracker = new CurrentUserTracker(mContext) {
@Override
public void onUserSwitched(int newUserId) {
@@ -142,6 +140,21 @@ public class BatteryMeterView extends LinearLayout implements
updateShowPercent();
}
+ public void setColorsFromContext(Context context) {
+ if (context == null) {
+ return;
+ }
+
+ Context dualToneDarkTheme = new ContextThemeWrapper(context,
+ Utils.getThemeAttr(context, R.attr.darkIconTheme));
+ Context dualToneLightTheme = new ContextThemeWrapper(context,
+ Utils.getThemeAttr(context, R.attr.lightIconTheme));
+ mDarkModeBackgroundColor = Utils.getColorAttr(dualToneDarkTheme, R.attr.backgroundColor);
+ mDarkModeFillColor = Utils.getColorAttr(dualToneDarkTheme, R.attr.fillColor);
+ mLightModeBackgroundColor = Utils.getColorAttr(dualToneLightTheme, R.attr.backgroundColor);
+ mLightModeFillColor = Utils.getColorAttr(dualToneLightTheme, R.attr.fillColor);
+ }
+
@Override
public boolean hasOverlappingRendering() {
return false;
@@ -218,7 +231,6 @@ public class BatteryMeterView extends LinearLayout implements
if (mTextColor != 0) mBatteryPercentView.setTextColor(mTextColor);
updatePercentText();
addView(mBatteryPercentView,
- 0,
new ViewGroup.LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.MATCH_PARENT));
diff --git a/packages/SystemUI/src/com/android/systemui/ChargingView.java b/packages/SystemUI/src/com/android/systemui/ChargingView.java
deleted file mode 100644
index 33f8b069b751..000000000000
--- a/packages/SystemUI/src/com/android/systemui/ChargingView.java
+++ /dev/null
@@ -1,126 +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;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.os.UserHandle;
-import android.util.AttributeSet;
-import android.widget.ImageView;
-
-import com.android.internal.hardware.AmbientDisplayConfiguration;
-import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-
-/**
- * A view that only shows its drawable while the phone is charging.
- *
- * Also reloads its drawable upon density changes.
- */
-public class ChargingView extends ImageView implements
- BatteryController.BatteryStateChangeCallback,
- ConfigurationController.ConfigurationListener {
-
- private static final long CHARGING_INDICATION_DELAY_MS = 1000;
-
- private final AmbientDisplayConfiguration mConfig;
- private final Runnable mClearSuppressCharging = this::clearSuppressCharging;
- private BatteryController mBatteryController;
- private int mImageResource;
- private boolean mCharging;
- private boolean mDark;
- private boolean mSuppressCharging;
-
-
- private void clearSuppressCharging() {
- mSuppressCharging = false;
- removeCallbacks(mClearSuppressCharging);
- updateVisibility();
- }
-
- public ChargingView(Context context, @Nullable AttributeSet attrs) {
- super(context, attrs);
-
- mConfig = new AmbientDisplayConfiguration(context);
-
- TypedArray a = context.obtainStyledAttributes(attrs, new int[]{android.R.attr.src});
- int srcResId = a.getResourceId(0, 0);
-
- if (srcResId != 0) {
- mImageResource = srcResId;
- }
-
- a.recycle();
-
- updateVisibility();
- }
-
- @Override
- public void onAttachedToWindow() {
- super.onAttachedToWindow();
- mBatteryController = Dependency.get(BatteryController.class);
- mBatteryController.addCallback(this);
- Dependency.get(ConfigurationController.class).addCallback(this);
- }
-
- @Override
- public void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- mBatteryController.removeCallback(this);
- Dependency.get(ConfigurationController.class).removeCallback(this);
- removeCallbacks(mClearSuppressCharging);
- }
-
- @Override
- public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
- boolean startCharging = charging && !mCharging;
- if (startCharging && deviceWillWakeUpWhenPluggedIn() && mDark) {
- // We're about to wake up, and thus don't want to show the indicator just for it to be
- // hidden again.
- clearSuppressCharging();
- mSuppressCharging = true;
- postDelayed(mClearSuppressCharging, CHARGING_INDICATION_DELAY_MS);
- }
- mCharging = charging;
- updateVisibility();
- }
-
- private boolean deviceWillWakeUpWhenPluggedIn() {
- boolean plugTurnsOnScreen = getResources().getBoolean(
- com.android.internal.R.bool.config_unplugTurnsOnScreen);
- boolean aod = mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT);
- return !aod && plugTurnsOnScreen;
- }
-
- @Override
- public void onDensityOrFontScaleChanged() {
- setImageResource(mImageResource);
- }
-
- public void setDark(boolean dark) {
- mDark = dark;
- if (!dark) {
- clearSuppressCharging();
- }
- updateVisibility();
- }
-
- private void updateVisibility() {
- setVisibility(mCharging && !mSuppressCharging && mDark ? VISIBLE : INVISIBLE);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/DemoMode.java b/packages/SystemUI/src/com/android/systemui/DemoMode.java
index 11996d078bc3..5c3971571b87 100644
--- a/packages/SystemUI/src/com/android/systemui/DemoMode.java
+++ b/packages/SystemUI/src/com/android/systemui/DemoMode.java
@@ -37,4 +37,5 @@ public interface DemoMode {
public static final String COMMAND_STATUS = "status";
public static final String COMMAND_NOTIFICATIONS = "notifications";
public static final String COMMAND_VOLUME = "volume";
+ public static final String COMMAND_OPERATOR = "operator";
}
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index b25719b0272d..7403ddc441f6 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -26,7 +26,7 @@ import android.view.IWindowManager;
import android.view.WindowManagerGlobal;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.app.NightDisplayController;
+import com.android.internal.app.ColorDisplayController;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.util.Preconditions;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
@@ -40,6 +40,8 @@ import com.android.systemui.plugins.PluginDependencyProvider;
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.PluginManagerImpl;
import com.android.systemui.plugins.VolumeDialogController;
+import com.android.systemui.power.EnhancedEstimates;
+import com.android.systemui.power.EnhancedEstimatesImpl;
import com.android.systemui.power.PowerNotificationWarnings;
import com.android.systemui.power.PowerUI;
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
@@ -205,8 +207,8 @@ public class Dependency extends SystemUI {
mProviders.put(BatteryController.class, () ->
new BatteryControllerImpl(mContext));
- mProviders.put(NightDisplayController.class, () ->
- new NightDisplayController(mContext));
+ mProviders.put(ColorDisplayController.class, () ->
+ new ColorDisplayController(mContext));
mProviders.put(ManagedProfileController.class, () ->
new ManagedProfileControllerImpl(mContext));
@@ -308,6 +310,10 @@ public class Dependency extends SystemUI {
mProviders.put(IWindowManager.class, () -> WindowManagerGlobal.getWindowManagerService());
+ mProviders.put(OverviewProxyService.class, () -> new OverviewProxyService(mContext));
+
+ mProviders.put(EnhancedEstimates.class, () -> new EnhancedEstimatesImpl());
+
// Put all dependencies above here so the factory can override them if it wants.
SystemUIFactory.getInstance().injectDependencies(mProviders, mContext);
}
@@ -352,7 +358,8 @@ public class Dependency extends SystemUI {
@SuppressWarnings("unchecked")
DependencyProvider<T> provider = mProviders.get(cls);
if (provider == null) {
- throw new IllegalArgumentException("Unsupported dependency " + cls);
+ throw new IllegalArgumentException("Unsupported dependency " + cls
+ + ". " + mProviders.size() + " providers known.");
}
return provider.createDependency();
}
diff --git a/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags b/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags
index d4149ea6d68f..9c847be75fab 100644
--- a/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags
+++ b/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags
@@ -61,8 +61,3 @@ option java_package com.android.systemui;
## 4: SYSTEM_REGISTER_USER System sysui registers user's callbacks
## 5: SYSTEM_UNREGISTER_USER System sysui unregisters user's callbacks (after death)
36060 sysui_recents_connection (type|1),(user|1)
-
-# ---------------------------
-# LatencyTracker.java
-# ---------------------------
-36070 sysui_latency (action|1|5),(latency|1|3)
diff --git a/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java b/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java
index ca34345d923b..948178802003 100644
--- a/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java
@@ -58,6 +58,7 @@ public class HardwareUiLayout extends FrameLayout implements Tunable {
private boolean mRoundedDivider;
private int mRotation = ROTATION_NONE;
private boolean mRotatedBackground;
+ private boolean mSwapOrientation = true;
public HardwareUiLayout(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -145,6 +146,10 @@ public class HardwareUiLayout extends FrameLayout implements Tunable {
updateRotation();
}
+ public void setSwapOrientation(boolean swapOrientation) {
+ mSwapOrientation = swapOrientation;
+ }
+
private void updateRotation() {
int rotation = RotationUtils.getRotation(getContext());
if (rotation != mRotation) {
@@ -173,7 +178,9 @@ public class HardwareUiLayout extends FrameLayout implements Tunable {
if (to == ROTATION_SEASCAPE) {
swapOrder(linearLayout);
}
- linearLayout.setOrientation(LinearLayout.HORIZONTAL);
+ if (mSwapOrientation) {
+ linearLayout.setOrientation(LinearLayout.HORIZONTAL);
+ }
swapDimens(this.mChild);
}
} else {
@@ -184,7 +191,9 @@ public class HardwareUiLayout extends FrameLayout implements Tunable {
if (from == ROTATION_SEASCAPE) {
swapOrder(linearLayout);
}
- linearLayout.setOrientation(LinearLayout.VERTICAL);
+ if (mSwapOrientation) {
+ linearLayout.setOrientation(LinearLayout.VERTICAL);
+ }
swapDimens(mChild);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index 907a79e723ac..a59c97e0d08d 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -16,11 +16,6 @@
package com.android.systemui;
-import static android.opengl.GLES20.*;
-
-import static javax.microedition.khronos.egl.EGL10.*;
-
-import android.app.ActivityManager;
import android.app.WallpaperManager;
import android.content.ComponentCallbacks2;
import android.graphics.Bitmap;
@@ -28,31 +23,18 @@ import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region.Op;
-import android.opengl.GLUtils;
import android.os.AsyncTask;
-import android.os.SystemProperties;
import android.os.Trace;
-import android.renderscript.Matrix4f;
import android.service.wallpaper.WallpaperService;
import android.util.Log;
import android.view.Display;
import android.view.DisplayInfo;
-import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.WindowManager;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.FloatBuffer;
-
-import javax.microedition.khronos.egl.EGL10;
-import javax.microedition.khronos.egl.EGLConfig;
-import javax.microedition.khronos.egl.EGLContext;
-import javax.microedition.khronos.egl.EGLDisplay;
-import javax.microedition.khronos.egl.EGLSurface;
/**
* Default built-in wallpaper that simply shows a static image.
@@ -64,24 +46,13 @@ public class ImageWallpaper extends WallpaperService {
private static final boolean DEBUG = false;
private static final String PROPERTY_KERNEL_QEMU = "ro.kernel.qemu";
- static final boolean FIXED_SIZED_SURFACE = true;
- static final boolean USE_OPENGL = true;
-
- WallpaperManager mWallpaperManager;
-
- DrawableEngine mEngine;
-
- boolean mIsHwAccelerated;
+ private WallpaperManager mWallpaperManager;
+ private DrawableEngine mEngine;
@Override
public void onCreate() {
super.onCreate();
- mWallpaperManager = (WallpaperManager) getSystemService(WALLPAPER_SERVICE);
-
- //noinspection PointlessBooleanExpression,ConstantConditions
- if (FIXED_SIZED_SURFACE && USE_OPENGL) {
- mIsHwAccelerated = ActivityManager.isHighEndGfx();
- }
+ mWallpaperManager = getSystemService(WallpaperManager.class);
}
@Override
@@ -98,15 +69,12 @@ public class ImageWallpaper extends WallpaperService {
}
class DrawableEngine extends Engine {
- static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
- static final int EGL_OPENGL_ES2_BIT = 4;
-
Bitmap mBackground;
int mBackgroundWidth = -1, mBackgroundHeight = -1;
int mLastSurfaceWidth = -1, mLastSurfaceHeight = -1;
int mLastRotation = -1;
- float mXOffset = 0.5f;
- float mYOffset = 0.5f;
+ float mXOffset = 0f;
+ float mYOffset = 0f;
float mScale = 1f;
private Display mDefaultDisplay;
@@ -117,34 +85,6 @@ public class ImageWallpaper extends WallpaperService {
int mLastXTranslation;
int mLastYTranslation;
- private EGL10 mEgl;
- private EGLDisplay mEglDisplay;
- private EGLConfig mEglConfig;
- private EGLContext mEglContext;
- private EGLSurface mEglSurface;
-
- private static final String sSimpleVS =
- "attribute vec4 position;\n" +
- "attribute vec2 texCoords;\n" +
- "varying vec2 outTexCoords;\n" +
- "uniform mat4 projection;\n" +
- "\nvoid main(void) {\n" +
- " outTexCoords = texCoords;\n" +
- " gl_Position = projection * position;\n" +
- "}\n\n";
- private static final String sSimpleFS =
- "precision mediump float;\n\n" +
- "varying vec2 outTexCoords;\n" +
- "uniform sampler2D texture;\n" +
- "\nvoid main(void) {\n" +
- " gl_FragColor = texture2D(texture, outTexCoords);\n" +
- "}\n\n";
-
- private static final int FLOAT_SIZE_BYTES = 4;
- private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES;
- private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
- private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
-
private int mRotationAtLastSurfaceSizeUpdate = -1;
private int mDisplayWidthAtLastSurfaceSizeUpdate = -1;
private int mDisplayHeightAtLastSurfaceSizeUpdate = -1;
@@ -154,13 +94,14 @@ public class ImageWallpaper extends WallpaperService {
private AsyncTask<Void, Void, Bitmap> mLoader;
private boolean mNeedsDrawAfterLoadingWallpaper;
private boolean mSurfaceValid;
+ private boolean mSurfaceRedrawNeeded;
- public DrawableEngine() {
+ DrawableEngine() {
super();
setFixedSizeAllowed(true);
}
- public void trimMemory(int level) {
+ void trimMemory(int level) {
if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW
&& level <= ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL
&& mBackground != null) {
@@ -179,6 +120,7 @@ public class ImageWallpaper extends WallpaperService {
super.onCreate(surfaceHolder);
+ //noinspection ConstantConditions
mDefaultDisplay = getSystemService(WindowManager.class).getDefaultDisplay();
setOffsetNotificationsEnabled(false);
@@ -199,7 +141,7 @@ public class ImageWallpaper extends WallpaperService {
// Load background image dimensions, if we haven't saved them yet
if (mBackgroundWidth <= 0 || mBackgroundHeight <= 0) {
// Need to load the image to get dimensions
- loadWallpaper(forDraw, false /* needsReset */);
+ loadWallpaper(forDraw);
if (DEBUG) {
Log.d(TAG, "Reloading, redoing updateSurfaceSize later.");
}
@@ -210,16 +152,13 @@ public class ImageWallpaper extends WallpaperService {
int surfaceWidth = Math.max(displayInfo.logicalWidth, mBackgroundWidth);
int surfaceHeight = Math.max(displayInfo.logicalHeight, mBackgroundHeight);
- if (FIXED_SIZED_SURFACE) {
- // Used a fixed size surface, because we are special. We can do
- // this because we know the current design of window animations doesn't
- // cause this to break.
- surfaceHolder.setFixedSize(surfaceWidth, surfaceHeight);
- mLastRequestedWidth = surfaceWidth;
- mLastRequestedHeight = surfaceHeight;
- } else {
- surfaceHolder.setSizeFromLayout();
- }
+ // Used a fixed size surface, because we are special. We can do
+ // this because we know the current design of window animations doesn't
+ // cause this to break.
+ surfaceHolder.setFixedSize(surfaceWidth, surfaceHeight);
+ mLastRequestedWidth = surfaceWidth;
+ mLastRequestedHeight = surfaceHeight;
+
return hasWallpaper;
}
@@ -300,6 +239,13 @@ public class ImageWallpaper extends WallpaperService {
Log.d(TAG, "onSurfaceRedrawNeeded");
}
super.onSurfaceRedrawNeeded(holder);
+ // At the end of this method we should have drawn into the surface.
+ // This means that the bitmap should be loaded synchronously if
+ // it was already unloaded.
+ if (mBackground == null) {
+ updateBitmap(mWallpaperManager.getBitmap(true /* hardware */));
+ }
+ mSurfaceRedrawNeeded = true;
drawFrame();
}
@@ -336,7 +282,8 @@ public class ImageWallpaper extends WallpaperService {
boolean surfaceDimensionsChanged = dw != mLastSurfaceWidth
|| dh != mLastSurfaceHeight;
- boolean redrawNeeded = surfaceDimensionsChanged || newRotation != mLastRotation;
+ boolean redrawNeeded = surfaceDimensionsChanged || newRotation != mLastRotation
+ || mSurfaceRedrawNeeded;
if (!redrawNeeded && !mOffsetsChanged) {
if (DEBUG) {
Log.d(TAG, "Suppressed drawFrame since redraw is not needed "
@@ -345,40 +292,24 @@ public class ImageWallpaper extends WallpaperService {
return;
}
mLastRotation = newRotation;
+ mSurfaceRedrawNeeded = false;
// Load bitmap if it is not yet loaded
if (mBackground == null) {
- if (DEBUG) {
- Log.d(TAG, "Reloading bitmap: mBackground, bgw, bgh, dw, dh = " +
- mBackground + ", " +
- ((mBackground == null) ? 0 : mBackground.getWidth()) + ", " +
- ((mBackground == null) ? 0 : mBackground.getHeight()) + ", " +
- dw + ", " + dh);
- }
- loadWallpaper(true /* needDraw */, true /* needReset */);
+ loadWallpaper(true);
if (DEBUG) {
Log.d(TAG, "Reloading, resuming draw later");
}
return;
}
- // Center the scaled image
+ // Left align the scaled image
mScale = Math.max(1f, Math.max(dw / (float) mBackground.getWidth(),
dh / (float) mBackground.getHeight()));
- final int availw = dw - (int) (mBackground.getWidth() * mScale);
- final int availh = dh - (int) (mBackground.getHeight() * mScale);
- int xPixels = availw / 2;
- int yPixels = availh / 2;
-
- // Adjust the image for xOffset/yOffset values. If window manager is handling offsets,
- // mXOffset and mYOffset are set to 0.5f by default and therefore xPixels and yPixels
- // will remain unchanged
- final int availwUnscaled = dw - mBackground.getWidth();
- final int availhUnscaled = dh - mBackground.getHeight();
- if (availwUnscaled < 0)
- xPixels += (int) (availwUnscaled * (mXOffset - .5f) + .5f);
- if (availhUnscaled < 0)
- yPixels += (int) (availhUnscaled * (mYOffset - .5f) + .5f);
+ final int availw = (int) (mBackground.getWidth() * mScale) - dw;
+ final int availh = (int) (mBackground.getHeight() * mScale) - dh;
+ int xPixels = (int) (availw * mXOffset);
+ int yPixels = (int) (availh * mYOffset);
mOffsetsChanged = false;
if (surfaceDimensionsChanged) {
@@ -399,21 +330,7 @@ public class ImageWallpaper extends WallpaperService {
Log.d(TAG, "Redrawing wallpaper");
}
- if (mIsHwAccelerated) {
- if (!drawWallpaperWithOpenGL(sh, availw, availh, xPixels, yPixels)) {
- drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels);
- }
- } else {
- drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels);
- if (FIXED_SIZED_SURFACE) {
- // If the surface is fixed-size, we should only need to
- // draw it once and then we'll let the window manager
- // position it appropriately. As such, we no longer needed
- // the loaded bitmap. Yay!
- // hw-accelerated renderer retains bitmap for faster rotation
- unloadWallpaper(false /* forgetSize */);
- }
- }
+ drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
@@ -428,28 +345,20 @@ public class ImageWallpaper extends WallpaperService {
*
* If {@param needsReset} is set also clears the cache in WallpaperManager first.
*/
- private void loadWallpaper(boolean needsDraw, boolean needsReset) {
+ private void loadWallpaper(boolean needsDraw) {
mNeedsDrawAfterLoadingWallpaper |= needsDraw;
if (mLoader != null) {
- if (needsReset) {
- mLoader.cancel(false /* interrupt */);
- mLoader = null;
- } else {
- if (DEBUG) {
- Log.d(TAG, "Skipping loadWallpaper, already in flight ");
- }
- return;
+ if (DEBUG) {
+ Log.d(TAG, "Skipping loadWallpaper, already in flight ");
}
+ return;
}
mLoader = new AsyncTask<Void, Void, Bitmap>() {
@Override
protected Bitmap doInBackground(Void... params) {
Throwable exception;
try {
- if (needsReset) {
- mWallpaperManager.forgetLoadedWallpaper();
- }
- return mWallpaperManager.getBitmap();
+ return mWallpaperManager.getBitmap(true /* hardware */);
} catch (RuntimeException | OutOfMemoryError e) {
exception = e;
}
@@ -458,48 +367,33 @@ public class ImageWallpaper extends WallpaperService {
return null;
}
- if (exception != null) {
- // Note that if we do fail at this, and the default wallpaper can't
- // be loaded, we will go into a cycle. Don't do a build where the
- // default wallpaper can't be loaded.
- Log.w(TAG, "Unable to load wallpaper!", exception);
- try {
- mWallpaperManager.clear();
- } catch (IOException ex) {
- // now we're really screwed.
- Log.w(TAG, "Unable reset to default wallpaper!", ex);
- }
-
- if (isCancelled()) {
- return null;
- }
-
- try {
- return mWallpaperManager.getBitmap();
- } catch (RuntimeException | OutOfMemoryError e) {
- Log.w(TAG, "Unable to load default wallpaper!", e);
- }
+ // Note that if we do fail at this, and the default wallpaper can't
+ // be loaded, we will go into a cycle. Don't do a build where the
+ // default wallpaper can't be loaded.
+ Log.w(TAG, "Unable to load wallpaper!", exception);
+ try {
+ mWallpaperManager.clear();
+ } catch (IOException ex) {
+ // now we're really screwed.
+ Log.w(TAG, "Unable reset to default wallpaper!", ex);
+ }
+
+ if (isCancelled()) {
+ return null;
+ }
+
+ try {
+ return mWallpaperManager.getBitmap(true /* hardware */);
+ } catch (RuntimeException | OutOfMemoryError e) {
+ Log.w(TAG, "Unable to load default wallpaper!", e);
}
return null;
}
@Override
protected void onPostExecute(Bitmap b) {
- mBackground = null;
- mBackgroundWidth = -1;
- mBackgroundHeight = -1;
-
- if (b != null) {
- mBackground = b;
- mBackgroundWidth = mBackground.getWidth();
- mBackgroundHeight = mBackground.getHeight();
- }
+ updateBitmap(b);
- if (DEBUG) {
- Log.d(TAG, "Wallpaper loaded: " + mBackground);
- }
- updateSurfaceSize(getSurfaceHolder(), getDefaultDisplayInfo(),
- false /* forDraw */);
if (mNeedsDrawAfterLoadingWallpaper) {
drawFrame();
}
@@ -510,6 +404,24 @@ public class ImageWallpaper extends WallpaperService {
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
+ private void updateBitmap(Bitmap bitmap) {
+ mBackground = null;
+ mBackgroundWidth = -1;
+ mBackgroundHeight = -1;
+
+ if (bitmap != null) {
+ mBackground = bitmap;
+ mBackgroundWidth = mBackground.getWidth();
+ mBackgroundHeight = mBackground.getHeight();
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "Wallpaper loaded: " + mBackground);
+ }
+ updateSurfaceSize(getSurfaceHolder(), getDefaultDisplayInfo(),
+ false /* forDraw */);
+ }
+
private void unloadWallpaper(boolean forgetSize) {
if (mLoader != null) {
mLoader.cancel(false);
@@ -564,7 +476,7 @@ public class ImageWallpaper extends WallpaperService {
}
private void drawWallpaperWithCanvas(SurfaceHolder sh, int w, int h, int left, int top) {
- Canvas c = sh.lockCanvas();
+ Canvas c = sh.lockHardwareCanvas();
if (c != null) {
try {
if (DEBUG) {
@@ -582,7 +494,8 @@ public class ImageWallpaper extends WallpaperService {
}
if (mBackground != null) {
RectF dest = new RectF(left, top, right, bottom);
- // add a filter bitmap?
+ Log.i(TAG, "Redrawing in rect: " + dest + " with surface size: "
+ + mLastRequestedWidth + "x" + mLastRequestedHeight);
c.drawBitmap(mBackground, null, dest, null);
}
} finally {
@@ -590,278 +503,5 @@ public class ImageWallpaper extends WallpaperService {
}
}
}
-
- private boolean drawWallpaperWithOpenGL(SurfaceHolder sh, int w, int h, int left, int top) {
- if (!initGL(sh)) return false;
-
- final float right = left + mBackground.getWidth() * mScale;
- final float bottom = top + mBackground.getHeight() * mScale;
-
- final Rect frame = sh.getSurfaceFrame();
- final Matrix4f ortho = new Matrix4f();
- ortho.loadOrtho(0.0f, frame.width(), frame.height(), 0.0f, -1.0f, 1.0f);
-
- final FloatBuffer triangleVertices = createMesh(left, top, right, bottom);
-
- final int texture = loadTexture(mBackground);
- final int program = buildProgram(sSimpleVS, sSimpleFS);
-
- final int attribPosition = glGetAttribLocation(program, "position");
- final int attribTexCoords = glGetAttribLocation(program, "texCoords");
- final int uniformTexture = glGetUniformLocation(program, "texture");
- final int uniformProjection = glGetUniformLocation(program, "projection");
-
- checkGlError();
-
- glViewport(0, 0, frame.width(), frame.height());
- glBindTexture(GL_TEXTURE_2D, texture);
-
- glUseProgram(program);
- glEnableVertexAttribArray(attribPosition);
- glEnableVertexAttribArray(attribTexCoords);
- glUniform1i(uniformTexture, 0);
- glUniformMatrix4fv(uniformProjection, 1, false, ortho.getArray(), 0);
-
- checkGlError();
-
- if (w > 0 || h > 0) {
- glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
- glClear(GL_COLOR_BUFFER_BIT);
- }
-
- // drawQuad
- triangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
- glVertexAttribPointer(attribPosition, 3, GL_FLOAT, false,
- TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);
-
- triangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
- glVertexAttribPointer(attribTexCoords, 3, GL_FLOAT, false,
- TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);
-
- glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
-
- boolean status = mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);
- checkEglError();
-
- finishGL(texture, program);
-
- return status;
- }
-
- private FloatBuffer createMesh(int left, int top, float right, float bottom) {
- final float[] verticesData = {
- // X, Y, Z, U, V
- left, bottom, 0.0f, 0.0f, 1.0f,
- right, bottom, 0.0f, 1.0f, 1.0f,
- left, top, 0.0f, 0.0f, 0.0f,
- right, top, 0.0f, 1.0f, 0.0f,
- };
-
- final int bytes = verticesData.length * FLOAT_SIZE_BYTES;
- final FloatBuffer triangleVertices = ByteBuffer.allocateDirect(bytes).order(
- ByteOrder.nativeOrder()).asFloatBuffer();
- triangleVertices.put(verticesData).position(0);
- return triangleVertices;
- }
-
- private int loadTexture(Bitmap bitmap) {
- int[] textures = new int[1];
-
- glActiveTexture(GL_TEXTURE0);
- glGenTextures(1, textures, 0);
- checkGlError();
-
- int texture = textures[0];
- glBindTexture(GL_TEXTURE_2D, texture);
- checkGlError();
-
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
-
- GLUtils.texImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bitmap, GL_UNSIGNED_BYTE, 0);
- checkGlError();
-
- return texture;
- }
-
- private int buildProgram(String vertex, String fragment) {
- int vertexShader = buildShader(vertex, GL_VERTEX_SHADER);
- if (vertexShader == 0) return 0;
-
- int fragmentShader = buildShader(fragment, GL_FRAGMENT_SHADER);
- if (fragmentShader == 0) return 0;
-
- int program = glCreateProgram();
- glAttachShader(program, vertexShader);
- glAttachShader(program, fragmentShader);
- glLinkProgram(program);
- checkGlError();
-
- glDeleteShader(vertexShader);
- glDeleteShader(fragmentShader);
-
- int[] status = new int[1];
- glGetProgramiv(program, GL_LINK_STATUS, status, 0);
- if (status[0] != GL_TRUE) {
- String error = glGetProgramInfoLog(program);
- Log.d(GL_LOG_TAG, "Error while linking program:\n" + error);
- glDeleteProgram(program);
- return 0;
- }
-
- return program;
- }
-
- private int buildShader(String source, int type) {
- int shader = glCreateShader(type);
-
- glShaderSource(shader, source);
- checkGlError();
-
- glCompileShader(shader);
- checkGlError();
-
- int[] status = new int[1];
- glGetShaderiv(shader, GL_COMPILE_STATUS, status, 0);
- if (status[0] != GL_TRUE) {
- String error = glGetShaderInfoLog(shader);
- Log.d(GL_LOG_TAG, "Error while compiling shader:\n" + error);
- glDeleteShader(shader);
- return 0;
- }
-
- return shader;
- }
-
- private void checkEglError() {
- int error = mEgl.eglGetError();
- if (error != EGL_SUCCESS) {
- Log.w(GL_LOG_TAG, "EGL error = " + GLUtils.getEGLErrorString(error));
- }
- }
-
- private void checkGlError() {
- int error = glGetError();
- if (error != GL_NO_ERROR) {
- Log.w(GL_LOG_TAG, "GL error = 0x" + Integer.toHexString(error), new Throwable());
- }
- }
-
- private void finishGL(int texture, int program) {
- int[] textures = new int[1];
- textures[0] = texture;
- glDeleteTextures(1, textures, 0);
- glDeleteProgram(program);
- mEgl.eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
- mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
- mEgl.eglDestroyContext(mEglDisplay, mEglContext);
- mEgl.eglTerminate(mEglDisplay);
- }
-
- private boolean initGL(SurfaceHolder surfaceHolder) {
- mEgl = (EGL10) EGLContext.getEGL();
-
- mEglDisplay = mEgl.eglGetDisplay(EGL_DEFAULT_DISPLAY);
- if (mEglDisplay == EGL_NO_DISPLAY) {
- throw new RuntimeException("eglGetDisplay failed " +
- GLUtils.getEGLErrorString(mEgl.eglGetError()));
- }
-
- int[] version = new int[2];
- if (!mEgl.eglInitialize(mEglDisplay, version)) {
- throw new RuntimeException("eglInitialize failed " +
- GLUtils.getEGLErrorString(mEgl.eglGetError()));
- }
-
- mEglConfig = chooseEglConfig();
- if (mEglConfig == null) {
- throw new RuntimeException("eglConfig not initialized");
- }
-
- mEglContext = createContext(mEgl, mEglDisplay, mEglConfig);
- if (mEglContext == EGL_NO_CONTEXT) {
- throw new RuntimeException("createContext failed " +
- GLUtils.getEGLErrorString(mEgl.eglGetError()));
- }
-
- int attribs[] = {
- EGL_WIDTH, 1,
- EGL_HEIGHT, 1,
- EGL_NONE
- };
- EGLSurface tmpSurface = mEgl.eglCreatePbufferSurface(mEglDisplay, mEglConfig, attribs);
- mEgl.eglMakeCurrent(mEglDisplay, tmpSurface, tmpSurface, mEglContext);
-
- int[] maxSize = new int[1];
- Rect frame = surfaceHolder.getSurfaceFrame();
- glGetIntegerv(GL_MAX_TEXTURE_SIZE, maxSize, 0);
-
- mEgl.eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
- mEgl.eglDestroySurface(mEglDisplay, tmpSurface);
-
- if(frame.width() > maxSize[0] || frame.height() > maxSize[0]) {
- mEgl.eglDestroyContext(mEglDisplay, mEglContext);
- mEgl.eglTerminate(mEglDisplay);
- Log.e(GL_LOG_TAG, "requested texture size " +
- frame.width() + "x" + frame.height() + " exceeds the support maximum of " +
- maxSize[0] + "x" + maxSize[0]);
- return false;
- }
-
- mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, surfaceHolder, null);
- if (mEglSurface == null || mEglSurface == EGL_NO_SURFACE) {
- int error = mEgl.eglGetError();
- if (error == EGL_BAD_NATIVE_WINDOW || error == EGL_BAD_ALLOC) {
- Log.e(GL_LOG_TAG, "createWindowSurface returned " +
- GLUtils.getEGLErrorString(error) + ".");
- return false;
- }
- throw new RuntimeException("createWindowSurface failed " +
- GLUtils.getEGLErrorString(error));
- }
-
- if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
- throw new RuntimeException("eglMakeCurrent failed " +
- GLUtils.getEGLErrorString(mEgl.eglGetError()));
- }
-
- return true;
- }
-
-
- EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) {
- int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
- return egl.eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, attrib_list);
- }
-
- private EGLConfig chooseEglConfig() {
- int[] configsCount = new int[1];
- EGLConfig[] configs = new EGLConfig[1];
- int[] configSpec = getConfig();
- if (!mEgl.eglChooseConfig(mEglDisplay, configSpec, configs, 1, configsCount)) {
- throw new IllegalArgumentException("eglChooseConfig failed " +
- GLUtils.getEGLErrorString(mEgl.eglGetError()));
- } else if (configsCount[0] > 0) {
- return configs[0];
- }
- return null;
- }
-
- private int[] getConfig() {
- return new int[] {
- EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
- EGL_RED_SIZE, 8,
- EGL_GREEN_SIZE, 8,
- EGL_BLUE_SIZE, 8,
- EGL_ALPHA_SIZE, 0,
- EGL_DEPTH_SIZE, 0,
- EGL_STENCIL_SIZE, 0,
- EGL_CONFIG_CAVEAT, EGL_NONE,
- EGL_NONE
- };
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/Interpolators.java b/packages/SystemUI/src/com/android/systemui/Interpolators.java
index b8cfa3e48c20..aeef49689517 100644
--- a/packages/SystemUI/src/com/android/systemui/Interpolators.java
+++ b/packages/SystemUI/src/com/android/systemui/Interpolators.java
@@ -18,6 +18,7 @@ package com.android.systemui;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AccelerateInterpolator;
+import android.view.animation.BounceInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
@@ -43,6 +44,7 @@ public class Interpolators {
public static final Interpolator ICON_OVERSHOT = new PathInterpolator(0.4f, 0f, 0.2f, 1.4f);
public static final Interpolator PANEL_CLOSE_ACCELERATED
= new PathInterpolator(0.3f, 0, 0.5f, 1);
+ public static final Interpolator BOUNCE = new BounceInterpolator();
/**
* Interpolator to be used when animating a move based on a click. Pair with enough duration.
diff --git a/packages/SystemUI/src/com/android/systemui/LatencyTester.java b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
index 1d55ee5ac0d6..cbb69ee98a03 100644
--- a/packages/SystemUI/src/com/android/systemui/LatencyTester.java
+++ b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
@@ -25,7 +25,7 @@ import android.os.PowerManager;
import android.os.SystemClock;
import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.LatencyTracker;
+import com.android.internal.util.LatencyTracker;
import com.android.systemui.statusbar.phone.FingerprintUnlockController;
import com.android.systemui.statusbar.phone.StatusBar;
diff --git a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java
new file mode 100644
index 000000000000..d0128efe2c44
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java
@@ -0,0 +1,267 @@
+/*
+ * 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;
+
+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.graphics.Rect;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.PatternMatcher;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+import android.view.SurfaceControl;
+
+import com.android.systemui.OverviewProxyService.OverviewProxyListener;
+import com.android.systemui.shared.recents.IOverviewProxy;
+import com.android.systemui.shared.recents.ISystemUiProxy;
+import com.android.systemui.shared.system.GraphicBufferCompat;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.policy.CallbackController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Class to send information from overview to launcher with a binder.
+ */
+public class OverviewProxyService implements CallbackController<OverviewProxyListener>, Dumpable {
+
+ public static final String TAG_OPS = "OverviewProxyService";
+ public static final boolean DEBUG_OVERVIEW_PROXY = false;
+ private static final long BACKOFF_MILLIS = 5000;
+
+ private final Context mContext;
+ private final Handler mHandler;
+ private final Runnable mConnectionRunnable = this::internalConnectToCurrentUser;
+ private final ComponentName mLauncherComponentName;
+ private final DeviceProvisionedController mDeviceProvisionedController
+ = Dependency.get(DeviceProvisionedController.class);
+ private final List<OverviewProxyListener> mConnectionCallbacks = new ArrayList<>();
+
+ private IOverviewProxy mOverviewProxy;
+ private int mConnectionBackoffAttempts;
+ private CharSequence mOnboardingText;
+
+ private ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() {
+
+ public GraphicBufferCompat screenshot(Rect sourceCrop, int width, int height, int minLayer,
+ int maxLayer, boolean useIdentityTransform, int rotation) {
+ long token = Binder.clearCallingIdentity();
+ try {
+ return new GraphicBufferCompat(SurfaceControl.screenshotToBuffer(sourceCrop, width,
+ height, minLayer, maxLayer, useIdentityTransform, rotation));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ public void startScreenPinning(int taskId) {
+ long token = Binder.clearCallingIdentity();
+ try {
+ mHandler.post(() -> {
+ StatusBar statusBar = ((SystemUIApplication) mContext).getComponent(
+ StatusBar.class);
+ if (statusBar != null) {
+ statusBar.showScreenPinningRequest(taskId, false /* allowCancel */);
+ }
+ });
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ public void onRecentsAnimationStarted() {
+ long token = Binder.clearCallingIdentity();
+ try {
+ mHandler.post(OverviewProxyService.this::notifyRecentsAnimationStarted);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ public void setRecentsOnboardingText(CharSequence text) {
+ mOnboardingText = text;
+ }
+ };
+
+ private final BroadcastReceiver mLauncherAddedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // Reconnect immediately, instead of waiting for resume to arrive.
+ startConnectionToCurrentUser();
+ }
+ };
+
+ private final ServiceConnection mOverviewServiceConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ if (service != null) {
+ mConnectionBackoffAttempts = 0;
+ mOverviewProxy = IOverviewProxy.Stub.asInterface(service);
+ // Listen for launcher's death
+ try {
+ service.linkToDeath(mOverviewServiceDeathRcpt, 0);
+ } catch (RemoteException e) {
+ Log.e(TAG_OPS, "Lost connection to launcher service", e);
+ }
+ try {
+ mOverviewProxy.onBind(mSysUiProxy);
+ } catch (RemoteException e) {
+ Log.e(TAG_OPS, "Failed to call onBind()", e);
+ }
+ notifyConnectionChanged();
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ // Do nothing
+ }
+ };
+
+ private final DeviceProvisionedListener mDeviceProvisionedCallback =
+ new DeviceProvisionedListener() {
+ @Override
+ public void onUserSetupChanged() {
+ if (mDeviceProvisionedController.isCurrentUserSetup()) {
+ internalConnectToCurrentUser();
+ }
+ }
+
+ @Override
+ public void onUserSwitched() {
+ mConnectionBackoffAttempts = 0;
+ internalConnectToCurrentUser();
+ }
+ };
+
+ // This is the death handler for the binder from the launcher service
+ private final IBinder.DeathRecipient mOverviewServiceDeathRcpt
+ = this::startConnectionToCurrentUser;
+
+ public OverviewProxyService(Context context) {
+ mContext = context;
+ mHandler = new Handler();
+ mConnectionBackoffAttempts = 0;
+ mLauncherComponentName = ComponentName
+ .unflattenFromString(context.getString(R.string.config_overviewServiceComponent));
+ mDeviceProvisionedController.addCallback(mDeviceProvisionedCallback);
+
+ // Listen for the package update changes.
+ IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+ filter.addDataScheme("package");
+ filter.addDataSchemeSpecificPart(mLauncherComponentName.getPackageName(),
+ PatternMatcher.PATTERN_LITERAL);
+ filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ mContext.registerReceiver(mLauncherAddedReceiver, filter);
+ }
+
+ public void startConnectionToCurrentUser() {
+ if (mHandler.getLooper() != Looper.myLooper()) {
+ mHandler.post(mConnectionRunnable);
+ } else {
+ internalConnectToCurrentUser();
+ }
+ }
+
+ private void internalConnectToCurrentUser() {
+ disconnectFromLauncherService();
+
+ // If user has not setup yet or already connected, do not try to connect
+ if (!mDeviceProvisionedController.isCurrentUserSetup()) {
+ return;
+ }
+ mHandler.removeCallbacks(mConnectionRunnable);
+ Intent launcherServiceIntent = new Intent();
+ launcherServiceIntent.setComponent(mLauncherComponentName);
+ boolean bound = mContext.bindServiceAsUser(launcherServiceIntent,
+ mOverviewServiceConnection, Context.BIND_AUTO_CREATE,
+ UserHandle.of(mDeviceProvisionedController.getCurrentUser()));
+ if (!bound) {
+ // Retry after exponential backoff timeout
+ final long timeoutMs = (long) Math.scalb(BACKOFF_MILLIS, mConnectionBackoffAttempts);
+ mHandler.postDelayed(mConnectionRunnable, timeoutMs);
+ mConnectionBackoffAttempts++;
+ }
+ }
+
+ @Override
+ public void addCallback(OverviewProxyListener listener) {
+ mConnectionCallbacks.add(listener);
+ listener.onConnectionChanged(mOverviewProxy != null);
+ }
+
+ @Override
+ public void removeCallback(OverviewProxyListener listener) {
+ mConnectionCallbacks.remove(listener);
+ }
+
+ public IOverviewProxy getProxy() {
+ return mOverviewProxy;
+ }
+
+ public CharSequence getOnboardingText() {
+ return mOnboardingText;
+ }
+
+ private void disconnectFromLauncherService() {
+ if (mOverviewProxy != null) {
+ mOverviewProxy.asBinder().unlinkToDeath(mOverviewServiceDeathRcpt, 0);
+ mContext.unbindService(mOverviewServiceConnection);
+ mOverviewProxy = null;
+ notifyConnectionChanged();
+ }
+ }
+
+ private void notifyConnectionChanged() {
+ for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
+ mConnectionCallbacks.get(i).onConnectionChanged(mOverviewProxy != null);
+ }
+ }
+
+ private void notifyRecentsAnimationStarted() {
+ for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
+ mConnectionCallbacks.get(i).onRecentsAnimationStarted();
+ }
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println(TAG_OPS + " state:");
+ pw.print(" mConnectionBackoffAttempts="); pw.println(mConnectionBackoffAttempts);
+ pw.print(" isCurrentUserSetup="); pw.println(mDeviceProvisionedController
+ .isCurrentUserSetup());
+ pw.print(" isConnected="); pw.println(mOverviewProxy != null);
+ }
+
+ public interface OverviewProxyListener {
+ default void onConnectionChanged(boolean isConnected) {}
+ default void onRecentsAnimationStarted() {}
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java
index 4437d314a7c2..adb4e33d1a19 100644
--- a/packages/SystemUI/src/com/android/systemui/Prefs.java
+++ b/packages/SystemUI/src/com/android/systemui/Prefs.java
@@ -48,6 +48,8 @@ public final class Prefs {
Key.QS_WORK_ADDED,
Key.QS_NIGHTDISPLAY_ADDED,
Key.SEEN_MULTI_USER,
+ Key.NUM_APPS_LAUNCHED,
+ Key.HAS_SEEN_RECENTS_ONBOARDING,
})
public @interface Key {
@Deprecated
@@ -75,6 +77,8 @@ public final class Prefs {
@Deprecated
String QS_NIGHTDISPLAY_ADDED = "QsNightDisplayAdded";
String SEEN_MULTI_USER = "HasSeenMultiUser";
+ String NUM_APPS_LAUNCHED = "NumAppsLaunched";
+ String HAS_SEEN_RECENTS_ONBOARDING = "HasSeenRecentsOnboarding";
}
public static boolean getBoolean(Context context, @Key String key, boolean defaultValue) {
diff --git a/packages/SystemUI/src/com/android/systemui/RecentsComponent.java b/packages/SystemUI/src/com/android/systemui/RecentsComponent.java
index 44a044bc4ce4..f9dbf4a15e5c 100644
--- a/packages/SystemUI/src/com/android/systemui/RecentsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/RecentsComponent.java
@@ -21,14 +21,14 @@ import android.view.Display;
import android.view.View;
public interface RecentsComponent {
- void showRecentApps(boolean triggeredFromAltTab, boolean fromHome);
+ void showRecentApps(boolean triggeredFromAltTab);
void showNextAffiliatedTask();
void showPrevAffiliatedTask();
/**
* Docks the top-most task and opens recents.
*/
- boolean dockTopTask(int dragMode, int stackCreateMode, Rect initialBounds,
+ boolean splitPrimaryTask(int dragMode, int stackCreateMode, Rect initialBounds,
int metricsDockAction);
/**
diff --git a/packages/SystemUI/src/com/android/systemui/RoundedCorners.java b/packages/SystemUI/src/com/android/systemui/RoundedCorners.java
deleted file mode 100644
index b15b79fb984e..000000000000
--- a/packages/SystemUI/src/com/android/systemui/RoundedCorners.java
+++ /dev/null
@@ -1,218 +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;
-
-import static com.android.systemui.tuner.TunablePadding.FLAG_START;
-import static com.android.systemui.tuner.TunablePadding.FLAG_END;
-
-import android.app.Fragment;
-import android.content.res.ColorStateList;
-import android.graphics.Color;
-import android.graphics.PixelFormat;
-import android.provider.Settings.Secure;
-import android.support.annotation.VisibleForTesting;
-import android.util.DisplayMetrics;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnLayoutChangeListener;
-import android.view.ViewGroup;
-import android.view.ViewGroup.LayoutParams;
-import android.view.WindowManager;
-import android.widget.ImageView;
-
-import com.android.systemui.R.id;
-import com.android.systemui.fragments.FragmentHostManager;
-import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
-import com.android.systemui.plugins.qs.QS;
-import com.android.systemui.qs.SecureSetting;
-import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
-import com.android.systemui.statusbar.phone.NavigationBarFragment;
-import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.tuner.TunablePadding;
-import com.android.systemui.tuner.TunerService;
-import com.android.systemui.tuner.TunerService.Tunable;
-
-public class RoundedCorners extends SystemUI implements Tunable {
- public static final String SIZE = "sysui_rounded_size";
- public static final String PADDING = "sysui_rounded_content_padding";
-
- private int mRoundedDefault;
- private View mOverlay;
- private View mBottomOverlay;
- private float mDensity;
- private TunablePadding mQsPadding;
- private TunablePadding mStatusBarPadding;
- private TunablePadding mNavBarPadding;
-
- @Override
- public void start() {
- mRoundedDefault = mContext.getResources().getDimensionPixelSize(
- R.dimen.rounded_corner_radius);
- if (mRoundedDefault != 0) {
- setupRounding();
- }
- int padding = mContext.getResources().getDimensionPixelSize(
- R.dimen.rounded_corner_content_padding);
- if (padding != 0) {
- setupPadding(padding);
- }
- }
-
- private void setupRounding() {
- mOverlay = LayoutInflater.from(mContext)
- .inflate(R.layout.rounded_corners, null);
- mOverlay.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
- mOverlay.setAlpha(0);
- mOverlay.findViewById(R.id.right).setRotation(90);
-
- mContext.getSystemService(WindowManager.class)
- .addView(mOverlay, getWindowLayoutParams());
- mBottomOverlay = LayoutInflater.from(mContext)
- .inflate(R.layout.rounded_corners, null);
- mBottomOverlay.setAlpha(0);
- mBottomOverlay.findViewById(R.id.right).setRotation(180);
- mBottomOverlay.findViewById(R.id.left).setRotation(270);
- WindowManager.LayoutParams layoutParams = getWindowLayoutParams();
- layoutParams.gravity = Gravity.BOTTOM;
- mContext.getSystemService(WindowManager.class)
- .addView(mBottomOverlay, layoutParams);
-
- DisplayMetrics metrics = new DisplayMetrics();
- mContext.getSystemService(WindowManager.class)
- .getDefaultDisplay().getMetrics(metrics);
- mDensity = metrics.density;
-
- Dependency.get(TunerService.class).addTunable(this, SIZE);
-
- // Watch color inversion and invert the overlay as needed.
- SecureSetting setting = new SecureSetting(mContext, Dependency.get(Dependency.MAIN_HANDLER),
- Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED) {
- @Override
- protected void handleValueChanged(int value, boolean observedChange) {
- int tint = value != 0 ? Color.WHITE : Color.BLACK;
- ColorStateList tintList = ColorStateList.valueOf(tint);
- ((ImageView) mOverlay.findViewById(id.left)).setImageTintList(tintList);
- ((ImageView) mOverlay.findViewById(id.right)).setImageTintList(tintList);
- ((ImageView) mBottomOverlay.findViewById(id.left)).setImageTintList(tintList);
- ((ImageView) mBottomOverlay.findViewById(id.right)).setImageTintList(tintList);
- }
- };
- setting.setListening(true);
- setting.onChange(false);
-
- mOverlay.addOnLayoutChangeListener(new OnLayoutChangeListener() {
- @Override
- public void onLayoutChange(View v, int left, int top, int right, int bottom,
- int oldLeft,
- int oldTop, int oldRight, int oldBottom) {
- mOverlay.removeOnLayoutChangeListener(this);
- mOverlay.animate()
- .alpha(1)
- .setDuration(1000)
- .start();
- mBottomOverlay.animate()
- .alpha(1)
- .setDuration(1000)
- .start();
- }
- });
- }
-
- private void setupPadding(int padding) {
- // Add some padding to all the content near the edge of the screen.
- StatusBar sb = getComponent(StatusBar.class);
- View statusBar = (sb != null ? sb.getStatusBarWindow() : null);
- if (statusBar != null) {
- TunablePadding.addTunablePadding(statusBar.findViewById(R.id.keyguard_header), PADDING,
- padding, FLAG_END);
-
- FragmentHostManager fragmentHostManager = FragmentHostManager.get(statusBar);
- fragmentHostManager.addTagListener(CollapsedStatusBarFragment.TAG,
- new TunablePaddingTagListener(padding, R.id.status_bar));
- fragmentHostManager.addTagListener(QS.TAG,
- new TunablePaddingTagListener(padding, R.id.header));
- }
- }
-
- private WindowManager.LayoutParams getWindowLayoutParams() {
- final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- LayoutParams.WRAP_CONTENT,
- WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
- WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
- | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
- | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
- | WindowManager.LayoutParams.FLAG_SLIPPERY
- | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
- PixelFormat.TRANSLUCENT);
- lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS
- | WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
- lp.setTitle("RoundedOverlay");
- lp.gravity = Gravity.TOP;
- return lp;
- }
-
-
- @Override
- public void onTuningChanged(String key, String newValue) {
- if (mOverlay == null) return;
- if (SIZE.equals(key)) {
- int size = mRoundedDefault;
- try {
- size = (int) (Integer.parseInt(newValue) * mDensity);
- } catch (Exception e) {
- }
- setSize(mOverlay.findViewById(R.id.left), size);
- setSize(mOverlay.findViewById(R.id.right), size);
- setSize(mBottomOverlay.findViewById(R.id.left), size);
- setSize(mBottomOverlay.findViewById(R.id.right), size);
- }
- }
-
- private void setSize(View view, int pixelSize) {
- LayoutParams params = view.getLayoutParams();
- params.width = pixelSize;
- params.height = pixelSize;
- view.setLayoutParams(params);
- }
-
- @VisibleForTesting
- static class TunablePaddingTagListener implements FragmentListener {
-
- private final int mPadding;
- private final int mId;
- private TunablePadding mTunablePadding;
-
- public TunablePaddingTagListener(int padding, int id) {
- mPadding = padding;
- mId = id;
- }
-
- @Override
- public void onFragmentViewCreated(String tag, Fragment fragment) {
- if (mTunablePadding != null) {
- mTunablePadding.destroy();
- }
- View view = fragment.getView();
- if (mId != 0) {
- view = view.findViewById(mId);
- }
- mTunablePadding = TunablePadding.addTunablePadding(view, PADDING, mPadding,
- FLAG_START | FLAG_END);
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
new file mode 100644
index 000000000000..0b3e9e5072aa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -0,0 +1,415 @@
+/*
+ * 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;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
+import static com.android.systemui.tuner.TunablePadding.FLAG_START;
+import static com.android.systemui.tuner.TunablePadding.FLAG_END;
+
+import android.app.Fragment;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.Configuration;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
+import android.provider.Settings.Secure;
+import android.support.annotation.VisibleForTesting;
+import android.util.DisplayMetrics;
+import android.view.DisplayCutout;
+import android.view.DisplayInfo;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnLayoutChangeListener;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import com.android.systemui.fragments.FragmentHostManager;
+import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
+import com.android.systemui.plugins.qs.QS;
+import com.android.systemui.qs.SecureSetting;
+import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.tuner.TunablePadding;
+import com.android.systemui.tuner.TunerService;
+import com.android.systemui.tuner.TunerService.Tunable;
+
+/**
+ * An overlay that draws screen decorations in software (e.g for rounded corners or display cutout)
+ * for antialiasing and emulation purposes.
+ */
+public class ScreenDecorations extends SystemUI implements Tunable {
+ public static final String SIZE = "sysui_rounded_size";
+ public static final String PADDING = "sysui_rounded_content_padding";
+
+ private int mRoundedDefault;
+ private View mOverlay;
+ private View mBottomOverlay;
+ private float mDensity;
+ private WindowManager mWindowManager;
+ private boolean mLandscape;
+
+ @Override
+ public void start() {
+ mWindowManager = mContext.getSystemService(WindowManager.class);
+ mRoundedDefault = mContext.getResources().getDimensionPixelSize(
+ R.dimen.rounded_corner_radius);
+ if (mRoundedDefault != 0 || shouldDrawCutout()) {
+ setupDecorations();
+ }
+ int padding = mContext.getResources().getDimensionPixelSize(
+ R.dimen.rounded_corner_content_padding);
+ if (padding != 0) {
+ setupPadding(padding);
+ }
+ }
+
+ private void setupDecorations() {
+ mOverlay = LayoutInflater.from(mContext)
+ .inflate(R.layout.rounded_corners, null);
+ ((ViewGroup)mOverlay).addView(new DisplayCutoutView(mContext, true,
+ this::updateWindowVisibilities));
+ mBottomOverlay = LayoutInflater.from(mContext)
+ .inflate(R.layout.rounded_corners, null);
+ ((ViewGroup)mBottomOverlay).addView(new DisplayCutoutView(mContext, false,
+ this::updateWindowVisibilities));
+
+ mOverlay.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+ mOverlay.setAlpha(0);
+
+ mBottomOverlay.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+ mBottomOverlay.setAlpha(0);
+
+ updateViews();
+
+ mWindowManager.addView(mOverlay, getWindowLayoutParams());
+ mWindowManager.addView(mBottomOverlay, getBottomLayoutParams());
+
+ DisplayMetrics metrics = new DisplayMetrics();
+ mWindowManager.getDefaultDisplay().getMetrics(metrics);
+ mDensity = metrics.density;
+
+ Dependency.get(TunerService.class).addTunable(this, SIZE);
+
+ // Watch color inversion and invert the overlay as needed.
+ SecureSetting setting = new SecureSetting(mContext, Dependency.get(Dependency.MAIN_HANDLER),
+ Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED) {
+ @Override
+ protected void handleValueChanged(int value, boolean observedChange) {
+ int tint = value != 0 ? Color.WHITE : Color.BLACK;
+ ColorStateList tintList = ColorStateList.valueOf(tint);
+ ((ImageView) mOverlay.findViewById(R.id.left)).setImageTintList(tintList);
+ ((ImageView) mOverlay.findViewById(R.id.right)).setImageTintList(tintList);
+ ((ImageView) mBottomOverlay.findViewById(R.id.left)).setImageTintList(tintList);
+ ((ImageView) mBottomOverlay.findViewById(R.id.right)).setImageTintList(tintList);
+ }
+ };
+ setting.setListening(true);
+ setting.onChange(false);
+
+ mOverlay.addOnLayoutChangeListener(new OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft,
+ int oldTop, int oldRight, int oldBottom) {
+ mOverlay.removeOnLayoutChangeListener(this);
+ mOverlay.animate()
+ .alpha(1)
+ .setDuration(1000)
+ .start();
+ mBottomOverlay.animate()
+ .alpha(1)
+ .setDuration(1000)
+ .start();
+ }
+ });
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ boolean newLanscape = newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE;
+ if (newLanscape != mLandscape) {
+ mLandscape = newLanscape;
+
+ if (mOverlay != null) {
+ updateLayoutParams();
+ updateViews();
+ }
+ }
+ if (shouldDrawCutout() && mOverlay == null) {
+ setupDecorations();
+ }
+ }
+
+ private void updateViews() {
+ View topLeft = mOverlay.findViewById(R.id.left);
+ View topRight = mOverlay.findViewById(R.id.right);
+ View bottomLeft = mBottomOverlay.findViewById(R.id.left);
+ View bottomRight = mBottomOverlay.findViewById(R.id.right);
+ if (mLandscape) {
+ // Flip corners
+ View tmp = topRight;
+ topRight = bottomLeft;
+ bottomLeft = tmp;
+ }
+ updateView(topLeft, Gravity.TOP | Gravity.LEFT, 0);
+ updateView(topRight, Gravity.TOP | Gravity.RIGHT, 90);
+ updateView(bottomLeft, Gravity.BOTTOM | Gravity.LEFT, 270);
+ updateView(bottomRight, Gravity.BOTTOM | Gravity.RIGHT, 180);
+
+ updateWindowVisibilities();
+ }
+
+ private void updateView(View v, int gravity, int rotation) {
+ ((FrameLayout.LayoutParams)v.getLayoutParams()).gravity = gravity;
+ v.setRotation(rotation);
+ }
+
+ private void updateWindowVisibilities() {
+ updateWindowVisibility(mOverlay);
+ updateWindowVisibility(mBottomOverlay);
+ }
+
+ private void updateWindowVisibility(View overlay) {
+ boolean visibleForCutout = shouldDrawCutout()
+ && overlay.findViewById(R.id.display_cutout).getVisibility() == View.VISIBLE;
+ boolean visibleForRoundedCorners = mRoundedDefault > 0;
+ overlay.setVisibility(visibleForCutout || visibleForRoundedCorners
+ ? View.VISIBLE : View.GONE);
+ }
+
+ private boolean shouldDrawCutout() {
+ return mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout);
+ }
+
+ private void setupPadding(int padding) {
+ // Add some padding to all the content near the edge of the screen.
+ StatusBar sb = getComponent(StatusBar.class);
+ View statusBar = (sb != null ? sb.getStatusBarWindow() : null);
+ if (statusBar != null) {
+ TunablePadding.addTunablePadding(statusBar.findViewById(R.id.keyguard_header), PADDING,
+ padding, FLAG_END);
+
+ FragmentHostManager fragmentHostManager = FragmentHostManager.get(statusBar);
+ fragmentHostManager.addTagListener(CollapsedStatusBarFragment.TAG,
+ new TunablePaddingTagListener(padding, R.id.status_bar));
+ fragmentHostManager.addTagListener(QS.TAG,
+ new TunablePaddingTagListener(padding, R.id.header));
+ }
+ }
+
+ @VisibleForTesting
+ WindowManager.LayoutParams getWindowLayoutParams() {
+ final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ LayoutParams.WRAP_CONTENT,
+ WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
+ WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
+ | WindowManager.LayoutParams.FLAG_SLIPPERY
+ | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
+ PixelFormat.TRANSLUCENT);
+ lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS
+ | WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
+ lp.setTitle("ScreenDecorOverlay");
+ lp.gravity = Gravity.TOP | Gravity.LEFT;
+ lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ if (mLandscape) {
+ lp.width = WRAP_CONTENT;
+ lp.height = MATCH_PARENT;
+ }
+ return lp;
+ }
+
+ private WindowManager.LayoutParams getBottomLayoutParams() {
+ WindowManager.LayoutParams lp = getWindowLayoutParams();
+ lp.setTitle("ScreenDecorOverlayBottom");
+ lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
+ return lp;
+ }
+
+ private void updateLayoutParams() {
+ mWindowManager.updateViewLayout(mOverlay, getWindowLayoutParams());
+ mWindowManager.updateViewLayout(mBottomOverlay, getBottomLayoutParams());
+ }
+
+ @Override
+ public void onTuningChanged(String key, String newValue) {
+ if (mOverlay == null) return;
+ if (SIZE.equals(key)) {
+ int size = mRoundedDefault;
+ try {
+ size = (int) (Integer.parseInt(newValue) * mDensity);
+ } catch (Exception e) {
+ }
+ setSize(mOverlay.findViewById(R.id.left), size);
+ setSize(mOverlay.findViewById(R.id.right), size);
+ setSize(mBottomOverlay.findViewById(R.id.left), size);
+ setSize(mBottomOverlay.findViewById(R.id.right), size);
+ }
+ }
+
+ private void setSize(View view, int pixelSize) {
+ LayoutParams params = view.getLayoutParams();
+ params.width = pixelSize;
+ params.height = pixelSize;
+ view.setLayoutParams(params);
+ }
+
+ @VisibleForTesting
+ static class TunablePaddingTagListener implements FragmentListener {
+
+ private final int mPadding;
+ private final int mId;
+ private TunablePadding mTunablePadding;
+
+ public TunablePaddingTagListener(int padding, int id) {
+ mPadding = padding;
+ mId = id;
+ }
+
+ @Override
+ public void onFragmentViewCreated(String tag, Fragment fragment) {
+ if (mTunablePadding != null) {
+ mTunablePadding.destroy();
+ }
+ View view = fragment.getView();
+ if (mId != 0) {
+ view = view.findViewById(mId);
+ }
+ mTunablePadding = TunablePadding.addTunablePadding(view, PADDING, mPadding,
+ FLAG_START | FLAG_END);
+ }
+ }
+
+ public static class DisplayCutoutView extends View implements DisplayManager.DisplayListener {
+
+ private final DisplayInfo mInfo = new DisplayInfo();
+ private final Paint mPaint = new Paint();
+ private final Rect mBoundingRect = new Rect();
+ private final Path mBoundingPath = new Path();
+ private final int[] mLocation = new int[2];
+ private final boolean mStart;
+ private final Runnable mVisibilityChangedListener;
+
+ public DisplayCutoutView(Context context, boolean start,
+ Runnable visibilityChangedListener) {
+ super(context);
+ mStart = start;
+ mVisibilityChangedListener = visibilityChangedListener;
+ setId(R.id.display_cutout);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mContext.getSystemService(DisplayManager.class).registerDisplayListener(this,
+ getHandler());
+ update();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ getLocationOnScreen(mLocation);
+ canvas.translate(-mLocation[0], -mLocation[1]);
+ if (!mBoundingPath.isEmpty()) {
+ mPaint.setColor(Color.BLACK);
+ mPaint.setStyle(Paint.Style.FILL);
+ canvas.drawPath(mBoundingPath, mPaint);
+ }
+ }
+
+ @Override
+ public void onDisplayAdded(int displayId) {
+ }
+
+ @Override
+ public void onDisplayRemoved(int displayId) {
+ }
+
+ @Override
+ public void onDisplayChanged(int displayId) {
+ if (displayId == getDisplay().getDisplayId()) {
+ update();
+ }
+ }
+
+ private void update() {
+ requestLayout();
+ getDisplay().getDisplayInfo(mInfo);
+ mBoundingRect.setEmpty();
+ mBoundingPath.reset();
+ int newVisible;
+ if (hasCutout()) {
+ mBoundingRect.set(mInfo.displayCutout.getBoundingRect());
+ mInfo.displayCutout.getBounds().getBoundaryPath(mBoundingPath);
+ newVisible = VISIBLE;
+ } else {
+ newVisible = GONE;
+ }
+ if (newVisible != getVisibility()) {
+ setVisibility(newVisible);
+ mVisibilityChangedListener.run();
+ }
+ }
+
+ private boolean hasCutout() {
+ if (mInfo.displayCutout == null) {
+ return false;
+ }
+ DisplayCutout displayCutout = mInfo.displayCutout.calculateRelativeTo(
+ new Rect(0, 0, mInfo.logicalWidth, mInfo.logicalHeight));
+ if (mStart) {
+ return displayCutout.getSafeInsetLeft() > 0
+ || displayCutout.getSafeInsetTop() > 0;
+ } else {
+ return displayCutout.getSafeInsetRight() > 0
+ || displayCutout.getSafeInsetBottom() > 0;
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ if (mBoundingRect.isEmpty()) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ return;
+ }
+ setMeasuredDimension(
+ resolveSizeAndState(mBoundingRect.width(), widthMeasureSpec, 0),
+ resolveSizeAndState(mBoundingRect.height(), heightMeasureSpec, 0));
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java b/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java
new file mode 100644
index 000000000000..302face14c1a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.slice.SliceManager;
+import android.app.slice.SliceProvider;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.DialogInterface.OnDismissListener;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.CheckBox;
+import android.widget.TextView;
+
+public class SlicePermissionActivity extends Activity implements OnClickListener,
+ OnDismissListener {
+
+ private static final String TAG = "SlicePermissionActivity";
+
+ private CheckBox mAllCheckbox;
+
+ private Uri mUri;
+ private String mCallingPkg;
+ private String mProviderPkg;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mUri = getIntent().getParcelableExtra(SliceProvider.EXTRA_BIND_URI);
+ mCallingPkg = getIntent().getStringExtra(SliceProvider.EXTRA_PKG);
+ mProviderPkg = getIntent().getStringExtra(SliceProvider.EXTRA_PROVIDER_PKG);
+
+ try {
+ PackageManager pm = getPackageManager();
+ CharSequence app1 = pm.getApplicationInfo(mCallingPkg, 0).loadLabel(pm);
+ CharSequence app2 = pm.getApplicationInfo(mProviderPkg, 0).loadLabel(pm);
+ AlertDialog dialog = new AlertDialog.Builder(this)
+ .setTitle(getString(R.string.slice_permission_title, app1, app2))
+ .setView(R.layout.slice_permission_request)
+ .setNegativeButton(R.string.slice_permission_deny, this)
+ .setPositiveButton(R.string.slice_permission_allow, this)
+ .setOnDismissListener(this)
+ .show();
+ TextView t1 = dialog.getWindow().getDecorView().findViewById(R.id.text1);
+ t1.setText(getString(R.string.slice_permission_text_1, app2));
+ TextView t2 = dialog.getWindow().getDecorView().findViewById(R.id.text2);
+ t2.setText(getString(R.string.slice_permission_text_2, app2));
+ mAllCheckbox = dialog.getWindow().getDecorView().findViewById(
+ R.id.slice_permission_checkbox);
+ mAllCheckbox.setText(getString(R.string.slice_permission_checkbox, app1));
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Couldn't find package", e);
+ finish();
+ }
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ getSystemService(SliceManager.class).grantPermissionFromUser(mUri, mCallingPkg,
+ mAllCheckbox.isChecked());
+ }
+ finish();
+ }
+
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ finish();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 4b377153e558..a64ce296c76c 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -21,6 +21,7 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.annotation.NonNull;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.RectF;
@@ -83,7 +84,6 @@ public class SwipeHelper implements Gefingerpoken {
private boolean mMenuRowIntercepting;
private boolean mLongPressSent;
- private LongPressListener mLongPressListener;
private Runnable mWatchLongPress;
private final long mLongPressTimeout;
@@ -115,10 +115,6 @@ public class SwipeHelper implements Gefingerpoken {
mFlingAnimationUtils = new FlingAnimationUtils(context, getMaxEscapeAnimDuration() / 1000f);
}
- public void setLongPressListener(LongPressListener listener) {
- mLongPressListener = listener;
- }
-
public void setDensityScale(float densityScale) {
mDensityScale = densityScale;
}
@@ -257,7 +253,7 @@ public class SwipeHelper implements Gefingerpoken {
}
}
- public void removeLongPressCallback() {
+ public void cancelLongPress() {
if (mWatchLongPress != null) {
mHandler.removeCallbacks(mWatchLongPress);
mWatchLongPress = null;
@@ -288,33 +284,27 @@ public class SwipeHelper implements Gefingerpoken {
mInitialTouchPos = getPos(ev);
mPerpendicularInitialTouchPos = getPerpendicularPos(ev);
mTranslation = getTranslation(mCurrView);
- if (mLongPressListener != null) {
- if (mWatchLongPress == null) {
- mWatchLongPress = new Runnable() {
- @Override
- public void run() {
- if (mCurrView != null && !mLongPressSent) {
- mLongPressSent = true;
+ if (mWatchLongPress == null) {
+ mWatchLongPress = new Runnable() {
+ @Override
+ public void run() {
+ if (mCurrView != null && !mLongPressSent) {
+ mLongPressSent = true;
+ mCurrView.getLocationOnScreen(mTmpPos);
+ final int x = (int) ev.getRawX() - mTmpPos[0];
+ final int y = (int) ev.getRawY() - mTmpPos[1];
+ if (mCurrView instanceof ExpandableNotificationRow) {
mCurrView.sendAccessibilityEvent(
AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
- mCurrView.getLocationOnScreen(mTmpPos);
- final int x = (int) ev.getRawX() - mTmpPos[0];
- final int y = (int) ev.getRawY() - mTmpPos[1];
- MenuItem menuItem = null;
- if (mCurrView instanceof ExpandableNotificationRow) {
- menuItem = ((ExpandableNotificationRow) mCurrView)
- .getProvider().getLongpressMenuItem(mContext);
- }
- if (menuItem != null) {
- mLongPressListener.onLongPress(mCurrView, x, y,
- menuItem);
- }
+ ExpandableNotificationRow currRow =
+ (ExpandableNotificationRow) mCurrView;
+ currRow.doLongClickCallback(x, y);
}
}
- };
- }
- mHandler.postDelayed(mWatchLongPress, mLongPressTimeout);
+ }
+ };
}
+ mHandler.postDelayed(mWatchLongPress, mLongPressTimeout);
}
break;
@@ -327,11 +317,13 @@ public class SwipeHelper implements Gefingerpoken {
float deltaPerpendicular = perpendicularPos - mPerpendicularInitialTouchPos;
if (Math.abs(delta) > mPagingTouchSlop
&& Math.abs(delta) > Math.abs(deltaPerpendicular)) {
- mCallback.onBeginDrag(mCurrView);
- mDragging = true;
- mInitialTouchPos = getPos(ev);
- mTranslation = getTranslation(mCurrView);
- removeLongPressCallback();
+ if (mCallback.canChildBeDragged(mCurrView)) {
+ mCallback.onBeginDrag(mCurrView);
+ mDragging = true;
+ mInitialTouchPos = getPos(ev);
+ mTranslation = getTranslation(mCurrView);
+ }
+ cancelLongPress();
}
}
break;
@@ -343,7 +335,7 @@ public class SwipeHelper implements Gefingerpoken {
mCurrView = null;
mLongPressSent = false;
mMenuRowIntercepting = false;
- removeLongPressCallback();
+ cancelLongPress();
if (captured) return true;
break;
}
@@ -586,7 +578,7 @@ public class SwipeHelper implements Gefingerpoken {
// We are not doing anything, make sure the long press callback
// is not still ticking like a bomb waiting to go off.
- removeLongPressCallback();
+ cancelLongPress();
return false;
}
}
@@ -733,16 +725,10 @@ public class SwipeHelper implements Gefingerpoken {
* @return The factor the falsing threshold should be multiplied with
*/
float getFalsingThresholdFactor();
- }
- /**
- * Equivalent to View.OnLongClickListener with coordinates
- */
- public interface LongPressListener {
/**
- * Equivalent to {@link View.OnLongClickListener#onLongClick(View)} with coordinates
- * @return whether the longpress was handled
+ * @return If true, the given view is draggable.
*/
- boolean onLongPress(View v, int x, int y, MenuItem item);
+ default boolean canChildBeDragged(@NonNull View animView) { return true; }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/SysUIToast.java b/packages/SystemUI/src/com/android/systemui/SysUIToast.java
index 89bc82f87930..43b918dbea73 100644
--- a/packages/SystemUI/src/com/android/systemui/SysUIToast.java
+++ b/packages/SystemUI/src/com/android/systemui/SysUIToast.java
@@ -15,13 +15,19 @@
*/
package com.android.systemui;
+import android.annotation.StringRes;
import android.content.Context;
import android.view.WindowManager;
import android.widget.Toast;
+import static android.widget.Toast.Duration;
public class SysUIToast {
- public static Toast makeText(Context context, CharSequence text, int duration) {
+ public static Toast makeText(Context context, @StringRes int resId, @Duration int duration) {
+ return makeText(context, context.getString(resId), duration);
+ }
+
+ public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
Toast toast = Toast.makeText(context, text, duration);
toast.getWindowParams().privateFlags |=
WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 9adafda7adf5..2c0e95b5af26 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -37,9 +37,7 @@ import com.android.systemui.keyboard.KeyboardUI;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.media.RingtonePlayer;
import com.android.systemui.pip.PipUI;
-import com.android.systemui.plugins.GlobalActions;
import com.android.systemui.plugins.OverlayPlugin;
-import com.android.systemui.plugins.Plugin;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.power.PowerUI;
@@ -54,6 +52,7 @@ import com.android.systemui.util.NotificationChannels;
import com.android.systemui.util.leak.GarbageMonitor;
import com.android.systemui.volume.VolumeUI;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
@@ -62,48 +61,13 @@ import java.util.Map;
*/
public class SystemUIApplication extends Application implements SysUiServiceProvider {
- private static final String TAG = "SystemUIService";
+ public static final String TAG = "SystemUIService";
private static final boolean DEBUG = false;
/**
- * The classes of the stuff to start.
- */
- private final Class<?>[] SERVICES = new Class[] {
- Dependency.class,
- NotificationChannels.class,
- CommandQueue.CommandQueueStart.class,
- KeyguardViewMediator.class,
- Recents.class,
- VolumeUI.class,
- Divider.class,
- SystemBars.class,
- StorageNotification.class,
- PowerUI.class,
- RingtonePlayer.class,
- KeyboardUI.class,
- PipUI.class,
- ShortcutKeyDispatcher.class,
- VendorServices.class,
- GarbageMonitor.Service.class,
- LatencyTester.class,
- GlobalActionsComponent.class,
- RoundedCorners.class,
- };
-
- /**
- * The classes of the stuff to start for each user. This is a subset of the services listed
- * above.
- */
- private final Class<?>[] SERVICES_PER_USER = new Class[] {
- Dependency.class,
- NotificationChannels.class,
- Recents.class
- };
-
- /**
* Hold a reference on the stuff we start.
*/
- private final SystemUI[] mServices = new SystemUI[SERVICES.length];
+ private SystemUI[] mServices;
private boolean mServicesStarted;
private boolean mBootCompleted;
private final Map<Class<?>, Object> mComponents = new HashMap<>();
@@ -149,7 +113,7 @@ public class SystemUIApplication extends Application implements SysUiServiceProv
// been broadcasted on startup for the primary SystemUI process. Instead, for
// components which require the SystemUI component to be initialized per-user, we
// start those components now for the current non-system user.
- startServicesIfNeeded(SERVICES_PER_USER);
+ startSecondaryUserServicesIfNeeded();
}
}
@@ -161,7 +125,8 @@ public class SystemUIApplication extends Application implements SysUiServiceProv
*/
public void startServicesIfNeeded() {
- startServicesIfNeeded(SERVICES);
+ String[] names = getResources().getStringArray(R.array.config_systemUIServiceComponents);
+ startServicesIfNeeded(names);
}
/**
@@ -171,13 +136,16 @@ public class SystemUIApplication extends Application implements SysUiServiceProv
* <p>This method must only be called from the main thread.</p>
*/
void startSecondaryUserServicesIfNeeded() {
- startServicesIfNeeded(SERVICES_PER_USER);
+ String[] names =
+ getResources().getStringArray(R.array.config_systemUIServiceComponentsPerUser);
+ startServicesIfNeeded(names);
}
- private void startServicesIfNeeded(Class<?>[] services) {
+ private void startServicesIfNeeded(String[] services) {
if (mServicesStarted) {
return;
}
+ mServices = new SystemUI[services.length];
if (!mBootCompleted) {
// check to see if maybe it was already completed long before we began
@@ -195,14 +163,16 @@ public class SystemUIApplication extends Application implements SysUiServiceProv
log.traceBegin("StartServices");
final int N = services.length;
for (int i = 0; i < N; i++) {
- Class<?> cl = services[i];
- if (DEBUG) Log.d(TAG, "loading: " + cl);
- log.traceBegin("StartServices" + cl.getSimpleName());
+ String clsName = services[i];
+ if (DEBUG) Log.d(TAG, "loading: " + clsName);
+ log.traceBegin("StartServices" + clsName);
long ti = System.currentTimeMillis();
+ Class cls;
try {
-
- Object newService = SystemUIFactory.getInstance().createInstance(cl);
- mServices[i] = (SystemUI) ((newService == null) ? cl.newInstance() : newService);
+ cls = Class.forName(clsName);
+ mServices[i] = (SystemUI) cls.newInstance();
+ } catch(ClassNotFoundException ex){
+ throw new RuntimeException(ex);
} catch (IllegalAccessException ex) {
throw new RuntimeException(ex);
} catch (InstantiationException ex) {
@@ -218,7 +188,7 @@ public class SystemUIApplication extends Application implements SysUiServiceProv
// Warn if initialization of component takes too long
ti = System.currentTimeMillis() - ti;
if (ti > 1000) {
- Log.w(TAG, "Initialization of " + cl.getName() + " took " + ti + " ms");
+ Log.w(TAG, "Initialization of " + cls.getName() + " took " + ti + " ms");
}
if (mBootCompleted) {
mServices[i].onBootCompleted();
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 0c067ff38295..d1834e921d20 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -16,29 +16,43 @@
package com.android.systemui;
+import android.app.AlarmManager;
import android.content.Context;
import android.util.ArrayMap;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
+import com.android.internal.logging.MetricsLogger;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.Dependency.DependencyProvider;
import com.android.systemui.keyguard.DismissCallbackRegistry;
+import com.android.systemui.qs.QSTileHost;
import com.android.systemui.statusbar.KeyguardIndicationController;
+import com.android.systemui.statusbar.NotificationEntryManager;
+import com.android.systemui.statusbar.NotificationGutsManager;
+import com.android.systemui.statusbar.NotificationListener;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.NotificationLogger;
+import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationViewHierarchyManager;
import com.android.systemui.statusbar.ScrimView;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.KeyguardBouncer;
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.phone.LockIcon;
import com.android.systemui.statusbar.phone.LockscreenWallpaper;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
-import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.qs.QSTileHost;
import com.android.systemui.statusbar.phone.ScrimController;
+import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.volume.VolumeDialogControllerImpl;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.SmartReplyConstants;
import java.util.function.Consumer;
@@ -86,10 +100,10 @@ public class SystemUIFactory {
public ScrimController createScrimController(LightBarController lightBarController,
ScrimView scrimBehind, ScrimView scrimInFront, View headsUpScrim,
- LockscreenWallpaper lockscreenWallpaper,
- Consumer<Boolean> scrimVisibleListener) {
+ LockscreenWallpaper lockscreenWallpaper, Consumer<Integer> scrimVisibleListener,
+ DozeParameters dozeParameters, AlarmManager alarmManager) {
return new ScrimController(lightBarController, scrimBehind, scrimInFront, headsUpScrim,
- scrimVisibleListener);
+ scrimVisibleListener, dozeParameters, alarmManager);
}
public NotificationIconAreaController createNotificationIconAreaController(Context context,
@@ -107,10 +121,22 @@ public class SystemUIFactory {
return new QSTileHost(context, statusBar, iconController);
}
- public <T> T createInstance(Class<T> classType) {
- return null;
- }
-
public void injectDependencies(ArrayMap<Object, DependencyProvider> providers,
- Context context) { }
+ Context context) {
+ providers.put(NotificationLockscreenUserManager.class,
+ () -> new NotificationLockscreenUserManager(context));
+ providers.put(VisualStabilityManager.class, VisualStabilityManager::new);
+ providers.put(NotificationGroupManager.class, NotificationGroupManager::new);
+ providers.put(NotificationMediaManager.class, () -> new NotificationMediaManager(context));
+ providers.put(NotificationGutsManager.class, () -> new NotificationGutsManager(context));
+ providers.put(NotificationRemoteInputManager.class,
+ () -> new NotificationRemoteInputManager(context));
+ providers.put(SmartReplyConstants.class,
+ () -> new SmartReplyConstants(Dependency.get(Dependency.MAIN_HANDLER), context));
+ providers.put(NotificationListener.class, () -> new NotificationListener(context));
+ providers.put(NotificationLogger.class, NotificationLogger::new);
+ providers.put(NotificationViewHierarchyManager.class,
+ () -> new NotificationViewHierarchyManager(context));
+ providers.put(NotificationEntryManager.class, () -> new NotificationEntryManager(context));
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIService.java b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
index 11e0f289e6a4..ddf0bd0cbab4 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIService.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
@@ -20,11 +20,15 @@ import android.app.Service;
import android.content.Intent;
import android.os.Build;
import android.os.IBinder;
+import android.os.Process;
import android.os.SystemProperties;
+import android.util.Slog;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import com.android.internal.os.BinderInternal;
+
public class SystemUIService extends Service {
@Override
@@ -36,6 +40,21 @@ public class SystemUIService extends Service {
if (Build.IS_DEBUGGABLE && SystemProperties.getBoolean("debug.crash_sysui", false)) {
throw new RuntimeException();
}
+
+ if (Build.IS_DEBUGGABLE) {
+ // b/71353150 - looking for leaked binder proxies
+ BinderInternal.nSetBinderProxyCountEnabled(true);
+ BinderInternal.nSetBinderProxyCountWatermarks(1000,900);
+ BinderInternal.setBinderProxyCountCallback(
+ new BinderInternal.BinderProxyLimitListener() {
+ @Override
+ public void onLimitReached(int uid) {
+ Slog.w(SystemUIApplication.TAG,
+ "uid " + uid + " sent too many Binder proxies to uid "
+ + Process.myUid());
+ }
+ }, Dependency.get(Dependency.MAIN_HANDLER));
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/analytics/DataCollector.java b/packages/SystemUI/src/com/android/systemui/analytics/DataCollector.java
index 931a99415615..69e347c9476d 100644
--- a/packages/SystemUI/src/com/android/systemui/analytics/DataCollector.java
+++ b/packages/SystemUI/src/com/android/systemui/analytics/DataCollector.java
@@ -463,4 +463,8 @@ public class DataCollector implements SensorEventListener {
public boolean isReportingEnabled() {
return mAllowReportRejectedTouch;
}
+
+ public void onFalsingSessionStarted() {
+ sessionEntrypoint();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java
new file mode 100644
index 000000000000..a89a8ef5b70e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.car;
+
+import android.content.Context;
+import android.service.notification.StatusBarNotification;
+
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.NotificationData;
+import com.android.systemui.statusbar.NotificationEntryManager;
+
+public class CarNotificationEntryManager extends NotificationEntryManager {
+ public CarNotificationEntryManager(Context context) {
+ super(context);
+ }
+
+ /**
+ * Returns the
+ * {@link com.android.systemui.statusbar.ExpandableNotificationRow.LongPressListener} that will
+ * be triggered when a notification card is long-pressed.
+ */
+ @Override
+ public ExpandableNotificationRow.LongPressListener getNotificationLongClicker() {
+ // For the automative use case, we do not want to the user to be able to interact with
+ // a notification other than a regular click. As a result, just return null for the
+ // long click listener.
+ return null;
+ }
+
+ @Override
+ public boolean shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn) {
+ // Because space is usually constrained in the auto use-case, there should not be a
+ // pinned notification when the shade has been expanded. Ensure this by not pinning any
+ // notification if the shade is already opened.
+ if (!mPresenter.isPresenterFullyCollapsed()) {
+ return false;
+ }
+
+ return super.shouldPeek(entry, sbn);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java
index 5a19e7dc60ed..245d240017e1 100644
--- a/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java
@@ -20,8 +20,7 @@ import android.util.ArrayMap;
import com.android.systemui.Dependency.DependencyProvider;
import com.android.systemui.SystemUIFactory;
-import com.android.systemui.plugins.VolumeDialogController;
-import com.android.systemui.volume.car.CarVolumeDialogController;
+import com.android.systemui.statusbar.NotificationEntryManager;
/**
* Class factory to provide car specific SystemUI components.
@@ -31,6 +30,7 @@ public class CarSystemUIFactory extends SystemUIFactory {
public void injectDependencies(ArrayMap<Object, DependencyProvider> providers,
Context context) {
super.injectDependencies(providers, context);
- providers.put(VolumeDialogController.class, () -> new CarVolumeDialogController(context));
+ providers.put(NotificationEntryManager.class,
+ () -> new CarNotificationEntryManager(context));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
new file mode 100644
index 000000000000..afc9629ecede
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.charging;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+import android.util.Slog;
+import android.view.Gravity;
+import android.view.View;
+import android.view.WindowManager;
+
+/**
+ * A WirelessChargingAnimation is a view containing view + animation for wireless charging.
+ * @hide
+ */
+public class WirelessChargingAnimation {
+
+ public static final long DURATION = 1133;
+ private static final String TAG = "WirelessChargingView";
+ private static final boolean LOCAL_LOGV = false;
+
+ private final WirelessChargingView mCurrentWirelessChargingView;
+ private static WirelessChargingView mPreviousWirelessChargingView;
+
+ public interface Callback {
+ void onAnimationStarting();
+ void onAnimationEnded();
+ }
+
+ /**
+ * Constructs an empty WirelessChargingAnimation object. If looper is null,
+ * Looper.myLooper() is used. Must set
+ * {@link WirelessChargingAnimation#mCurrentWirelessChargingView}
+ * before calling {@link #show} - can be done through {@link #makeWirelessChargingAnimation}.
+ * @hide
+ */
+ public WirelessChargingAnimation(@NonNull Context context, @Nullable Looper looper, int
+ batteryLevel, Callback callback) {
+ mCurrentWirelessChargingView = new WirelessChargingView(context, looper,
+ batteryLevel, callback);
+ }
+
+ /**
+ * Creates a wireless charging animation object populated with next view.
+ * @hide
+ */
+ public static WirelessChargingAnimation makeWirelessChargingAnimation(@NonNull Context context,
+ @Nullable Looper looper, int batteryLevel, Callback callback) {
+ return new WirelessChargingAnimation(context, looper, batteryLevel, callback);
+ }
+
+ /**
+ * Show the view for the specified duration.
+ */
+ public void show() {
+ if (mCurrentWirelessChargingView == null ||
+ mCurrentWirelessChargingView.mNextView == null) {
+ throw new RuntimeException("setView must have been called");
+ }
+
+ if (mPreviousWirelessChargingView != null) {
+ mPreviousWirelessChargingView.hide(0);
+ }
+
+ mPreviousWirelessChargingView = mCurrentWirelessChargingView;
+ mCurrentWirelessChargingView.show();
+ mCurrentWirelessChargingView.hide(DURATION);
+ }
+
+ private static class WirelessChargingView {
+ private static final int SHOW = 0;
+ private static final int HIDE = 1;
+
+ private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
+ private final Handler mHandler;
+
+ private int mGravity;
+
+ private View mView;
+ private View mNextView;
+ private WindowManager mWM;
+ private Callback mCallback;
+
+ public WirelessChargingView(Context context, @Nullable Looper looper, int batteryLevel,
+ Callback callback) {
+ mCallback = callback;
+ mNextView = new WirelessChargingLayout(context, batteryLevel);
+ mGravity = Gravity.CENTER_HORIZONTAL | Gravity.CENTER;
+
+ final WindowManager.LayoutParams params = mParams;
+ params.height = WindowManager.LayoutParams.WRAP_CONTENT;
+ params.width = WindowManager.LayoutParams.WRAP_CONTENT;
+ params.format = PixelFormat.TRANSLUCENT;
+
+ params.type = WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;
+ params.setTitle("Charging Animation");
+ params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+ | WindowManager.LayoutParams.FLAG_DIM_BEHIND;
+
+ params.dimAmount = .3f;
+
+ if (looper == null) {
+ // Use Looper.myLooper() if looper is not specified.
+ looper = Looper.myLooper();
+ if (looper == null) {
+ throw new RuntimeException(
+ "Can't display wireless animation on a thread that has not called "
+ + "Looper.prepare()");
+ }
+ }
+
+ mHandler = new Handler(looper, null) {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case SHOW: {
+ handleShow();
+ break;
+ }
+ case HIDE: {
+ handleHide();
+ // Don't do this in handleHide() because it is also invoked by
+ // handleShow()
+ mNextView = null;
+ break;
+ }
+ }
+ }
+ };
+ }
+
+ public void show() {
+ if (LOCAL_LOGV) Log.v(TAG, "SHOW: " + this);
+ mHandler.obtainMessage(SHOW).sendToTarget();
+ }
+
+ public void hide(long duration) {
+ mHandler.removeMessages(HIDE);
+
+ if (LOCAL_LOGV) Log.v(TAG, "HIDE: " + this);
+ mHandler.sendMessageDelayed(Message.obtain(mHandler, HIDE), duration);
+ }
+
+ private void handleShow() {
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView + " mNextView="
+ + mNextView);
+ }
+
+ if (mView != mNextView) {
+ // remove the old view if necessary
+ handleHide();
+ mView = mNextView;
+ Context context = mView.getContext().getApplicationContext();
+ String packageName = mView.getContext().getOpPackageName();
+ if (context == null) {
+ context = mView.getContext();
+ }
+ mWM = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ mParams.packageName = packageName;
+ mParams.hideTimeoutMilliseconds = DURATION;
+
+ if (mView.getParent() != null) {
+ if (LOCAL_LOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
+ mWM.removeView(mView);
+ }
+ if (LOCAL_LOGV) Log.v(TAG, "ADD! " + mView + " in " + this);
+
+ try {
+ if (mCallback != null) {
+ mCallback.onAnimationStarting();
+ }
+ mWM.addView(mView, mParams);
+ } catch (WindowManager.BadTokenException e) {
+ Slog.d(TAG, "Unable to add wireless charging view. " + e);
+ }
+ }
+ }
+
+ private void handleHide() {
+ if (LOCAL_LOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);
+ if (mView != null) {
+ if (mView.getParent() != null) {
+ if (LOCAL_LOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
+ if (mCallback != null) {
+ mCallback.onAnimationEnded();
+ }
+ mWM.removeViewImmediate(mView);
+ }
+
+ mView = null;
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
new file mode 100644
index 000000000000..8f87d647a0e3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.charging;
+
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.animation.PathInterpolator;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+
+import java.text.NumberFormat;
+
+/**
+ * @hide
+ */
+public class WirelessChargingLayout extends FrameLayout {
+ private final static int UNKNOWN_BATTERY_LEVEL = -1;
+
+ public WirelessChargingLayout(Context context) {
+ super(context);
+ init(context, null);
+ }
+
+ public WirelessChargingLayout(Context context, int batterylLevel) {
+ super(context);
+ init(context, null, batterylLevel);
+ }
+
+ public WirelessChargingLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context, attrs);
+ }
+
+ private void init(Context c, AttributeSet attrs) {
+ init(c, attrs, -1);
+ }
+
+ private void init(Context context, AttributeSet attrs, int batteryLevel) {
+ final int mBatteryLevel = batteryLevel;
+ inflate(context, R.layout.wireless_charging_layout, this);
+
+ // where the circle animation occurs:
+ final WirelessChargingView mChargingView = findViewById(R.id.wireless_charging_view);
+
+ // amount of battery:
+ final TextView mPercentage = findViewById(R.id.wireless_charging_percentage);
+
+ // (optional) time until full charge if available
+ final TextView mSecondaryText = findViewById(R.id.wireless_charging_secondary_text);
+
+ if (batteryLevel != UNKNOWN_BATTERY_LEVEL) {
+ mPercentage.setText(NumberFormat.getPercentInstance().format(mBatteryLevel / 100f));
+ mPercentage.setAlpha(0);
+ }
+
+ final long chargingAnimationFadeStartOffset = (long) context.getResources().getInteger(
+ R.integer.wireless_charging_fade_offset);
+ final long chargingAnimationFadeDuration = (long) context.getResources().getInteger(
+ R.integer.wireless_charging_fade_duration);
+ final int batteryLevelTextSizeStart = context.getResources().getDimensionPixelSize(
+ R.dimen.wireless_charging_anim_battery_level_text_size_start);
+ final int batteryLevelTextSizeEnd = context.getResources().getDimensionPixelSize(
+ R.dimen.wireless_charging_anim_battery_level_text_size_end);
+
+ // Animation Scale: battery percentage text scales from 0% to 100%
+ ValueAnimator textSizeAnimator = ObjectAnimator.ofFloat(mPercentage, "textSize",
+ batteryLevelTextSizeStart, batteryLevelTextSizeEnd);
+ textSizeAnimator.setInterpolator(new PathInterpolator(0, 0, 0, 1));
+ textSizeAnimator.setDuration((long) context.getResources().getInteger(
+ R.integer.wireless_charging_battery_level_text_scale_animation_duration));
+
+ // Animation Opacity: battery percentage text transitions from 0 to 1 opacity
+ ValueAnimator textOpacityAnimator = ObjectAnimator.ofFloat(mPercentage, "alpha", 0, 1);
+ textOpacityAnimator.setInterpolator(Interpolators.LINEAR);
+ textOpacityAnimator.setDuration((long) context.getResources().getInteger(
+ R.integer.wireless_charging_battery_level_text_opacity_duration));
+ textOpacityAnimator.setStartDelay((long) context.getResources().getInteger(
+ R.integer.wireless_charging_anim_opacity_offset));
+
+ // Animation Opacity: battery percentage text fades from 1 to 0 opacity
+ ValueAnimator textFadeAnimator = ObjectAnimator.ofFloat(mPercentage, "alpha", 1, 0);
+ textFadeAnimator.setDuration(chargingAnimationFadeDuration);
+ textFadeAnimator.setInterpolator(Interpolators.LINEAR);
+ textFadeAnimator.setStartDelay(chargingAnimationFadeStartOffset);
+
+ // Animation Opacity: wireless charging circle animation fades from 1 to 0 opacity
+ ValueAnimator circleFadeAnimator = ObjectAnimator.ofFloat(mChargingView, "alpha",
+ 1, 0);
+ circleFadeAnimator.setDuration(chargingAnimationFadeDuration);
+ circleFadeAnimator.setInterpolator(Interpolators.LINEAR);
+ circleFadeAnimator.setStartDelay(chargingAnimationFadeStartOffset);
+
+ // Animation Opacity: secondary text animation fades from 1 to 0 opacity
+ ValueAnimator secondaryTextFadeAnimator = ObjectAnimator.ofFloat(mSecondaryText, "alpha",
+ 1, 0);
+ circleFadeAnimator.setDuration(chargingAnimationFadeDuration);
+ secondaryTextFadeAnimator.setInterpolator(Interpolators.LINEAR);
+ secondaryTextFadeAnimator.setStartDelay(chargingAnimationFadeStartOffset);
+
+ // play all animations together
+ AnimatorSet animatorSet = new AnimatorSet();
+ animatorSet.playTogether(textSizeAnimator, textOpacityAnimator, textFadeAnimator,
+ circleFadeAnimator, secondaryTextFadeAnimator);
+ animatorSet.start();
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingView.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingView.java
new file mode 100644
index 000000000000..19c6dc1ceb1f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingView.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.charging;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.animation.Interpolator;
+
+import com.android.settingslib.Utils;
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+
+final class WirelessChargingView extends View {
+
+ private Interpolator mInterpolator;
+ private float mPathGone;
+ private float mInterpolatedPathGone;
+ private long mAnimationStartTime;
+ private long mScaleDotsDuration;
+
+ private boolean mFinishedAnimatingDots = false;
+ private int mNumDots;
+
+ private double mAngleOffset;
+ private double mCurrAngleOffset;
+
+ private int mDotsRadiusStart;
+ private int mDotsRadiusEnd;
+ private int mCurrDotRadius;
+
+ private int mMainCircleStartRadius;
+ private int mMainCircleEndRadius;
+ private int mMainCircleCurrentRadius;
+
+ private int mCenterX;
+ private int mCenterY;
+
+ private Paint mPaint;
+ private Context mContext;
+
+ public WirelessChargingView(Context context) {
+ super(context);
+ init(context, null);
+ }
+
+ public WirelessChargingView(Context context, AttributeSet attr) {
+ super(context, attr);
+ init(context, attr);
+ }
+
+ public WirelessChargingView(Context context, AttributeSet attr, int styleAttr) {
+ super(context, attr, styleAttr);
+ init(context, attr);
+ }
+
+ public void init(Context context, AttributeSet attr) {
+ mContext = context;
+
+ mDotsRadiusStart = context.getResources().getDimensionPixelSize(
+ R.dimen.wireless_charging_dots_radius_start);
+ mDotsRadiusEnd = context.getResources().getDimensionPixelSize(
+ R.dimen.wireless_charging_dots_radius_end);
+
+ mMainCircleStartRadius = context.getResources().getDimensionPixelSize(
+ R.dimen.wireless_charging_circle_radius_start);
+ mMainCircleEndRadius = context.getResources().getDimensionPixelSize(
+ R.dimen.wireless_charging_circle_radius_end);
+ mMainCircleCurrentRadius = mMainCircleStartRadius;
+
+ mAngleOffset = context.getResources().getInteger(R.integer.wireless_charging_angle_offset);
+ mScaleDotsDuration = (long) context.getResources().getInteger(
+ R.integer.wireless_charging_scale_dots_duration);
+ mNumDots = context.getResources().getInteger(R.integer.wireless_charging_num_dots);
+
+ setupPaint();
+ mInterpolator = Interpolators.LINEAR_OUT_SLOW_IN;
+ }
+
+ private void setupPaint() {
+ mPaint = new Paint();
+ mPaint.setColor(Utils.getColorAttr(mContext, R.attr.wallpaperTextColor));
+ }
+
+ @Override
+ protected void onDraw(final Canvas canvas) {
+ super.onDraw(canvas);
+
+ if (mAnimationStartTime == 0) {
+ mAnimationStartTime = System.currentTimeMillis();
+ }
+
+ updateDrawingParameters();
+ drawCircles(canvas);
+
+ if (!mFinishedAnimatingDots) {
+ invalidate();
+ }
+ }
+
+ /**
+ * Draws a larger circle of radius {@link WirelessChargingView#mMainCircleEndRadius} composed of
+ * {@link WirelessChargingView#mNumDots} smaller circles
+ * @param canvas
+ */
+ private void drawCircles(Canvas canvas) {
+ mCenterX = canvas.getWidth() / 2;
+ mCenterY = canvas.getHeight() / 2;
+
+ // draws mNumDots to compose a larger, main circle
+ for (int circle = 0; circle < mNumDots; circle++) {
+ double angle = ((mCurrAngleOffset) * Math.PI / 180) + (circle * ((2 * Math.PI)
+ / mNumDots));
+
+ int x = (int) (mCenterX + Math.cos(angle) * (mMainCircleCurrentRadius +
+ mCurrDotRadius));
+ int y = (int) (mCenterY + Math.sin(angle) * (mMainCircleCurrentRadius +
+ mCurrDotRadius));
+
+ canvas.drawCircle(x, y, mCurrDotRadius, mPaint);
+ }
+
+ if (mMainCircleCurrentRadius >= mMainCircleEndRadius) {
+ mFinishedAnimatingDots = true;
+ }
+ }
+
+ private void updateDrawingParameters() {
+ long now = System.currentTimeMillis();
+ long timeSinceStart = now - mAnimationStartTime;
+ mPathGone = getPathGone(now);
+ mInterpolatedPathGone = mInterpolator.getInterpolation(mPathGone);
+
+ // Position Dots: update main circle radius (that the dots compose)
+ if (mPathGone < 1.0f) {
+ mMainCircleCurrentRadius = mMainCircleStartRadius + (int) (mInterpolatedPathGone *
+ (mMainCircleEndRadius - mMainCircleStartRadius));
+ } else {
+ mMainCircleCurrentRadius = mMainCircleEndRadius;
+ }
+
+ // Scale Dots: update dot radius
+ if (timeSinceStart < mScaleDotsDuration) {
+ mCurrDotRadius = mDotsRadiusStart + (int) (mInterpolator.getInterpolation((float)
+ timeSinceStart / mScaleDotsDuration) * (mDotsRadiusEnd - mDotsRadiusStart));
+ } else {
+ mCurrDotRadius = mDotsRadiusEnd;
+ }
+
+ // Rotation Dot Group: dots rotate from 0 to 20 degrees
+ mCurrAngleOffset = mAngleOffset * mPathGone;
+ }
+
+ /**
+ * @return decimal depicting how far along the creation of the larger circle (of circles) is
+ * For values < 1.0, the larger circle is being drawn
+ * For values > 1.0 the larger circle has been drawn and further animation can occur
+ */
+ private float getPathGone(long now) {
+ return (float) (now - mAnimationStartTime) / (WirelessChargingAnimation.DURATION);
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java
index e4b405f580d4..ed659e2d16d5 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java
@@ -167,6 +167,9 @@ public class FalsingManager implements SensorEventListener {
if (mDataCollector.isEnabledFull()) {
registerSensors(COLLECTOR_SENSORS);
}
+ if (mDataCollector.isEnabled()) {
+ mDataCollector.onFalsingSessionStarted();
+ }
}
private void registerSensors(int [] sensors) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java b/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java
index debda2109637..8515bf2e2968 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java
@@ -30,8 +30,6 @@ import android.util.Log;
import com.android.systemui.R;
-import java.util.Arrays;
-
/**
* Class to store the policy for AOD, which comes from
* {@link android.provider.Settings.Global}
@@ -42,12 +40,16 @@ public class AlwaysOnDisplayPolicy {
private static final long DEFAULT_PROX_SCREEN_OFF_DELAY_MS = 10 * DateUtils.SECOND_IN_MILLIS;
private static final long DEFAULT_PROX_COOLDOWN_TRIGGER_MS = 2 * DateUtils.SECOND_IN_MILLIS;
private static final long DEFAULT_PROX_COOLDOWN_PERIOD_MS = 5 * DateUtils.SECOND_IN_MILLIS;
+ private static final long DEFAULT_WALLPAPER_VISIBILITY_MS = 60 * DateUtils.SECOND_IN_MILLIS;
+ private static final long DEFAULT_WALLPAPER_FADE_OUT_MS = 400;
static final String KEY_SCREEN_BRIGHTNESS_ARRAY = "screen_brightness_array";
static final String KEY_DIMMING_SCRIM_ARRAY = "dimming_scrim_array";
static final String KEY_PROX_SCREEN_OFF_DELAY_MS = "prox_screen_off_delay";
static final String KEY_PROX_COOLDOWN_TRIGGER_MS = "prox_cooldown_trigger";
static final String KEY_PROX_COOLDOWN_PERIOD_MS = "prox_cooldown_period";
+ static final String KEY_WALLPAPER_VISIBILITY_MS = "wallpaper_visibility_timeout";
+ static final String KEY_WALLPAPER_FADE_OUT_MS = "wallpaper_fade_out_duration";
/**
* Integer array to map ambient brightness type to real screen brightness.
@@ -91,6 +93,24 @@ public class AlwaysOnDisplayPolicy {
*/
public long proxCooldownPeriodMs;
+ /**
+ * For how long(ms) the wallpaper should still be visible
+ * after entering AoD.
+ *
+ * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS
+ * @see #KEY_WALLPAPER_VISIBILITY_MS
+ */
+ public long wallpaperVisibilityDuration;
+
+ /**
+ * Duration(ms) of the fade out animation after
+ * {@link #KEY_WALLPAPER_VISIBILITY_MS} elapses.
+ *
+ * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS
+ * @see #KEY_WALLPAPER_FADE_OUT_MS
+ */
+ public long wallpaperFadeOutDuration;
+
private final KeyValueListParser mParser;
private final Context mContext;
private SettingsObserver mSettingsObserver;
@@ -102,20 +122,6 @@ public class AlwaysOnDisplayPolicy {
mSettingsObserver.observe();
}
- private int[] parseIntArray(final String key, final int[] defaultArray) {
- final String value = mParser.getString(key, null);
- if (value != null) {
- try {
- return Arrays.stream(value.split(":")).map(String::trim).mapToInt(
- Integer::parseInt).toArray();
- } catch (NumberFormatException e) {
- return defaultArray;
- }
- } else {
- return defaultArray;
- }
- }
-
private final class SettingsObserver extends ContentObserver {
private final Uri ALWAYS_ON_DISPLAY_CONSTANTS_URI
= Settings.Global.getUriFor(Settings.Global.ALWAYS_ON_DISPLAY_CONSTANTS);
@@ -154,10 +160,14 @@ public class AlwaysOnDisplayPolicy {
DEFAULT_PROX_COOLDOWN_TRIGGER_MS);
proxCooldownPeriodMs = mParser.getLong(KEY_PROX_COOLDOWN_PERIOD_MS,
DEFAULT_PROX_COOLDOWN_PERIOD_MS);
- screenBrightnessArray = parseIntArray(KEY_SCREEN_BRIGHTNESS_ARRAY,
+ wallpaperFadeOutDuration = mParser.getLong(KEY_WALLPAPER_FADE_OUT_MS,
+ DEFAULT_WALLPAPER_FADE_OUT_MS);
+ wallpaperVisibilityDuration = mParser.getLong(KEY_WALLPAPER_VISIBILITY_MS,
+ DEFAULT_WALLPAPER_VISIBILITY_MS);
+ screenBrightnessArray = mParser.getIntArray(KEY_SCREEN_BRIGHTNESS_ARRAY,
resources.getIntArray(
R.array.config_doze_brightness_sensor_to_brightness));
- dimmingScrimArray = parseIntArray(KEY_DIMMING_SCRIM_ARRAY,
+ dimmingScrimArray = mParser.getIntArray(KEY_DIMMING_SCRIM_ARRAY,
resources.getIntArray(
R.array.config_doze_brightness_sensor_to_scrim_opacity));
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
index 6f8bcff16a83..092f3d241d6c 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
@@ -63,9 +63,10 @@ public class DozeFactory {
new DozeFalsingManagerAdapter(FalsingManager.getInstance(context)),
createDozeTriggers(context, sensorManager, host, alarmManager, config, params,
handler, wakeLock, machine),
- createDozeUi(context, host, wakeLock, machine, handler, alarmManager),
- new DozeScreenState(wrappedService, handler),
+ createDozeUi(context, host, wakeLock, machine, handler, alarmManager, params),
+ new DozeScreenState(wrappedService, handler, params),
createDozeScreenBrightness(context, wrappedService, sensorManager, host, handler),
+ new DozeWallpaperState(context)
});
return machine;
@@ -89,8 +90,9 @@ public class DozeFactory {
}
private DozeMachine.Part createDozeUi(Context context, DozeHost host, WakeLock wakeLock,
- DozeMachine machine, Handler handler, AlarmManager alarmManager) {
- return new DozeUi(context, alarmManager, machine, wakeLock, host, handler);
+ DozeMachine machine, Handler handler, AlarmManager alarmManager,
+ DozeParameters params) {
+ return new DozeUi(context, alarmManager, machine, wakeLock, host, handler, params);
}
public static DozeHost getHost(DozeService service) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
index 7db118d7fb1c..6a29299872a4 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
@@ -35,10 +35,10 @@ public interface DozeHost {
boolean isBlockingDoze();
void startPendingIntentDismissingKeyguard(PendingIntent intent);
- void abortPulsing();
void extendPulse();
void setAnimateWakeup(boolean animateWakeup);
+ void setAnimateScreenOff(boolean animateScreenOff);
void onDoubleTap(float x, float y);
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
index 8ec6afc326e1..6ff8e3db25e5 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
@@ -24,6 +24,7 @@ import android.view.Display;
import com.android.internal.hardware.AmbientDisplayConfiguration;
import com.android.internal.util.Preconditions;
+import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.util.Assert;
import com.android.systemui.util.wakelock.WakeLock;
@@ -87,12 +88,11 @@ public class DozeMachine {
}
}
- int screenState() {
+ int screenState(DozeParameters parameters) {
switch (this) {
case UNINITIALIZED:
case INITIALIZED:
case DOZE:
- case DOZE_REQUEST_PULSE:
case DOZE_AOD_PAUSED:
return Display.STATE_OFF;
case DOZE_PULSING:
@@ -100,6 +100,9 @@ public class DozeMachine {
case DOZE_AOD:
case DOZE_AOD_PAUSING:
return Display.STATE_DOZE_SUSPEND;
+ case DOZE_REQUEST_PULSE:
+ return parameters.getDisplayNeedsBlanking() ? Display.STATE_OFF
+ : Display.STATE_ON;
default:
return Display.STATE_UNKNOWN;
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
index bef9cb38180f..3053de366be5 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
@@ -19,6 +19,8 @@ package com.android.systemui.doze;
import android.os.Handler;
import android.view.Display;
+import com.android.systemui.statusbar.phone.DozeParameters;
+
/**
* Controls the screen when dozing.
*/
@@ -26,17 +28,20 @@ public class DozeScreenState implements DozeMachine.Part {
private final DozeMachine.Service mDozeService;
private final Handler mHandler;
private final Runnable mApplyPendingScreenState = this::applyPendingScreenState;
+ private final DozeParameters mParameters;
private int mPendingScreenState = Display.STATE_UNKNOWN;
- public DozeScreenState(DozeMachine.Service service, Handler handler) {
+ public DozeScreenState(DozeMachine.Service service, Handler handler,
+ DozeParameters parameters) {
mDozeService = service;
mHandler = handler;
+ mParameters = parameters;
}
@Override
public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
- int screenState = newState.screenState();
+ int screenState = newState.screenState(mParameters);
if (newState == DozeMachine.State.FINISH) {
// Make sure not to apply the screen state after DozeService was destroyed.
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java
index 5d0a9d70e99d..03b0082b96e7 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java
@@ -33,8 +33,10 @@ public class DozeScreenStatePreventingAdapter extends DozeMachine.Service.Delega
@Override
public void setDozeScreenState(int state) {
- if (state == Display.STATE_DOZE || state == Display.STATE_DOZE_SUSPEND) {
+ if (state == Display.STATE_DOZE) {
state = Display.STATE_ON;
+ } else if (state == Display.STATE_DOZE_SUSPEND) {
+ state = Display.STATE_ON_SUSPEND;
}
super.setDozeScreenState(state);
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
index 98b110619a1c..34d392861d7c 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
@@ -24,9 +24,10 @@ import android.util.Log;
import com.android.systemui.Dependency;
import com.android.systemui.plugins.DozeServicePlugin;
-import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.DozeServicePlugin.RequestDoze;
import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -92,6 +93,7 @@ public class DozeService extends DreamService
@Override
protected void dumpOnHandler(FileDescriptor fd, PrintWriter pw, String[] args) {
+ super.dumpOnHandler(fd, pw, args);
if (mDozeMachine != null) {
mDozeMachine.dump(pw);
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
index 851b78cfcd49..75f1b501b3f4 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
@@ -16,6 +16,8 @@
package com.android.systemui.doze;
+import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSED;
+
import android.app.AlarmManager;
import android.content.Context;
import android.os.Handler;
@@ -23,6 +25,7 @@ import android.os.SystemClock;
import android.text.format.Formatter;
import android.util.Log;
+import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.util.AlarmTimeout;
import com.android.systemui.util.wakelock.WakeLock;
@@ -41,18 +44,22 @@ public class DozeUi implements DozeMachine.Part {
private final WakeLock mWakeLock;
private final DozeMachine mMachine;
private final AlarmTimeout mTimeTicker;
+ private final boolean mCanAnimateWakeup;
private long mLastTimeTickElapsed = 0;
public DozeUi(Context context, AlarmManager alarmManager, DozeMachine machine,
- WakeLock wakeLock, DozeHost host, Handler handler) {
+ WakeLock wakeLock, DozeHost host, Handler handler,
+ DozeParameters params) {
mContext = context;
mMachine = machine;
mWakeLock = wakeLock;
mHost = host;
mHandler = handler;
+ mCanAnimateWakeup = !params.getDisplayNeedsBlanking();
mTimeTicker = new AlarmTimeout(alarmManager, this::onTimeTick, "doze_time_tick", handler);
+ mHost.setAnimateScreenOff(params.getCanControlScreenOffAnimation());
}
private void pulseWhileDozing(int reason) {
@@ -74,6 +81,11 @@ public class DozeUi implements DozeMachine.Part {
public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
switch (newState) {
case DOZE_AOD:
+ if (oldState == DOZE_AOD_PAUSED) {
+ mHost.dozeTimeTick();
+ }
+ scheduleTimeTick();
+ break;
case DOZE_AOD_PAUSING:
scheduleTimeTick();
break;
@@ -106,7 +118,7 @@ public class DozeUi implements DozeMachine.Part {
// Keep current state.
break;
default:
- mHost.setAnimateWakeup(false);
+ mHost.setAnimateWakeup(mCanAnimateWakeup);
break;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
new file mode 100644
index 000000000000..5156272b1ade
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
@@ -0,0 +1,106 @@
+/*
+ * 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.doze;
+
+import android.app.IWallpaperManager;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+
+import java.io.PrintWriter;
+
+/**
+ * Propagates doze state to wallpaper engine.
+ */
+public class DozeWallpaperState implements DozeMachine.Part {
+
+ private static final String TAG = "DozeWallpaperState";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private final IWallpaperManager mWallpaperManagerService;
+ private boolean mKeyguardVisible;
+ private boolean mIsAmbientMode;
+ private final DozeParameters mDozeParameters;
+
+ public DozeWallpaperState(Context context) {
+ this(IWallpaperManager.Stub.asInterface(
+ ServiceManager.getService(Context.WALLPAPER_SERVICE)),
+ new DozeParameters(context), KeyguardUpdateMonitor.getInstance(context));
+ }
+
+ @VisibleForTesting
+ DozeWallpaperState(IWallpaperManager wallpaperManagerService, DozeParameters parameters,
+ KeyguardUpdateMonitor keyguardUpdateMonitor) {
+ mWallpaperManagerService = wallpaperManagerService;
+ mDozeParameters = parameters;
+ keyguardUpdateMonitor.registerCallback(new KeyguardUpdateMonitorCallback() {
+ @Override
+ public void onKeyguardVisibilityChanged(boolean showing) {
+ mKeyguardVisible = showing;
+ }
+ });
+ }
+
+ @Override
+ public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
+ final boolean isAmbientMode;
+ switch (newState) {
+ case DOZE_AOD:
+ case DOZE_AOD_PAUSING:
+ case DOZE_AOD_PAUSED:
+ case DOZE_REQUEST_PULSE:
+ case DOZE_PULSING:
+ case DOZE_PULSE_DONE:
+ isAmbientMode = mDozeParameters.getAlwaysOn();
+ break;
+ default:
+ isAmbientMode = false;
+ }
+
+ final boolean animated;
+ if (isAmbientMode) {
+ animated = mDozeParameters.getCanControlScreenOffAnimation() && !mKeyguardVisible;
+ } else {
+ animated = !mDozeParameters.getDisplayNeedsBlanking();
+ }
+
+ if (isAmbientMode != mIsAmbientMode) {
+ mIsAmbientMode = isAmbientMode;
+ try {
+ Log.i(TAG, "AoD wallpaper state changed to: " + mIsAmbientMode
+ + ", animated: " + animated);
+ mWallpaperManagerService.setInAmbientMode(mIsAmbientMode, animated);
+ } catch (RemoteException e) {
+ // Cannot notify wallpaper manager service, but it's fine, let's just skip it.
+ Log.w(TAG, "Cannot notify state to WallpaperManagerService: " + mIsAmbientMode);
+ }
+ }
+ }
+
+ @Override
+ public void dump(PrintWriter pw) {
+ pw.println("DozeWallpaperState:");
+ pw.println(" isAmbientMode: " + mIsAmbientMode);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/fingerprint/FingerprintDialogImpl.java b/packages/SystemUI/src/com/android/systemui/fingerprint/FingerprintDialogImpl.java
new file mode 100644
index 000000000000..262c71ae0b1e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/fingerprint/FingerprintDialogImpl.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.fingerprint;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.fingerprint.FingerprintDialog;
+import android.hardware.fingerprint.IFingerprintDialogReceiver;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.WindowManager;
+
+import com.android.internal.os.SomeArgs;
+import com.android.systemui.SystemUI;
+import com.android.systemui.statusbar.CommandQueue;
+
+public class FingerprintDialogImpl extends SystemUI implements CommandQueue.Callbacks {
+ private static final String TAG = "FingerprintDialogImpl";
+ private static final boolean DEBUG = true;
+
+ protected static final int MSG_SHOW_DIALOG = 1;
+ protected static final int MSG_FINGERPRINT_AUTHENTICATED = 2;
+ protected static final int MSG_FINGERPRINT_HELP = 3;
+ protected static final int MSG_FINGERPRINT_ERROR = 4;
+ protected static final int MSG_HIDE_DIALOG = 5;
+ protected static final int MSG_BUTTON_NEGATIVE = 6;
+ protected static final int MSG_USER_CANCELED = 7;
+ protected static final int MSG_BUTTON_POSITIVE = 8;
+ protected static final int MSG_CLEAR_MESSAGE = 9;
+
+
+ private FingerprintDialogView mDialogView;
+ private WindowManager mWindowManager;
+ private IFingerprintDialogReceiver mReceiver;
+ private boolean mDialogShowing;
+
+ private Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch(msg.what) {
+ case MSG_SHOW_DIALOG:
+ handleShowDialog((SomeArgs) msg.obj);
+ break;
+ case MSG_FINGERPRINT_AUTHENTICATED:
+ handleFingerprintAuthenticated();
+ break;
+ case MSG_FINGERPRINT_HELP:
+ handleFingerprintHelp((String) msg.obj);
+ break;
+ case MSG_FINGERPRINT_ERROR:
+ handleFingerprintError((String) msg.obj);
+ break;
+ case MSG_HIDE_DIALOG:
+ handleHideDialog((Boolean) msg.obj);
+ break;
+ case MSG_BUTTON_NEGATIVE:
+ handleButtonNegative();
+ break;
+ case MSG_USER_CANCELED:
+ handleUserCanceled();
+ break;
+ case MSG_BUTTON_POSITIVE:
+ handleButtonPositive();
+ break;
+ case MSG_CLEAR_MESSAGE:
+ handleClearMessage();
+ break;
+ }
+ }
+ };
+
+ @Override
+ public void start() {
+ if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
+ return;
+ }
+ getComponent(CommandQueue.class).addCallbacks(this);
+ mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+ mDialogView = new FingerprintDialogView(mContext, mHandler);
+ }
+
+ @Override
+ public void showFingerprintDialog(Bundle bundle, IFingerprintDialogReceiver receiver) {
+ if (DEBUG) Log.d(TAG, "showFingerprintDialog");
+ // Remove these messages as they are part of the previous client
+ mHandler.removeMessages(MSG_FINGERPRINT_ERROR);
+ mHandler.removeMessages(MSG_FINGERPRINT_HELP);
+ mHandler.removeMessages(MSG_FINGERPRINT_AUTHENTICATED);
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = bundle;
+ args.arg2 = receiver;
+ mHandler.obtainMessage(MSG_SHOW_DIALOG, args).sendToTarget();
+ }
+
+ @Override
+ public void onFingerprintAuthenticated() {
+ if (DEBUG) Log.d(TAG, "onFingerprintAuthenticated");
+ mHandler.obtainMessage(MSG_FINGERPRINT_AUTHENTICATED).sendToTarget();
+ }
+
+ @Override
+ public void onFingerprintHelp(String message) {
+ if (DEBUG) Log.d(TAG, "onFingerprintHelp: " + message);
+ mHandler.obtainMessage(MSG_FINGERPRINT_HELP, message).sendToTarget();
+ }
+
+ @Override
+ public void onFingerprintError(String error) {
+ if (DEBUG) Log.d(TAG, "onFingerprintError: " + error);
+ mHandler.obtainMessage(MSG_FINGERPRINT_ERROR, error).sendToTarget();
+ }
+
+ @Override
+ public void hideFingerprintDialog() {
+ if (DEBUG) Log.d(TAG, "hideFingerprintDialog");
+ mHandler.obtainMessage(MSG_HIDE_DIALOG, false /* userCanceled */).sendToTarget();
+ }
+
+ private void handleShowDialog(SomeArgs args) {
+ if (DEBUG) Log.d(TAG, "handleShowDialog");
+ if (mDialogShowing) {
+ Log.w(TAG, "Dialog already showing");
+ return;
+ }
+ mReceiver = (IFingerprintDialogReceiver) args.arg2;
+ mDialogView.setBundle((Bundle)args.arg1);
+ mWindowManager.addView(mDialogView, mDialogView.getLayoutParams());
+ mDialogShowing = true;
+ }
+
+ private void handleFingerprintAuthenticated() {
+ if (DEBUG) Log.d(TAG, "handleFingerprintAuthenticated");
+ handleHideDialog(false /* userCanceled */);
+ }
+
+ private void handleFingerprintHelp(String message) {
+ if (DEBUG) Log.d(TAG, "handleFingerprintHelp: " + message);
+ mDialogView.showHelpMessage(message);
+ }
+
+ private void handleFingerprintError(String error) {
+ if (DEBUG) Log.d(TAG, "handleFingerprintError: " + error);
+ if (!mDialogShowing) {
+ if (DEBUG) Log.d(TAG, "Dialog already dismissed");
+ return;
+ }
+ mDialogView.showErrorMessage(error);
+ }
+
+ private void handleHideDialog(boolean userCanceled) {
+ if (DEBUG) Log.d(TAG, "handleHideDialog");
+ if (!mDialogShowing) {
+ // This can happen if there's a race and we get called from both
+ // onAuthenticated and onError, etc.
+ Log.w(TAG, "Dialog already dismissed, userCanceled: " + userCanceled);
+ return;
+ }
+ if (userCanceled) {
+ try {
+ mReceiver.onDialogDismissed(FingerprintDialog.DISMISSED_REASON_USER_CANCEL);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException when hiding dialog", e);
+ }
+ }
+ mReceiver = null;
+ mWindowManager.removeView(mDialogView);
+ mDialogShowing = false;
+ }
+
+ private void handleButtonNegative() {
+ if (mReceiver == null) {
+ Log.e(TAG, "Receiver is null");
+ return;
+ }
+ try {
+ mReceiver.onDialogDismissed(FingerprintDialog.DISMISSED_REASON_NEGATIVE);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Remote exception when handling negative button", e);
+ }
+ handleHideDialog(false /* userCanceled */);
+ }
+
+ private void handleButtonPositive() {
+ if (mReceiver == null) {
+ Log.e(TAG, "Receiver is null");
+ return;
+ }
+ try {
+ mReceiver.onDialogDismissed(FingerprintDialog.DISMISSED_REASON_POSITIVE);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Remote exception when handling positive button", e);
+ }
+ handleHideDialog(false /* userCanceled */);
+ }
+
+ private void handleClearMessage() {
+ mDialogView.clearMessage();
+ }
+
+ private void handleUserCanceled() {
+ handleHideDialog(true /* userCanceled */);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/fingerprint/FingerprintDialogView.java b/packages/SystemUI/src/com/android/systemui/fingerprint/FingerprintDialogView.java
new file mode 100644
index 000000000000..9779937aed99
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/fingerprint/FingerprintDialogView.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.fingerprint;
+
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.Drawable;
+import android.hardware.fingerprint.FingerprintDialog;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.animation.Interpolator;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.PackageManagerWrapper;
+
+/**
+ * This class loads the view for the system-provided dialog. The view consists of:
+ * Application Icon, Title, Subtitle, Description, Fingerprint Icon, Error/Help message area,
+ * and positive/negative buttons.
+ */
+public class FingerprintDialogView extends LinearLayout {
+
+ private static final String TAG = "FingerprintDialogView";
+
+ private static final int ANIMATION_DURATION = 250; // ms
+
+ private final IBinder mWindowToken = new Binder();
+ private final ActivityManagerWrapper mActivityManagerWrapper;
+ private final PackageManagerWrapper mPackageManageWrapper;
+ private final Interpolator mLinearOutSlowIn;
+ private final Interpolator mFastOutLinearIn;
+ private final float mAnimationTranslationOffset;
+
+ private ViewGroup mLayout;
+ private final TextView mErrorText;
+ private Handler mHandler;
+ private Bundle mBundle;
+ private final LinearLayout mDialog;
+
+ public FingerprintDialogView(Context context, Handler handler) {
+ super(context);
+ mHandler = handler;
+ mActivityManagerWrapper = ActivityManagerWrapper.getInstance();
+ mPackageManageWrapper = PackageManagerWrapper.getInstance();
+ mLinearOutSlowIn = Interpolators.LINEAR_OUT_SLOW_IN;
+ mFastOutLinearIn = Interpolators.FAST_OUT_LINEAR_IN;
+ mAnimationTranslationOffset = getResources()
+ .getDimension(R.dimen.fingerprint_dialog_animation_translation_offset);
+
+ // Create the dialog
+ LayoutInflater factory = LayoutInflater.from(getContext());
+ mLayout = (ViewGroup) factory.inflate(R.layout.fingerprint_dialog, this, false);
+ addView(mLayout);
+
+ mDialog = mLayout.findViewById(R.id.dialog);
+
+ mErrorText = mLayout.findViewById(R.id.error);
+
+ mLayout.setOnKeyListener(new View.OnKeyListener() {
+ boolean downPressed = false;
+ @Override
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+ if (keyCode != KeyEvent.KEYCODE_BACK) {
+ return false;
+ }
+ if (event.getAction() == KeyEvent.ACTION_DOWN && downPressed == false) {
+ downPressed = true;
+ } else if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ downPressed = false;
+ } else if (event.getAction() == KeyEvent.ACTION_UP && downPressed == true) {
+ downPressed = false;
+ mHandler.obtainMessage(FingerprintDialogImpl.MSG_USER_CANCELED).sendToTarget();
+ }
+ return true;
+ }
+ });
+
+ final View space = mLayout.findViewById(R.id.space);
+ final Button negative = mLayout.findViewById(R.id.button2);
+ final Button positive = mLayout.findViewById(R.id.button1);
+
+ space.setClickable(true);
+ space.setOnTouchListener((View view, MotionEvent event) -> {
+ mHandler.obtainMessage(FingerprintDialogImpl.MSG_HIDE_DIALOG, true /* userCanceled*/)
+ .sendToTarget();
+ return true;
+ });
+
+ negative.setOnClickListener((View v) -> {
+ mHandler.obtainMessage(FingerprintDialogImpl.MSG_BUTTON_NEGATIVE).sendToTarget();
+ });
+
+ positive.setOnClickListener((View v) -> {
+ mHandler.obtainMessage(FingerprintDialogImpl.MSG_BUTTON_POSITIVE).sendToTarget();
+ });
+
+ mLayout.setFocusableInTouchMode(true);
+ mLayout.requestFocus();
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ final TextView title = mLayout.findViewById(R.id.title);
+ final TextView subtitle = mLayout.findViewById(R.id.subtitle);
+ final TextView description = mLayout.findViewById(R.id.description);
+ final Button negative = mLayout.findViewById(R.id.button2);
+ final ImageView image = mLayout.findViewById(R.id.icon);
+ final Button positive = mLayout.findViewById(R.id.button1);
+ final ImageView fingerprint_icon = mLayout.findViewById(R.id.fingerprint_icon);
+
+ title.setText(mBundle.getCharSequence(FingerprintDialog.KEY_TITLE));
+ title.setSelected(true);
+ subtitle.setText(mBundle.getCharSequence(FingerprintDialog.KEY_SUBTITLE));
+ description.setText(mBundle.getCharSequence(FingerprintDialog.KEY_DESCRIPTION));
+ negative.setText(mBundle.getCharSequence(FingerprintDialog.KEY_NEGATIVE_TEXT));
+ setAppIcon(image);
+
+ final CharSequence positiveText =
+ mBundle.getCharSequence(FingerprintDialog.KEY_POSITIVE_TEXT);
+ positive.setText(positiveText); // needs to be set for marquee to work
+ if (positiveText != null) {
+ positive.setVisibility(View.VISIBLE);
+ } else {
+ positive.setVisibility(View.GONE);
+ }
+
+ // Dim the background and slide the dialog up
+ mDialog.setTranslationY(mAnimationTranslationOffset);
+ mLayout.setAlpha(0f);
+ postOnAnimation(new Runnable() {
+ @Override
+ public void run() {
+ mLayout.animate()
+ .alpha(1f)
+ .setDuration(ANIMATION_DURATION)
+ .setInterpolator(mLinearOutSlowIn)
+ .withLayer()
+ .start();
+ mDialog.animate()
+ .translationY(0)
+ .setDuration(ANIMATION_DURATION)
+ .setInterpolator(mLinearOutSlowIn)
+ .withLayer()
+ .start();
+ }
+ });
+ }
+
+ public void setBundle(Bundle bundle) {
+ mBundle = bundle;
+ }
+
+ protected void clearMessage() {
+ mErrorText.setVisibility(View.INVISIBLE);
+ }
+
+ private void showMessage(String message) {
+ mHandler.removeMessages(FingerprintDialogImpl.MSG_CLEAR_MESSAGE);
+ mErrorText.setText(message);
+ mErrorText.setContentDescription(message);
+ mErrorText.setVisibility(View.VISIBLE);
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(FingerprintDialogImpl.MSG_CLEAR_MESSAGE),
+ FingerprintDialog.HIDE_DIALOG_DELAY);
+ }
+
+ public void showHelpMessage(String message) {
+ showMessage(message);
+ }
+
+ public void showErrorMessage(String error) {
+ showMessage(error);
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(FingerprintDialogImpl.MSG_HIDE_DIALOG,
+ false /* userCanceled */), FingerprintDialog.HIDE_DIALOG_DELAY);
+ }
+
+ private void setAppIcon(ImageView image) {
+ final ActivityManager.RunningTaskInfo taskInfo = mActivityManagerWrapper.getRunningTask();
+ final ComponentName cn = taskInfo.topActivity;
+ final int userId = mActivityManagerWrapper.getCurrentUserId();
+ final ActivityInfo activityInfo = mPackageManageWrapper.getActivityInfo(cn, userId);
+ image.setImageDrawable(mActivityManagerWrapper.getBadgedActivityIcon(activityInfo, userId));
+ image.setContentDescription(
+ getResources().getString(R.string.accessibility_fingerprint_dialog_app_icon)
+ + " "
+ + mActivityManagerWrapper.getBadgedActivityLabel(activityInfo, userId));
+ }
+
+ public WindowManager.LayoutParams getLayoutParams() {
+ final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
+ WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
+ PixelFormat.TRANSLUCENT);
+ lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ lp.setTitle("FingerprintDialogView");
+ lp.token = mWindowToken;
+ return lp;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
index 09a08f09fc9a..aa085626b6c2 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
@@ -14,6 +14,10 @@
package com.android.systemui.globalactions;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.Dependency;
import com.android.systemui.SysUiServiceProvider;
@@ -25,12 +29,9 @@ import com.android.systemui.statusbar.CommandQueue.Callbacks;
import com.android.systemui.statusbar.policy.ExtensionController;
import com.android.systemui.statusbar.policy.ExtensionController.Extension;
-import android.content.Context;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-
public class GlobalActionsComponent extends SystemUI implements Callbacks, GlobalActionsManager {
+ private GlobalActions mPlugin;
private Extension<GlobalActions> mExtension;
private IStatusBarService mBarService;
@@ -41,10 +42,19 @@ public class GlobalActionsComponent extends SystemUI implements Callbacks, Globa
mExtension = Dependency.get(ExtensionController.class).newExtension(GlobalActions.class)
.withPlugin(GlobalActions.class)
.withDefault(() -> new GlobalActionsImpl(mContext))
+ .withCallback(this::onExtensionCallback)
.build();
+ mPlugin = mExtension.get();
SysUiServiceProvider.getComponent(mContext, CommandQueue.class).addCallbacks(this);
}
+ private void onExtensionCallback(GlobalActions newPlugin) {
+ if (mPlugin != null) {
+ mPlugin.destroy();
+ }
+ mPlugin = newPlugin;
+ }
+
@Override
public void handleShowShutdownUi(boolean isReboot, String reason) {
mExtension.get().showShutdownUi(isReboot, reason);
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 33d5617c0397..1aea5e7f4a80 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -14,35 +14,22 @@
package com.android.systemui.globalactions;
-import com.android.internal.R;
-import com.android.internal.colorextraction.ColorExtractor;
-import com.android.internal.colorextraction.ColorExtractor.GradientColors;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.util.EmergencyAffordanceManager;
-import com.android.internal.telephony.TelephonyIntents;
-import com.android.internal.telephony.TelephonyProperties;
-import com.android.internal.widget.LockPatternUtils;
-import com.android.systemui.Dependency;
-import com.android.systemui.HardwareUiLayout;
-import com.android.systemui.Interpolators;
-import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
-import com.android.systemui.statusbar.notification.NotificationUtils;
-import com.android.systemui.statusbar.phone.ScrimController;
-import com.android.systemui.volume.VolumeDialogMotion.LogAccelerateInterpolator;
-import com.android.systemui.volume.VolumeDialogMotion.LogDecelerateInterpolator;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.app.ActivityManager;
import android.app.Dialog;
+import android.app.KeyguardManager;
import android.app.WallpaperManager;
+import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.ServiceConnection;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.graphics.Point;
@@ -51,7 +38,9 @@ import android.media.AudioManager;
import android.net.ConnectivityManager;
import android.os.Build;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Message;
+import android.os.Messenger;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
@@ -67,7 +56,6 @@ import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
-import android.util.MathUtils;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
@@ -84,7 +72,24 @@ import android.widget.ImageView.ScaleType;
import android.widget.LinearLayout;
import android.widget.TextView;
+import com.android.internal.R;
+import com.android.internal.colorextraction.ColorExtractor;
+import com.android.internal.colorextraction.ColorExtractor.GradientColors;
import com.android.internal.colorextraction.drawable.GradientDrawable;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.telephony.TelephonyProperties;
+import com.android.internal.util.EmergencyAffordanceManager;
+import com.android.internal.util.ScreenshotHelper;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.systemui.Dependency;
+import com.android.systemui.HardwareUiLayout;
+import com.android.systemui.Interpolators;
+import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
+import com.android.systemui.statusbar.phone.ScrimController;
+import com.android.systemui.volume.SystemUIInterpolators.LogAccelerateInterpolator;
import java.util.ArrayList;
import java.util.List;
@@ -94,7 +99,8 @@ import java.util.List;
* may show depending on whether the keyguard is showing, and whether the device
* is provisioned.
*/
-class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener {
+class GlobalActionsDialog implements DialogInterface.OnDismissListener,
+ DialogInterface.OnClickListener {
static public final String SYSTEM_DIALOG_REASON_KEY = "reason";
static public final String SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS = "globalactions";
@@ -115,11 +121,16 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogIn
private static final String GLOBAL_ACTION_KEY_VOICEASSIST = "voiceassist";
private static final String GLOBAL_ACTION_KEY_ASSIST = "assist";
private static final String GLOBAL_ACTION_KEY_RESTART = "restart";
+ private static final String GLOBAL_ACTION_KEY_LOGOUT = "logout";
+ private static final String GLOBAL_ACTION_KEY_SCREENSHOT = "screenshot";
private final Context mContext;
private final GlobalActionsManager mWindowManagerFuncs;
private final AudioManager mAudioManager;
private final IDreamManager mDreamManager;
+ private final DevicePolicyManager mDevicePolicyManager;
+ private final LockPatternUtils mLockPatternUtils;
+ private final KeyguardManager mKeyguardManager;
private ArrayList<Action> mItems;
private ActionsDialog mDialog;
@@ -135,8 +146,11 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogIn
private boolean mIsWaitingForEcmExit = false;
private boolean mHasTelephony;
private boolean mHasVibrator;
+ private boolean mHasLogoutButton;
+ private boolean mHasLockdownButton;
private final boolean mShowSilentToggle;
private final EmergencyAffordanceManager mEmergencyAffordanceManager;
+ private final ScreenshotHelper mScreenshotHelper;
/**
* @param context everything needs a context :(
@@ -147,6 +161,10 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogIn
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
mDreamManager = IDreamManager.Stub.asInterface(
ServiceManager.getService(DreamService.DREAM_SERVICE));
+ mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService(
+ Context.DEVICE_POLICY_SERVICE);
+ mLockPatternUtils = new LockPatternUtils(mContext);
+ mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
// receive broadcasts
IntentFilter filter = new IntentFilter();
@@ -173,6 +191,7 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogIn
R.bool.config_useFixedVolume);
mEmergencyAffordanceManager = new EmergencyAffordanceManager(context);
+ mScreenshotHelper = new ScreenshotHelper(context);
}
/**
@@ -193,6 +212,14 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogIn
}
}
+ /**
+ * Dismiss the global actions dialog, if it's currently shown
+ */
+ public void dismissDialog() {
+ mHandler.removeMessages(MESSAGE_DISMISS);
+ mHandler.sendEmptyMessage(MESSAGE_DISMISS);
+ }
+
private void awakenIfNecessary() {
if (mDreamManager != null) {
try {
@@ -284,6 +311,8 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogIn
R.array.config_globalActionsList);
ArraySet<String> addedKeys = new ArraySet<String>();
+ mHasLogoutButton = false;
+ mHasLockdownButton = false;
for (int i = 0; i < defaultActions.length; i++) {
String actionKey = defaultActions[i];
if (addedKeys.contains(actionKey)) {
@@ -310,13 +339,26 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogIn
} else if (GLOBAL_ACTION_KEY_SETTINGS.equals(actionKey)) {
mItems.add(getSettingsAction());
} else if (GLOBAL_ACTION_KEY_LOCKDOWN.equals(actionKey)) {
- mItems.add(getLockdownAction());
+ if (Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.LOCKDOWN_IN_POWER_MENU, 0) != 0
+ && shouldDisplayLockdown()) {
+ mItems.add(getLockdownAction());
+ mHasLockdownButton = true;
+ }
} else if (GLOBAL_ACTION_KEY_VOICEASSIST.equals(actionKey)) {
mItems.add(getVoiceAssistAction());
} else if (GLOBAL_ACTION_KEY_ASSIST.equals(actionKey)) {
mItems.add(getAssistAction());
} else if (GLOBAL_ACTION_KEY_RESTART.equals(actionKey)) {
mItems.add(new RestartAction());
+ } else if (GLOBAL_ACTION_KEY_SCREENSHOT.equals(actionKey)) {
+ mItems.add(new ScreenshotAction());
+ } else if (GLOBAL_ACTION_KEY_LOGOUT.equals(actionKey)) {
+ if (mDevicePolicyManager.isLogoutEnabled()
+ && getCurrentUser().id != UserHandle.USER_SYSTEM) {
+ mItems.add(new LogoutAction());
+ mHasLogoutButton = true;
+ }
} else {
Log.e(TAG, "Invalid global action key " + actionKey);
}
@@ -351,6 +393,19 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogIn
return dialog;
}
+ private boolean shouldDisplayLockdown() {
+ int userId = getCurrentUser().id;
+ // Lockdown is meaningless without a place to go.
+ if (!mKeyguardManager.isDeviceSecure(userId)) {
+ return false;
+ }
+
+ // Only show the lockdown button if the device isn't locked down (for whatever reason).
+ int state = mLockPatternUtils.getStrongAuthForUser(userId);
+ return (state == STRONG_AUTH_NOT_REQUIRED
+ || state == SOME_AUTH_REQUIRED_AFTER_USER_REQUEST);
+ }
+
private final class PowerAction extends SinglePressAction implements LongPressAction {
private PowerAction() {
super(R.drawable.ic_lock_power_off,
@@ -416,6 +471,38 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogIn
}
+ private class ScreenshotAction extends SinglePressAction {
+ public ScreenshotAction() {
+ super(R.drawable.ic_screenshot, R.string.global_action_screenshot);
+ }
+
+ @Override
+ public void onPress() {
+ // Add a little delay before executing, to give the
+ // dialog a chance to go away before it takes a
+ // screenshot.
+ // TODO: instead, omit global action dialog layer
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ mScreenshotHelper.takeScreenshot(1, true, true, mHandler);
+ MetricsLogger.action(mContext,
+ MetricsEvent.ACTION_SCREENSHOT_POWER_MENU);
+ }
+ }, 500);
+ }
+
+ @Override
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ @Override
+ public boolean showBeforeProvisioning() {
+ return false;
+ }
+ }
+
private class BugReportAction extends SinglePressAction implements LongPressAction {
public BugReportAction() {
@@ -482,6 +569,37 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogIn
}
}
+ private final class LogoutAction extends SinglePressAction {
+ private LogoutAction() {
+ super(R.drawable.ic_logout, R.string.global_action_logout);
+ }
+
+ @Override
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ @Override
+ public boolean showBeforeProvisioning() {
+ return false;
+ }
+
+ @Override
+ public void onPress() {
+ // Add a little delay before executing, to give the dialog a chance to go away before
+ // switching user
+ mHandler.postDelayed(() -> {
+ try {
+ int currentUserId = getCurrentUser().id;
+ ActivityManager.getService().switchUser(UserHandle.USER_SYSTEM);
+ ActivityManager.getService().stopUser(currentUserId, true /*force*/, null);
+ } catch (RemoteException re) {
+ Log.e(TAG, "Couldn't logout user " + re);
+ }
+ }, 500);
+ }
+ }
+
private Action getSettingsAction() {
return new SinglePressAction(R.drawable.ic_settings,
R.string.global_action_settings) {
@@ -575,7 +693,9 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogIn
@Override
public void onPress() {
- new LockPatternUtils(mContext).requireCredentialEntry(UserHandle.USER_ALL);
+ new LockPatternUtils(mContext)
+ .requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN,
+ UserHandle.USER_ALL);
try {
WindowManagerGlobal.getWindowManagerService().lockNow(null);
} catch (RemoteException e) {
@@ -754,7 +874,9 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogIn
public View getView(int position, View convertView, ViewGroup parent) {
Action action = getItem(position);
View view = action.create(mContext, convertView, parent, LayoutInflater.from(mContext));
- if (position == 2) {
+ // Power off, restart, logout (if present) and lockdown (if present) should be in white
+ // background. Set the division based on which buttons are currently being displayed.
+ if (position == 2 + (mHasLogoutButton ? 1 : 0) + (mHasLockdownButton ? 1 : 0)) {
HardwareUiLayout.get(parent).setDivisionView(view);
}
return view;
@@ -1247,6 +1369,7 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogIn
mListView = findViewById(android.R.id.list);
mHardwareLayout = HardwareUiLayout.get(mListView);
mHardwareLayout.setOutsideTouchListener(view -> dismiss());
+ setTitle(R.string.global_actions);
}
private void updateList() {
@@ -1273,7 +1396,23 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogIn
mGradientDrawable.setScreenSize(displaySize.x, displaySize.y);
GradientColors colors = mColorExtractor.getColors(mKeyguardShowing ?
WallpaperManager.FLAG_LOCK : WallpaperManager.FLAG_SYSTEM);
- mGradientDrawable.setColors(colors, false);
+ updateColors(colors, false /* animate */);
+ }
+
+ /**
+ * Updates background and system bars according to current GradientColors.
+ * @param colors Colors and hints to use.
+ * @param animate Interpolates gradient if true, just sets otherwise.
+ */
+ private void updateColors(GradientColors colors, boolean animate) {
+ mGradientDrawable.setColors(colors, animate);
+ View decorView = getWindow().getDecorView();
+ if (colors.supportsDarkText()) {
+ decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR |
+ View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
+ } else {
+ decorView.setSystemUiVisibility(0);
+ }
}
@Override
@@ -1326,28 +1465,16 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogIn
}
@Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
- for (int i = 0; i < mAdapter.getCount(); ++i) {
- CharSequence label =
- mAdapter.getItem(i).getLabelForAccessibility(getContext());
- if (label != null) {
- event.getText().add(label);
- }
- }
- }
- return super.dispatchPopulateAccessibilityEvent(event);
- }
-
- @Override
public void onColorsChanged(ColorExtractor extractor, int which) {
if (mKeyguardShowing) {
if ((WallpaperManager.FLAG_LOCK & which) != 0) {
- mGradientDrawable.setColors(extractor.getColors(WallpaperManager.FLAG_LOCK));
+ updateColors(extractor.getColors(WallpaperManager.FLAG_LOCK),
+ true /* animate */);
}
} else {
if ((WallpaperManager.FLAG_SYSTEM & which) != 0) {
- mGradientDrawable.setColors(extractor.getColors(WallpaperManager.FLAG_SYSTEM));
+ updateColors(extractor.getColors(WallpaperManager.FLAG_SYSTEM),
+ true /* animate */);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
index 2cf230c8e12d..35634374d5dd 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
@@ -14,12 +14,12 @@
package com.android.systemui.globalactions;
+import static android.app.StatusBarManager.DISABLE2_GLOBAL_ACTIONS;
+
import android.app.Dialog;
import android.app.KeyguardManager;
-import android.app.WallpaperColors;
import android.app.WallpaperManager;
import android.content.Context;
-import android.graphics.Color;
import android.graphics.Point;
import android.view.ViewGroup;
import android.view.Window;
@@ -32,12 +32,14 @@ import com.android.internal.colorextraction.ColorExtractor.GradientColors;
import com.android.internal.colorextraction.drawable.GradientDrawable;
import com.android.settingslib.Utils;
import com.android.systemui.Dependency;
+import com.android.systemui.SysUiServiceProvider;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.plugins.GlobalActions;
+import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
-public class GlobalActionsImpl implements GlobalActions {
+public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks {
private static final float SHUTDOWN_SCRIM_ALPHA = 0.95f;
@@ -45,15 +47,23 @@ public class GlobalActionsImpl implements GlobalActions {
private final KeyguardMonitor mKeyguardMonitor;
private final DeviceProvisionedController mDeviceProvisionedController;
private GlobalActionsDialog mGlobalActions;
+ private boolean mDisabled;
public GlobalActionsImpl(Context context) {
mContext = context;
mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
+ SysUiServiceProvider.getComponent(context, CommandQueue.class).addCallbacks(this);
+ }
+
+ @Override
+ public void destroy() {
+ SysUiServiceProvider.getComponent(mContext, CommandQueue.class).removeCallbacks(this);
}
@Override
public void showGlobalActions(GlobalActionsManager manager) {
+ if (mDisabled) return;
if (mGlobalActions == null) {
mGlobalActions = new GlobalActionsDialog(mContext, manager);
}
@@ -107,4 +117,14 @@ public class GlobalActionsImpl implements GlobalActions {
d.show();
}
+
+ @Override
+ public void disable(int state1, int state2, boolean animate) {
+ final boolean disabled = (state2 & DISABLE2_GLOBAL_ACTIONS) != 0;
+ if (disabled == mDisabled) return;
+ mDisabled = disabled;
+ if (disabled && mGlobalActions != null) {
+ mGlobalActions.dismissDialog();
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 2a5ae0d3efc8..22b41a4f9cfa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -96,9 +96,9 @@ public class KeyguardService extends Service {
}
@Override // Binder interface
- public void dismiss(IKeyguardDismissCallback callback) {
+ public void dismiss(IKeyguardDismissCallback callback, CharSequence message) {
checkPermission();
- mKeyguardViewMediator.dismiss(callback);
+ mKeyguardViewMediator.dismiss(callback, message);
}
@Override // Binder interface
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
new file mode 100644
index 000000000000..c7d276c1b7a3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -0,0 +1,209 @@
+/*
+ * 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.keyguard;
+
+import android.app.ActivityManager;
+import android.app.AlarmManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.drawable.Icon;
+import android.icu.text.DateFormat;
+import android.icu.text.DisplayContext;
+import android.net.Uri;
+import android.os.Handler;
+import android.text.TextUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.NextAlarmController;
+import com.android.systemui.statusbar.policy.NextAlarmControllerImpl;
+
+import java.util.Date;
+import java.util.Locale;
+
+import androidx.app.slice.Slice;
+import androidx.app.slice.SliceProvider;
+import androidx.app.slice.builders.ListBuilder;
+import androidx.app.slice.builders.ListBuilder.RowBuilder;
+
+/**
+ * Simple Slice provider that shows the current date.
+ */
+public class KeyguardSliceProvider extends SliceProvider implements
+ NextAlarmController.NextAlarmChangeCallback {
+
+ public static final String KEYGUARD_SLICE_URI = "content://com.android.systemui.keyguard/main";
+ public static final String KEYGUARD_DATE_URI = "content://com.android.systemui.keyguard/date";
+ public static final String KEYGUARD_NEXT_ALARM_URI =
+ "content://com.android.systemui.keyguard/alarm";
+
+ private final Date mCurrentTime = new Date();
+ protected final Uri mSliceUri;
+ protected final Uri mDateUri;
+ protected final Uri mAlarmUri;
+ private final Handler mHandler;
+ private String mDatePattern;
+ private DateFormat mDateFormat;
+ private String mLastText;
+ private boolean mRegistered;
+ private boolean mRegisteredEveryMinute;
+ private String mNextAlarm;
+ private NextAlarmController mNextAlarmController;
+
+ /**
+ * Receiver responsible for time ticking and updating the date format.
+ */
+ @VisibleForTesting
+ final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (Intent.ACTION_TIME_TICK.equals(action)
+ || Intent.ACTION_DATE_CHANGED.equals(action)
+ || Intent.ACTION_TIME_CHANGED.equals(action)
+ || Intent.ACTION_TIMEZONE_CHANGED.equals(action)
+ || Intent.ACTION_LOCALE_CHANGED.equals(action)) {
+ if (Intent.ACTION_LOCALE_CHANGED.equals(action)
+ || Intent.ACTION_TIMEZONE_CHANGED.equals(action)) {
+ // need to get a fresh date format
+ mHandler.post(KeyguardSliceProvider.this::cleanDateFormat);
+ }
+ mHandler.post(KeyguardSliceProvider.this::updateClock);
+ }
+ }
+ };
+
+ public KeyguardSliceProvider() {
+ this(new Handler());
+ }
+
+ @VisibleForTesting
+ KeyguardSliceProvider(Handler handler) {
+ mHandler = handler;
+ mSliceUri = Uri.parse(KEYGUARD_SLICE_URI);
+ mDateUri = Uri.parse(KEYGUARD_DATE_URI);
+ mAlarmUri = Uri.parse(KEYGUARD_NEXT_ALARM_URI);
+ }
+
+ @Override
+ public Slice onBindSlice(Uri sliceUri) {
+ ListBuilder builder = new ListBuilder(getContext(), mSliceUri);
+ builder.addRow(new RowBuilder(builder, mDateUri).setTitle(mLastText));
+ if (!TextUtils.isEmpty(mNextAlarm)) {
+ Icon icon = Icon.createWithResource(getContext(), R.drawable.ic_access_alarms_big);
+ builder.addRow(new RowBuilder(builder, mAlarmUri)
+ .setTitle(mNextAlarm).addEndItem(icon));
+ }
+
+ return builder.build();
+ }
+
+ @Override
+ public boolean onCreateSliceProvider() {
+ mNextAlarmController = new NextAlarmControllerImpl(getContext());
+ mNextAlarmController.addCallback(this);
+ mDatePattern = getContext().getString(R.string.system_ui_aod_date_pattern);
+ registerClockUpdate(false /* everyMinute */);
+ updateClock();
+ return true;
+ }
+
+ public static String formatNextAlarm(Context context, AlarmManager.AlarmClockInfo info) {
+ if (info == null) {
+ return "";
+ }
+ String skeleton = android.text.format.DateFormat
+ .is24HourFormat(context, ActivityManager.getCurrentUser()) ? "EHm" : "Ehma";
+ String pattern = android.text.format.DateFormat
+ .getBestDateTimePattern(Locale.getDefault(), skeleton);
+ return android.text.format.DateFormat.format(pattern, info.getTriggerTime()).toString();
+ }
+
+ /**
+ * Registers a broadcast receiver for clock updates, include date, time zone and manually
+ * changing the date/time via the settings app.
+ *
+ * @param everyMinute {@code true} if you also want updates every minute.
+ */
+ protected void registerClockUpdate(boolean everyMinute) {
+ if (mRegistered) {
+ if (mRegisteredEveryMinute == everyMinute) {
+ return;
+ } else {
+ unregisterClockUpdate();
+ }
+ }
+
+ IntentFilter filter = new IntentFilter();
+ if (everyMinute) {
+ filter.addAction(Intent.ACTION_TIME_TICK);
+ }
+ filter.addAction(Intent.ACTION_DATE_CHANGED);
+ filter.addAction(Intent.ACTION_TIME_CHANGED);
+ filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
+ filter.addAction(Intent.ACTION_LOCALE_CHANGED);
+ getContext().registerReceiver(mIntentReceiver, filter, null /* permission*/,
+ null /* scheduler */);
+ mRegistered = true;
+ mRegisteredEveryMinute = everyMinute;
+ }
+
+ protected void unregisterClockUpdate() {
+ if (!mRegistered) {
+ return;
+ }
+ getContext().unregisterReceiver(mIntentReceiver);
+ mRegistered = false;
+ }
+
+ @VisibleForTesting
+ boolean isRegistered() {
+ return mRegistered;
+ }
+
+ protected void updateClock() {
+ final String text = getFormattedDate();
+ if (!text.equals(mLastText)) {
+ mLastText = text;
+ getContext().getContentResolver().notifyChange(mSliceUri, null /* observer */);
+ }
+ }
+
+ protected String getFormattedDate() {
+ if (mDateFormat == null) {
+ final Locale l = Locale.getDefault();
+ DateFormat format = DateFormat.getInstanceForSkeleton(mDatePattern, l);
+ format.setContext(DisplayContext.CAPITALIZATION_FOR_STANDALONE);
+ mDateFormat = format;
+ }
+ mCurrentTime.setTime(System.currentTimeMillis());
+ return mDateFormat.format(mCurrentTime);
+ }
+
+ @VisibleForTesting
+ void cleanDateFormat() {
+ mDateFormat = null;
+ }
+
+ @Override
+ public void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm) {
+ mNextAlarm = formatNextAlarm(getContext(), nextAlarm);
+ getContext().getContentResolver().notifyChange(mSliceUri, null /* observer */);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 28adca97b8bf..eedc50f23d60 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -25,9 +25,9 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STR
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT;
-import android.app.Activity;
import android.app.ActivityManager;
import android.app.AlarmManager;
+import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.StatusBarManager;
import android.app.trust.TrustManager;
@@ -52,14 +52,13 @@ import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
-import android.provider.Settings.System;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.util.EventLog;
import android.util.Log;
import android.util.Slog;
import android.view.ViewGroup;
-import android.view.WindowManagerPolicy;
+import android.view.WindowManagerPolicyConstants;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
@@ -76,20 +75,16 @@ import com.android.keyguard.KeyguardDisplayManager;
import com.android.keyguard.KeyguardSecurityView;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.keyguard.LatencyTracker;
+import com.android.internal.util.LatencyTracker;
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.Dependency;
import com.android.systemui.SystemUI;
import com.android.systemui.SystemUIFactory;
import com.android.systemui.UiOffloadThread;
import com.android.systemui.classifier.FalsingManager;
-import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.statusbar.phone.FingerprintUnlockController;
import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.StatusBarWindowManager;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -128,7 +123,7 @@ import java.util.ArrayList;
* false, this will override all other conditions for turning on the keyguard.
*
* Threading and synchronization:
- * This class is created by the initialization routine of the {@link android.view.WindowManagerPolicy},
+ * This class is created by the initialization routine of the {@link WindowManagerPolicyConstants},
* and runs on its thread. The keyguard UI is created from that thread in the
* constructor of this class. The apis may be called from other threads, including the
* {@link com.android.server.input.InputManagerService}'s and {@link android.view.WindowManager}'s.
@@ -168,7 +163,6 @@ public class KeyguardViewMediator extends SystemUI {
private static final int NOTIFY_SCREEN_TURNED_ON = 15;
private static final int NOTIFY_SCREEN_TURNED_OFF = 16;
private static final int NOTIFY_STARTED_GOING_TO_SLEEP = 17;
- private static final int SET_SWITCHING_USER = 18;
/**
* The default amount of time we stay awake (used for all key input)
@@ -349,6 +343,7 @@ public class KeyguardViewMediator extends SystemUI {
private boolean mWakeAndUnlocking;
private IKeyguardDrawnCallback mDrawnCallback;
private boolean mLockWhenSimRemoved;
+ private CharSequence mCustomMessage;
KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() {
@@ -373,7 +368,7 @@ public class KeyguardViewMediator extends SystemUI {
return;
} else if (info.isGuest() || info.isDemo()) {
// If we just switched to a guest, try to dismiss keyguard.
- dismiss(null /* callback */);
+ dismiss(null /* callback */, null /* message */);
}
}
}
@@ -618,6 +613,13 @@ public class KeyguardViewMediator extends SystemUI {
}
@Override
+ public void onBouncerVisiblityChanged(boolean shown) {
+ synchronized (KeyguardViewMediator.this) {
+ adjustStatusBarLocked(shown);
+ }
+ }
+
+ @Override
public void playTrustedSound() {
KeyguardViewMediator.this.playTrustedSound();
}
@@ -652,6 +654,13 @@ public class KeyguardViewMediator extends SystemUI {
}
@Override
+ public CharSequence consumeCustomMessage() {
+ final CharSequence message = mCustomMessage;
+ mCustomMessage = null;
+ return message;
+ }
+
+ @Override
public void onSecondaryDisplayShowingChanged(int displayId) {
synchronized (KeyguardViewMediator.this) {
setShowingLocked(mShowing, displayId, false);
@@ -698,6 +707,9 @@ public class KeyguardViewMediator extends SystemUI {
&& !mLockPatternUtils.isLockScreenDisabled(
KeyguardUpdateMonitor.getCurrentUser()),
mSecondaryDisplayShowing, true /* forceCallbacks */);
+ } else {
+ // The system's keyguard is disabled or missing.
+ setShowingLocked(false, mSecondaryDisplayShowing, true);
}
mStatusBarKeyguardViewManager =
@@ -765,8 +777,8 @@ public class KeyguardViewMediator extends SystemUI {
/**
* Called to let us know the screen was turned off.
- * @param why either {@link android.view.WindowManagerPolicy#OFF_BECAUSE_OF_USER} or
- * {@link android.view.WindowManagerPolicy#OFF_BECAUSE_OF_TIMEOUT}.
+ * @param why either {@link WindowManagerPolicyConstants#OFF_BECAUSE_OF_USER} or
+ * {@link WindowManagerPolicyConstants#OFF_BECAUSE_OF_TIMEOUT}.
*/
public void onStartedGoingToSleep(int why) {
if (DEBUG) Log.d(TAG, "onStartedGoingToSleep(" + why + ")");
@@ -796,8 +808,8 @@ public class KeyguardViewMediator extends SystemUI {
}
} else if (mShowing) {
mPendingReset = true;
- } else if ((why == WindowManagerPolicy.OFF_BECAUSE_OF_TIMEOUT && timeout > 0)
- || (why == WindowManagerPolicy.OFF_BECAUSE_OF_USER && !lockImmediately)) {
+ } else if ((why == WindowManagerPolicyConstants.OFF_BECAUSE_OF_TIMEOUT && timeout > 0)
+ || (why == WindowManagerPolicyConstants.OFF_BECAUSE_OF_USER && !lockImmediately)) {
doKeyguardLaterLocked(timeout);
mLockLater = true;
} else if (!mLockPatternUtils.isLockScreenDisabled(currentUser)) {
@@ -870,7 +882,7 @@ public class KeyguardViewMediator extends SystemUI {
// From DevicePolicyAdmin
final long policyTimeout = mLockPatternUtils.getDevicePolicyManager()
- .getMaximumTimeToLockForUserAndProfiles(userId);
+ .getMaximumTimeToLock(null, userId);
long timeout;
@@ -1030,7 +1042,7 @@ public class KeyguardViewMediator extends SystemUI {
}
/**
- * Same semantics as {@link android.view.WindowManagerPolicy#enableKeyguard}; provide
+ * Same semantics as {@link WindowManagerPolicyConstants#enableKeyguard}; provide
* a way for external stuff to override normal keyguard behavior. For instance
* the phone app disables the keyguard when it receives incoming calls.
*/
@@ -1319,20 +1331,22 @@ public class KeyguardViewMediator extends SystemUI {
/**
* Dismiss the keyguard through the security layers.
* @param callback Callback to be informed about the result
+ * @param message Message that should be displayed on the bouncer.
*/
- private void handleDismiss(IKeyguardDismissCallback callback) {
+ private void handleDismiss(IKeyguardDismissCallback callback, CharSequence message) {
if (mShowing) {
if (callback != null) {
mDismissCallbackRegistry.addCallback(callback);
}
+ mCustomMessage = message;
mStatusBarKeyguardViewManager.dismissAndCollapse();
} else if (callback != null) {
new DismissCallbackWrapper(callback).notifyDismissError();
}
}
- public void dismiss(IKeyguardDismissCallback callback) {
- mHandler.obtainMessage(DISMISS, callback).sendToTarget();
+ public void dismiss(IKeyguardDismissCallback callback, CharSequence message) {
+ mHandler.obtainMessage(DISMISS, new DismissMessage(callback, message)).sendToTarget();
}
/**
@@ -1423,11 +1437,7 @@ public class KeyguardViewMediator extends SystemUI {
}
public void setSwitchingUser(boolean switching) {
- Trace.beginSection("KeyguardViewMediator#setSwitchingUser");
- mHandler.removeMessages(SET_SWITCHING_USER);
- Message msg = mHandler.obtainMessage(SET_SWITCHING_USER, switching ? 1 : 0, 0);
- mHandler.sendMessage(msg);
- Trace.endSection();
+ KeyguardUpdateMonitor.getInstance(mContext).setSwitchingUser(switching);
}
/**
@@ -1553,7 +1563,8 @@ public class KeyguardViewMediator extends SystemUI {
}
break;
case DISMISS:
- handleDismiss((IKeyguardDismissCallback) msg.obj);
+ final DismissMessage message = (DismissMessage) msg.obj;
+ handleDismiss(message.getCallback(), message.getMessage());
break;
case START_KEYGUARD_EXIT_ANIM:
Trace.beginSection("KeyguardViewMediator#handleMessage START_KEYGUARD_EXIT_ANIM");
@@ -1567,11 +1578,6 @@ public class KeyguardViewMediator extends SystemUI {
Log.w(TAG, "Timeout while waiting for activity drawn!");
Trace.endSection();
break;
- case SET_SWITCHING_USER:
- Trace.beginSection("KeyguardViewMediator#handleMessage SET_SWITCHING_USER");
- KeyguardUpdateMonitor.getInstance(mContext).setSwitchingUser(msg.arg1 != 0);
- Trace.endSection();
- break;
}
}
};
@@ -1691,6 +1697,8 @@ public class KeyguardViewMediator extends SystemUI {
mUiOffloadThread.submit(() -> {
// If the stream is muted, don't play the sound
if (mAudioManager.isStreamMute(mUiSoundsStreamType)) return;
+ // If DND blocks the sound, don't play the sound
+ if (areSystemSoundsZenModeBlocked(mContext)) return;
int id = mLockSounds.play(soundId,
mLockSoundVolume, mLockSoundVolume, 1/*priortiy*/, 0/*loop*/, 1.0f/*rate*/);
@@ -1702,6 +1710,25 @@ public class KeyguardViewMediator extends SystemUI {
}
}
+ private boolean areSystemSoundsZenModeBlocked(Context context) {
+ int zenMode = Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.ZEN_MODE, 0);
+
+ switch (zenMode) {
+ case Settings.Global.ZEN_MODE_NO_INTERRUPTIONS:
+ case Settings.Global.ZEN_MODE_ALARMS:
+ return true;
+ case Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
+ final NotificationManager noMan = (NotificationManager) context
+ .getSystemService(Context.NOTIFICATION_SERVICE);
+ return (noMan.getNotificationPolicy().priorityCategories
+ & NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER) == 0;
+ case Settings.Global.ZEN_MODE_OFF:
+ default:
+ return false;
+ }
+ }
+
private void playTrustedSound() {
playSound(mTrustedSoundId);
}
@@ -1758,13 +1785,13 @@ public class KeyguardViewMediator extends SystemUI {
int flags = 0;
if (mStatusBarKeyguardViewManager.shouldDisableWindowAnimationsForUnlock()
|| mWakeAndUnlocking) {
- flags |= WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS;
+ flags |= WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS;
}
if (mStatusBarKeyguardViewManager.isGoingToNotificationShade()) {
- flags |= WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
+ flags |= WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
}
if (mStatusBarKeyguardViewManager.isUnlockWithWallpaper()) {
- flags |= WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER;
+ flags |= WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER;
}
mUpdateMonitor.setKeyguardGoingAway(true /* goingAway */);
@@ -1855,6 +1882,10 @@ public class KeyguardViewMediator extends SystemUI {
}
private void adjustStatusBarLocked() {
+ adjustStatusBarLocked(false /* forceHideHomeRecentsButtons */);
+ }
+
+ private void adjustStatusBarLocked(boolean forceHideHomeRecentsButtons) {
if (mStatusBarManager == null) {
mStatusBarManager = (StatusBarManager)
mContext.getSystemService(Context.STATUS_BAR_SERVICE);
@@ -1865,19 +1896,14 @@ public class KeyguardViewMediator extends SystemUI {
// Disable aspects of the system/status/navigation bars that must not be re-enabled by
// windows that appear on top, ever
int flags = StatusBarManager.DISABLE_NONE;
- if (mShowing) {
- // Permanently disable components not available when keyguard is enabled
- // (like recents). Temporary enable/disable (e.g. the "back" button) are
- // done in KeyguardHostView.
- flags |= StatusBarManager.DISABLE_RECENT;
- }
- if (isShowingAndNotOccluded()) {
- flags |= StatusBarManager.DISABLE_HOME;
+ if (forceHideHomeRecentsButtons || isShowingAndNotOccluded()) {
+ flags |= StatusBarManager.DISABLE_HOME | StatusBarManager.DISABLE_RECENT;
}
if (DEBUG) {
Log.d(TAG, "adjustStatusBarLocked: mShowing=" + mShowing + " mOccluded=" + mOccluded
- + " isSecure=" + isSecure() + " --> flags=0x" + Integer.toHexString(flags));
+ + " isSecure=" + isSecure() + " force=" + forceHideHomeRecentsButtons
+ + " --> flags=0x" + Integer.toHexString(flags));
}
mStatusBarManager.disable(flags);
@@ -2006,12 +2032,9 @@ public class KeyguardViewMediator extends SystemUI {
}
public StatusBarKeyguardViewManager registerStatusBar(StatusBar statusBar,
- ViewGroup container,
- ScrimController scrimController,
- FingerprintUnlockController fingerprintUnlockController) {
+ ViewGroup container, FingerprintUnlockController fingerprintUnlockController) {
mStatusBarKeyguardViewManager.registerStatusBar(statusBar, container,
- scrimController, fingerprintUnlockController,
- mDismissCallbackRegistry);
+ fingerprintUnlockController, mDismissCallbackRegistry);
return mStatusBarKeyguardViewManager;
}
@@ -2151,4 +2174,22 @@ public class KeyguardViewMediator extends SystemUI {
}
}
}
+
+ private static class DismissMessage {
+ private final CharSequence mMessage;
+ private final IKeyguardDismissCallback mCallback;
+
+ DismissMessage(IKeyguardDismissCallback callback, CharSequence message) {
+ mCallback = callback;
+ mMessage = message;
+ }
+
+ public IKeyguardDismissCallback getCallback() {
+ return mCallback;
+ }
+
+ public CharSequence getMessage() {
+ return mMessage;
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
index f198229af691..b9e9e0a7f529 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
@@ -16,7 +16,6 @@
package com.android.systemui.keyguard;
-import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.IActivityManager;
@@ -27,35 +26,42 @@ import android.content.Intent;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
public class WorkLockActivityController {
+ private static final String TAG = WorkLockActivityController.class.getSimpleName();
+
private final Context mContext;
- private final SystemServicesProxy mSsp;
private final IActivityManager mIam;
public WorkLockActivityController(Context context) {
- this(context, SystemServicesProxy.getInstance(context), ActivityManager.getService());
+ this(context, ActivityManagerWrapper.getInstance(), ActivityManager.getService());
}
@VisibleForTesting
- WorkLockActivityController(Context context, SystemServicesProxy ssp, IActivityManager am) {
+ WorkLockActivityController(Context context, ActivityManagerWrapper am, IActivityManager iAm) {
mContext = context;
- mSsp = ssp;
- mIam = am;
+ mIam = iAm;
- mSsp.registerTaskStackListener(mLockListener);
+ am.registerTaskStackListener(mLockListener);
}
private void startWorkChallengeInTask(int taskId, int userId) {
+ ActivityManager.TaskDescription taskDescription = null;
+ try {
+ taskDescription = mIam.getTaskDescription(taskId);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to get description for task=" + taskId);
+ }
Intent intent = new Intent(KeyguardManager.ACTION_CONFIRM_DEVICE_CREDENTIAL_WITH_USER)
.setComponent(new ComponentName(mContext, WorkLockActivity.class))
.putExtra(Intent.EXTRA_USER_ID, userId)
- .putExtra(WorkLockActivity.EXTRA_TASK_DESCRIPTION, mSsp.getTaskDescription(taskId))
+ .putExtra(WorkLockActivity.EXTRA_TASK_DESCRIPTION, taskDescription)
.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
@@ -69,7 +75,11 @@ public class WorkLockActivityController {
} else {
// Starting the activity inside the task failed. We can't be sure why, so to be
// safe just remove the whole task if it still exists.
- mSsp.removeTask(taskId);
+ try {
+ mIam.removeTask(taskId);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to get description for task=" + taskId);
+ }
}
}
@@ -98,7 +108,7 @@ public class WorkLockActivityController {
}
}
- private final TaskStackListener mLockListener = new TaskStackListener() {
+ private final SysUiTaskStackChangeListener mLockListener = new SysUiTaskStackChangeListener() {
@Override
public void onTaskProfileLocked(int taskId, int userId) {
startWorkChallengeInTask(taskId, userId);
diff --git a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java
index b5c0d5386767..f5f06db348ab 100644
--- a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java
@@ -133,12 +133,19 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener
+ mNotificationRampTimeMs + "ms"); }
try {
Thread.sleep(mNotificationRampTimeMs);
- player.start();
} catch (InterruptedException e) {
Log.e(mTag, "Exception while sleeping to sync notification playback"
+ " with ducking", e);
}
- if (DEBUG) { Log.d(mTag, "player.start"); }
+ try {
+ player.start();
+ if (DEBUG) { Log.d(mTag, "player.start"); }
+ } catch (Exception e) {
+ player.release();
+ player = null;
+ // playing the notification didn't work, revert the focus request
+ abandonAudioFocusAfterError();
+ }
if (mPlayer != null) {
if (DEBUG) { Log.d(mTag, "mPlayer.release"); }
mPlayer.release();
@@ -147,6 +154,8 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener
}
catch (Exception e) {
Log.w(mTag, "error loading sound for " + mCmd.uri, e);
+ // playing the notification didn't work, revert the focus request
+ abandonAudioFocusAfterError();
}
this.notify();
}
@@ -154,6 +163,16 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener
}
};
+ private void abandonAudioFocusAfterError() {
+ synchronized (mQueueAudioFocusLock) {
+ if (mAudioManagerWithAudioFocus != null) {
+ if (DEBUG) Log.d(mTag, "abandoning focus after playback error");
+ mAudioManagerWithAudioFocus.abandonAudioFocus(null);
+ mAudioManagerWithAudioFocus = null;
+ }
+ }
+ }
+
private void startSound(Command cmd) {
// Preparing can be slow, so if there is something else
// is playing, let it continue until we're done, so there
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/InputConsumerController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/InputConsumerController.java
deleted file mode 100644
index abc5667251ea..000000000000
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/InputConsumerController.java
+++ /dev/null
@@ -1,161 +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.view.WindowManager.INPUT_CONSUMER_PIP;
-
-import android.os.Looper;
-import android.os.RemoteException;
-import android.util.Log;
-import android.view.BatchedInputEventReceiver;
-import android.view.Choreographer;
-import android.view.InputChannel;
-import android.view.InputEvent;
-import android.view.IWindowManager;
-import android.view.MotionEvent;
-
-import com.android.systemui.recents.misc.Utilities;
-
-import java.io.PrintWriter;
-
-/**
- * Manages the input consumer that allows the SystemUI to control the PiP.
- */
-public class InputConsumerController {
-
- private static final String TAG = InputConsumerController.class.getSimpleName();
-
- /**
- * Listener interface for callers to subscribe to touch events.
- */
- public interface TouchListener {
- boolean onTouchEvent(MotionEvent ev);
- }
-
- /**
- * Listener interface for callers to learn when this class is registered or unregistered with
- * window manager
- */
- public interface RegistrationListener {
- void onRegistrationChanged(boolean isRegistered);
- }
-
- /**
- * Input handler used for the PiP input consumer. Input events are batched and consumed with the
- * SurfaceFlinger vsync.
- */
- private final class PipInputEventReceiver extends BatchedInputEventReceiver {
-
- public PipInputEventReceiver(InputChannel inputChannel, Looper looper) {
- super(inputChannel, looper, Choreographer.getSfInstance());
- }
-
- @Override
- public void onInputEvent(InputEvent event, int displayId) {
- boolean handled = true;
- try {
- // To be implemented for input handling over Pip windows
- if (mListener != null && event instanceof MotionEvent) {
- MotionEvent ev = (MotionEvent) event;
- handled = mListener.onTouchEvent(ev);
- }
- } finally {
- finishInputEvent(event, handled);
- }
- }
- }
-
- private IWindowManager mWindowManager;
-
- private PipInputEventReceiver mInputEventReceiver;
- private TouchListener mListener;
- private RegistrationListener mRegistrationListener;
-
- public InputConsumerController(IWindowManager windowManager) {
- mWindowManager = windowManager;
- registerInputConsumer();
- }
-
- /**
- * Sets the touch listener.
- */
- public void setTouchListener(TouchListener listener) {
- mListener = listener;
- }
-
- /**
- * Sets the registration listener.
- */
- public void setRegistrationListener(RegistrationListener listener) {
- mRegistrationListener = listener;
- if (mRegistrationListener != null) {
- mRegistrationListener.onRegistrationChanged(mInputEventReceiver != null);
- }
- }
-
- /**
- * Check if the InputConsumer is currently registered with WindowManager
- *
- * @return {@code true} if registered, {@code false} if not.
- */
- public boolean isRegistered() {
- return mInputEventReceiver != null;
- }
-
- /**
- * Registers the input consumer.
- */
- public void registerInputConsumer() {
- if (mInputEventReceiver == null) {
- final InputChannel inputChannel = new InputChannel();
- try {
- mWindowManager.destroyInputConsumer(INPUT_CONSUMER_PIP);
- mWindowManager.createInputConsumer(INPUT_CONSUMER_PIP, inputChannel);
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to create PIP input consumer", e);
- }
- mInputEventReceiver = new PipInputEventReceiver(inputChannel, Looper.myLooper());
- if (mRegistrationListener != null) {
- mRegistrationListener.onRegistrationChanged(true /* isRegistered */);
- }
- }
- }
-
- /**
- * Unregisters the input consumer.
- */
- public void unregisterInputConsumer() {
- if (mInputEventReceiver != null) {
- try {
- mWindowManager.destroyInputConsumer(INPUT_CONSUMER_PIP);
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to destroy PIP input consumer", e);
- }
- mInputEventReceiver.dispose();
- mInputEventReceiver = null;
- if (mRegistrationListener != null) {
- mRegistrationListener.onRegistrationChanged(false /* isRegistered */);
- }
- }
- }
-
- public void dump(PrintWriter pw, String prefix) {
- final String innerPrefix = prefix + " ";
- pw.println(prefix + TAG);
- pw.println(innerPrefix + "registered=" + (mInputEventReceiver != null));
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipAppOpsListener.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipAppOpsListener.java
new file mode 100644
index 000000000000..f0e4ccc139ca
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipAppOpsListener.java
@@ -0,0 +1,89 @@
+/*
+ * 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.util.Pair;
+
+public class PipAppOpsListener {
+ private static final String TAG = PipAppOpsListener.class.getSimpleName();
+
+ private Context mContext;
+ private IActivityManager mActivityManager;
+ private AppOpsManager mAppOpsManager;
+
+ private PipMotionHelper mMotionHelper;
+
+ 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.getTopPinnedActivity(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) {
+ mMotionHelper.dismissPip();
+ }
+ }
+ } catch (NameNotFoundException e) {
+ // Unregister the listener if the package can't be found
+ unregisterAppOpsListener();
+ }
+ }
+ };
+
+ public PipAppOpsListener(Context context, IActivityManager activityManager,
+ PipMotionHelper motionHelper) {
+ mContext = context;
+ mActivityManager = activityManager;
+ mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
+ mMotionHelper = motionHelper;
+ }
+
+ 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);
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
index bae9ef8abfba..24d0126a1494 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -16,11 +16,10 @@
package com.android.systemui.pip.phone;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.INPUT_CONSUMER_PIP;
import android.app.ActivityManager;
-import android.app.ActivityManager.StackInfo;
import android.app.IActivityManager;
import android.content.ComponentName;
import android.content.Context;
@@ -39,9 +38,10 @@ import android.view.WindowManagerGlobal;
import com.android.systemui.pip.BasePipManager;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.component.ExpandPipEvent;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
-import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.InputConsumerController;
import java.io.PrintWriter;
@@ -63,20 +63,19 @@ public class PipManager implements BasePipManager {
private InputConsumerController mInputConsumerController;
private PipMenuActivityController mMenuController;
private PipMediaController mMediaController;
- private PipNotificationController mNotificationController;
private PipTouchHandler mTouchHandler;
+ private PipAppOpsListener mAppOpsListener;
/**
* Handler for system task stack changes.
*/
- TaskStackListener mTaskStackListener = new TaskStackListener() {
+ SysUiTaskStackChangeListener mTaskStackListener = new SysUiTaskStackChangeListener() {
@Override
- public void onActivityPinned(String packageName, int userId, int taskId) {
+ public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
mTouchHandler.onActivityPinned();
mMediaController.onActivityPinned();
mMenuController.onActivityPinned();
- mNotificationController.onActivityPinned(packageName, userId,
- true /* deferUntilAnimationEnds */);
+ mAppOpsListener.onActivityPinned(packageName);
SystemServicesProxy.getInstance(mContext).setPipVisibility(true);
}
@@ -89,7 +88,7 @@ public class PipManager implements BasePipManager {
final int userId = topActivity != null ? topPipActivityInfo.second : 0;
mMenuController.onActivityUnpinned();
mTouchHandler.onActivityUnpinned(topActivity);
- mNotificationController.onActivityUnpinned(topActivity, userId);
+ mAppOpsListener.onActivityUnpinned();
SystemServicesProxy.getInstance(mContext).setPipVisibility(topActivity != null);
}
@@ -106,7 +105,6 @@ public class PipManager implements BasePipManager {
mTouchHandler.setTouchEnabled(true);
mTouchHandler.onPinnedStackAnimationEnded();
mMenuController.onPinnedStackAnimationEnded();
- mNotificationController.onPinnedStackAnimationEnded();
}
@Override
@@ -173,15 +171,16 @@ public class PipManager implements BasePipManager {
} catch (RemoteException e) {
Log.e(TAG, "Failed to register pinned stack listener", e);
}
- SystemServicesProxy.getInstance(mContext).registerTaskStackListener(mTaskStackListener);
+ ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
- mInputConsumerController = new InputConsumerController(mWindowManager);
+ mInputConsumerController = InputConsumerController.getPipInputConsumer();
+ mInputConsumerController.registerInputConsumer();
mMediaController = new PipMediaController(context, mActivityManager);
mMenuController = new PipMenuActivityController(context, mActivityManager, mMediaController,
mInputConsumerController);
mTouchHandler = new PipTouchHandler(context, mActivityManager, mMenuController,
mInputConsumerController);
- mNotificationController = new PipNotificationController(context, mActivityManager,
+ mAppOpsListener = new PipAppOpsListener(context, mActivityManager,
mTouchHandler.getMotionHelper());
EventBus.getDefault().register(this);
}
@@ -197,19 +196,6 @@ public class PipManager implements BasePipManager {
* Expands the PIP.
*/
public final void onBusEvent(ExpandPipEvent event) {
- if (event.clearThumbnailWindows) {
- try {
- StackInfo stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
- if (stackInfo != null && stackInfo.taskIds != null) {
- SystemServicesProxy ssp = SystemServicesProxy.getInstance(mContext);
- for (int taskId : stackInfo.taskIds) {
- ssp.cancelThumbnailTransition(taskId);
- }
- }
- } catch (RemoteException e) {
- // Do nothing
- }
- }
mTouchHandler.getMotionHelper().expandPip(false /* skipAnimation */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
index 90f7b8db1c59..0486a9dcca74 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
@@ -16,6 +16,10 @@
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 com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_ACTIONS;
import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_ALLOW_TIMEOUT;
import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_CONTROLLER_MESSENGER;
@@ -39,6 +43,7 @@ import android.app.Activity;
import android.app.ActivityManager;
import android.app.PendingIntent.CanceledException;
import android.app.RemoteAction;
+import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ParceledListSlice;
import android.graphics.Color;
@@ -46,12 +51,15 @@ import android.graphics.PointF;
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.Message;
import android.os.Messenger;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.util.Log;
+import android.util.Pair;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
@@ -105,6 +113,7 @@ public class PipMenuActivity extends Activity {
private Drawable mBackgroundDrawable;
private View mMenuContainer;
private LinearLayout mActionsGroup;
+ private View mSettingsButton;
private View mDismissButton;
private ImageView mExpandButton;
private int mBetweenActionPaddingLand;
@@ -218,6 +227,11 @@ public class PipMenuActivity extends Activity {
}
return true;
});
+ mSettingsButton = findViewById(R.id.settings);
+ mSettingsButton.setAlpha(0);
+ mSettingsButton.setOnClickListener((v) -> {
+ showSettings();
+ });
mDismissButton = findViewById(R.id.dismiss);
mDismissButton.setAlpha(0);
mDismissButton.setOnClickListener((v) -> {
@@ -352,12 +366,14 @@ public class PipMenuActivity extends Activity {
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);
if (menuState == MENU_STATE_FULL) {
- mMenuContainerAnimator.playTogether(menuAnim, dismissAnim);
+ mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim);
} else {
- mMenuContainerAnimator.play(dismissAnim);
+ mMenuContainerAnimator.playTogether(dismissAnim);
}
mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN);
mMenuContainerAnimator.setDuration(MENU_FADE_DURATION);
@@ -394,9 +410,11 @@ public class PipMenuActivity extends Activity {
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);
- mMenuContainerAnimator.playTogether(menuAnim, dismissAnim);
+ mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim);
mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_OUT);
mMenuContainerAnimator.setDuration(MENU_FADE_DURATION);
mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() {
@@ -526,12 +544,14 @@ public class PipMenuActivity extends Activity {
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) {
+ mSettingsButton.setAlpha(menuAlpha);
mDismissButton.setAlpha(menuAlpha);
}
alpha = (int) (fraction * DISMISS_BACKGROUND_ALPHA * 255);
@@ -588,6 +608,19 @@ public class PipMenuActivity extends Activity {
sendMessage(m, "Could not notify controller to show PIP menu");
}
+ private void showSettings() {
+ final Pair<ComponentName, Integer> topPipActivityInfo =
+ PipUtils.getTopPinnedActivity(this, ActivityManager.getService());
+ if (topPipActivityInfo.first != null) {
+ final UserHandle user = UserHandle.of(topPipActivityInfo.second);
+ final Intent settingsIntent = new Intent(ACTION_PICTURE_IN_PICTURE_SETTINGS,
+ Uri.fromParts("package", topPipActivityInfo.first.getPackageName(), null));
+ settingsIntent.putExtra(Intent.EXTRA_USER_HANDLE, user);
+ settingsIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
+ startActivity(settingsIntent);
+ }
+ }
+
private void notifyActivityCallback(Messenger callback) {
Message m = Message.obtain();
m.what = PipMenuActivityController.MESSAGE_UPDATE_ACTIVITY_CALLBACK;
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
index 68743b34884d..26fced307bac 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
@@ -16,13 +16,13 @@
package com.android.systemui.pip.phone;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import android.app.ActivityManager.StackInfo;
import android.app.ActivityOptions;
import android.app.IActivityManager;
import android.app.RemoteAction;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ParceledListSlice;
@@ -42,6 +42,7 @@ import com.android.systemui.pip.phone.PipMediaController.ActionListener;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.component.HidePipMenuEvent;
import com.android.systemui.recents.misc.ReferenceCountedTrigger;
+import com.android.systemui.shared.system.InputConsumerController;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -383,7 +384,8 @@ public class PipMenuActivityController {
private void startMenuActivity(int menuState, Rect stackBounds, Rect movementBounds,
boolean allowMenuTimeout, boolean willResizeMenu) {
try {
- StackInfo pinnedStackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
+ StackInfo pinnedStackInfo = mActivityManager.getStackInfo(
+ WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
if (pinnedStackInfo != null && pinnedStackInfo.taskIds != null &&
pinnedStackInfo.taskIds.length > 0) {
Intent intent = new Intent(mContext, PipMenuActivity.class);
@@ -421,7 +423,8 @@ public class PipMenuActivityController {
// Fetch the pinned stack bounds
Rect stackBounds = null;
try {
- StackInfo pinnedStackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
+ StackInfo pinnedStackInfo = mActivityManager.getStackInfo(
+ WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
if (pinnedStackInfo != null) {
stackBounds = pinnedStackInfo.bounds;
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
index cebb22f07aaf..21a836c030cb 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
@@ -16,8 +16,10 @@
package com.android.systemui.pip.phone;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static com.android.systemui.Interpolators.FAST_OUT_LINEAR_IN;
import static com.android.systemui.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.systemui.Interpolators.LINEAR_OUT_SLOW_IN;
@@ -121,7 +123,8 @@ public class PipMotionHelper implements Handler.Callback {
void synchronizePinnedStackBounds() {
cancelAnimations();
try {
- StackInfo stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
+ StackInfo stackInfo =
+ mActivityManager.getStackInfo(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
if (stackInfo != null) {
mBounds.set(stackInfo.bounds);
}
@@ -158,13 +161,7 @@ public class PipMotionHelper implements Handler.Callback {
mMenuController.hideMenuWithoutResize();
mHandler.post(() -> {
try {
- if (skipAnimation) {
- mActivityManager.moveTasksToFullscreenStack(PINNED_STACK_ID, true /* onTop */);
- } else {
- mActivityManager.resizeStack(PINNED_STACK_ID, null /* bounds */,
- true /* allowResizeInDockedMode */, true /* preserveWindows */,
- true /* animate */, EXPAND_STACK_TO_FULLSCREEN_DURATION);
- }
+ mActivityManager.dismissPip(!skipAnimation, EXPAND_STACK_TO_FULLSCREEN_DURATION);
} catch (RemoteException e) {
Log.e(TAG, "Error expanding PiP activity", e);
}
@@ -182,7 +179,7 @@ public class PipMotionHelper implements Handler.Callback {
mMenuController.hideMenuWithoutResize();
mHandler.post(() -> {
try {
- mActivityManager.removeStack(PINNED_STACK_ID);
+ mActivityManager.removeStacksInWindowingModes(new int[]{ WINDOWING_MODE_PINNED });
} catch (RemoteException e) {
Log.e(TAG, "Failed to remove PiP", e);
}
@@ -529,14 +526,15 @@ public class PipMotionHelper implements Handler.Callback {
Rect toBounds = (Rect) args.arg1;
int duration = args.argi1;
try {
- StackInfo stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
+ StackInfo stackInfo = mActivityManager.getStackInfo(
+ WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
if (stackInfo == null) {
// In the case where we've already re-expanded or dismissed the PiP, then
// just skip the resize
return true;
}
- mActivityManager.resizeStack(PINNED_STACK_ID, toBounds,
+ mActivityManager.resizeStack(stackInfo.stackId, toBounds,
false /* allowResizeInDockedMode */, true /* preserveWindows */,
true /* animate */, duration);
mBounds.set(toBounds);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipNotificationController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipNotificationController.java
deleted file mode 100644
index 6d083e9d601d..000000000000
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipNotificationController.java
+++ /dev/null
@@ -1,231 +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 static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
-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 android.app.AppOpsManager;
-import android.app.AppOpsManager.OnOpChangedListener;
-import android.app.IActivityManager;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-import android.graphics.Canvas;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.UserHandle;
-import android.util.IconDrawableFactory;
-import android.util.Log;
-import android.util.Pair;
-
-import com.android.systemui.R;
-import com.android.systemui.SystemUI;
-import com.android.systemui.util.NotificationChannels;
-
-/**
- * Manages the BTW notification that shows whenever an activity enters or leaves picture-in-picture.
- */
-public class PipNotificationController {
- private static final String TAG = PipNotificationController.class.getSimpleName();
-
- private static final String NOTIFICATION_TAG = PipNotificationController.class.getName();
- private static final int NOTIFICATION_ID = 0;
-
- private Context mContext;
- private IActivityManager mActivityManager;
- private AppOpsManager mAppOpsManager;
- private NotificationManager mNotificationManager;
- private IconDrawableFactory mIconDrawableFactory;
-
- private PipMotionHelper mMotionHelper;
-
- // Used when building a deferred notification
- private String mDeferredNotificationPackageName;
- private int mDeferredNotificationUserId;
-
- 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.getTopPinnedActivity(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) {
- mMotionHelper.dismissPip();
- }
- }
- } catch (NameNotFoundException e) {
- // Unregister the listener if the package can't be found
- unregisterAppOpsListener();
- }
- }
- };
-
- public PipNotificationController(Context context, IActivityManager activityManager,
- PipMotionHelper motionHelper) {
- mContext = context;
- mActivityManager = activityManager;
- mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
- mNotificationManager = NotificationManager.from(context);
- mMotionHelper = motionHelper;
- mIconDrawableFactory = IconDrawableFactory.newInstance(context);
- }
-
- public void onActivityPinned(String packageName, int userId, boolean deferUntilAnimationEnds) {
- // Clear any existing notification
- mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID);
-
- if (deferUntilAnimationEnds) {
- mDeferredNotificationPackageName = packageName;
- mDeferredNotificationUserId = userId;
- } else {
- showNotificationForApp(packageName, userId);
- }
-
- // Register for changes to the app ops setting for this package while it is in PiP
- registerAppOpsListener(packageName);
- }
-
- public void onPinnedStackAnimationEnded() {
- if (mDeferredNotificationPackageName != null) {
- showNotificationForApp(mDeferredNotificationPackageName, mDeferredNotificationUserId);
- mDeferredNotificationPackageName = null;
- mDeferredNotificationUserId = 0;
- }
- }
-
- public void onActivityUnpinned(ComponentName topPipActivity, int userId) {
- // Unregister for changes to the previously PiP'ed package
- unregisterAppOpsListener();
-
- // Reset the deferred notification package
- mDeferredNotificationPackageName = null;
- mDeferredNotificationUserId = 0;
-
- if (topPipActivity != null) {
- // onActivityUnpinned() is only called after the transition is complete, so we don't
- // need to defer until the animation ends to update the notification
- onActivityPinned(topPipActivity.getPackageName(), userId,
- false /* deferUntilAnimationEnds */);
- } else {
- mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID);
- }
- }
-
- /**
- * Builds and shows the notification for the given app.
- */
- private void showNotificationForApp(String packageName, int userId) {
- // Build a new notification
- try {
- final UserHandle user = UserHandle.of(userId);
- final Context userContext = mContext.createPackageContextAsUser(
- mContext.getPackageName(), 0, user);
- final Notification.Builder builder =
- new Notification.Builder(userContext, NotificationChannels.GENERAL)
- .setLocalOnly(true)
- .setOngoing(true)
- .setSmallIcon(R.drawable.pip_notification_icon)
- .setColor(mContext.getColor(
- com.android.internal.R.color.system_notification_accent_color));
- if (updateNotificationForApp(builder, packageName, user)) {
- SystemUI.overrideNotificationAppName(mContext, builder);
-
- // Show the new notification
- mNotificationManager.notify(NOTIFICATION_TAG, NOTIFICATION_ID, builder.build());
- }
- } catch (NameNotFoundException e) {
- Log.e(TAG, "Could not show notification for application", e);
- }
- }
-
- /**
- * Updates the notification builder with app-specific information, returning whether it was
- * successful.
- */
- private boolean updateNotificationForApp(Notification.Builder builder, String packageName,
- UserHandle user) throws NameNotFoundException {
- final PackageManager pm = mContext.getPackageManager();
- final ApplicationInfo appInfo;
- try {
- appInfo = pm.getApplicationInfoAsUser(packageName, 0, user.getIdentifier());
- } catch (NameNotFoundException e) {
- Log.e(TAG, "Could not update notification for application", e);
- return false;
- }
-
- if (appInfo != null) {
- final String appName = pm.getUserBadgedLabel(pm.getApplicationLabel(appInfo), user)
- .toString();
- final String message = mContext.getString(R.string.pip_notification_message, appName);
- final Intent settingsIntent = new Intent(ACTION_PICTURE_IN_PICTURE_SETTINGS,
- Uri.fromParts("package", packageName, null));
- settingsIntent.putExtra(Intent.EXTRA_USER_HANDLE, user);
- settingsIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
-
- final Drawable iconDrawable = mIconDrawableFactory.getBadgedIcon(appInfo);
- builder.setContentTitle(mContext.getString(R.string.pip_notification_title, appName))
- .setContentText(message)
- .setContentIntent(PendingIntent.getActivityAsUser(mContext, packageName.hashCode(),
- settingsIntent, FLAG_CANCEL_CURRENT, null, user))
- .setStyle(new Notification.BigTextStyle().bigText(message))
- .setLargeIcon(createBitmap(iconDrawable).createAshmemBitmap());
- return true;
- }
- return false;
- }
-
- private void registerAppOpsListener(String packageName) {
- mAppOpsManager.startWatchingMode(OP_PICTURE_IN_PICTURE, packageName,
- mAppOpsChangedListener);
- }
-
- private void unregisterAppOpsListener() {
- mAppOpsManager.stopWatchingMode(mAppOpsChangedListener);
- }
-
- /**
- * Bakes a drawable into a bitmap.
- */
- private Bitmap createBitmap(Drawable d) {
- Bitmap bitmap = Bitmap.createBitmap(d.getIntrinsicWidth(), d.getIntrinsicHeight(),
- Config.ARGB_8888);
- Canvas c = new Canvas(bitmap);
- d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
- d.draw(c);
- c.setBitmap(null);
- return bitmap;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index 2b48e0fb32bd..b25351731a35 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -42,11 +42,11 @@ import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityWindowInfo;
-
-import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.os.logging.MetricsLoggerWrapper;
import com.android.internal.policy.PipSnapAlgorithm;
import com.android.systemui.R;
+import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.statusbar.FlingAnimationUtils;
import java.io.PrintWriter;
@@ -63,10 +63,6 @@ public class PipTouchHandler {
// Allow the PIP to be flung from anywhere on the screen to the bottom to be dismissed.
private static final boolean ENABLE_FLING_DISMISS = false;
- // These values are used for metrics and should never change
- private static final int METRIC_VALUE_DISMISSED_BY_TAP = 0;
- private static final int METRIC_VALUE_DISMISSED_BY_DRAG = 1;
-
private static final int SHOW_DISMISS_AFFORDANCE_DELAY = 225;
// Allow dragging the PIP to a location to close it
@@ -163,8 +159,7 @@ public class PipTouchHandler {
@Override
public void onPipDismiss() {
mMotionHelper.dismissPip();
- MetricsLogger.action(mContext, MetricsEvent.ACTION_PICTURE_IN_PICTURE_DISMISSED,
- METRIC_VALUE_DISMISSED_BY_TAP);
+ MetricsLoggerWrapper.logPictureInPictureDismissByTap(mContext);
}
@Override
@@ -463,8 +458,7 @@ public class PipTouchHandler {
return;
}
if (mIsMinimized != isMinimized) {
- MetricsLogger.action(mContext, MetricsEvent.ACTION_PICTURE_IN_PICTURE_MINIMIZED,
- isMinimized);
+ MetricsLoggerWrapper.logPictureInPictureMinimize(mContext, isMinimized);
}
mIsMinimized = isMinimized;
mSnapAlgorithm.setMinimized(isMinimized);
@@ -537,8 +531,7 @@ public class PipTouchHandler {
mMenuState = menuState;
updateMovementBounds(menuState);
if (menuState != MENU_STATE_CLOSE) {
- MetricsLogger.visibility(mContext, MetricsEvent.ACTION_PICTURE_IN_PICTURE_MENU,
- menuState == MENU_STATE_FULL);
+ MetricsLoggerWrapper.logPictureInPictureMenuVisible(mContext, menuState == MENU_STATE_FULL);
}
}
@@ -670,9 +663,7 @@ public class PipTouchHandler {
if (mMotionHelper.shouldDismissPip() || isFlingToBot) {
mMotionHelper.animateDismiss(mMotionHelper.getBounds(), vel.x,
vel.y, mUpdateScrimListener);
- MetricsLogger.action(mContext,
- MetricsEvent.ACTION_PICTURE_IN_PICTURE_DISMISSED,
- METRIC_VALUE_DISMISSED_BY_DRAG);
+ MetricsLoggerWrapper.logPictureInPictureDismissByDrag(mContext);
return true;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipUtils.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipUtils.java
index 56275fd043cf..2f53de96db2d 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipUtils.java
@@ -16,7 +16,8 @@
package com.android.systemui.pip.phone;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import android.app.ActivityManager.StackInfo;
import android.app.IActivityManager;
@@ -38,7 +39,8 @@ public class PipUtils {
IActivityManager activityManager) {
try {
final String sysUiPackageName = context.getPackageName();
- final StackInfo pinnedStackInfo = activityManager.getStackInfo(PINNED_STACK_ID);
+ final StackInfo pinnedStackInfo =
+ activityManager.getStackInfo(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
if (pinnedStackInfo != null && pinnedStackInfo.taskIds != null &&
pinnedStackInfo.taskIds.length > 0) {
for (int i = pinnedStackInfo.taskNames.length - 1; i >= 0; i--) {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
index 2d2869d8817b..a98468017f17 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
@@ -20,7 +20,6 @@ import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityManager.StackInfo;
import android.app.IActivityManager;
-import android.app.RemoteAction;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -46,14 +45,17 @@ import android.view.WindowManagerGlobal;
import com.android.systemui.R;
import com.android.systemui.pip.BasePipManager;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.view.Display.DEFAULT_DISPLAY;
/**
@@ -121,6 +123,7 @@ public class PipManager implements BasePipManager {
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;
@@ -232,7 +235,7 @@ public class PipManager implements BasePipManager {
mActivityManager = ActivityManager.getService();
mWindowManager = WindowManagerGlobal.getWindowManagerService();
- SystemServicesProxy.getInstance(context).registerTaskStackListener(mTaskStackListener);
+ ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_MEDIA_RESOURCE_GRANTED);
mContext.registerReceiver(mBroadcastReceiver, intentFilter);
@@ -336,9 +339,11 @@ public class PipManager implements BasePipManager {
mMediaSessionManager.removeOnActiveSessionsChangedListener(mActiveMediaSessionListener);
if (removePipStack) {
try {
- mActivityManager.removeStack(PINNED_STACK_ID);
+ mActivityManager.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) {
@@ -424,7 +429,7 @@ public class PipManager implements BasePipManager {
}
try {
int animationDurationMs = -1;
- mActivityManager.resizeStack(PINNED_STACK_ID, mCurrentPipBounds,
+ mActivityManager.resizeStack(mPinnedStackId, mCurrentPipBounds,
true, true, true, animationDurationMs);
} catch (RemoteException e) {
Log.e(TAG, "resizeStack failed", e);
@@ -502,7 +507,8 @@ public class PipManager implements BasePipManager {
private StackInfo getPinnedStackInfo() {
StackInfo stackInfo = null;
try {
- stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
+ stackInfo = mActivityManager.getStackInfo(
+ WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
} catch (RemoteException e) {
Log.e(TAG, "getStackInfo failed", e);
}
@@ -594,8 +600,8 @@ public class PipManager implements BasePipManager {
private boolean isSettingsShown() {
List<RunningTaskInfo> runningTasks;
try {
- runningTasks = mActivityManager.getTasks(1, 0);
- if (runningTasks == null || runningTasks.size() == 0) {
+ runningTasks = mActivityManager.getTasks(1);
+ if (runningTasks.isEmpty()) {
return false;
}
} catch (RemoteException e) {
@@ -615,7 +621,7 @@ public class PipManager implements BasePipManager {
return false;
}
- private TaskStackListener mTaskStackListener = new TaskStackListener() {
+ private SysUiTaskStackChangeListener mTaskStackListener = new SysUiTaskStackChangeListener() {
@Override
public void onTaskStackChanged() {
if (DEBUG) Log.d(TAG, "onTaskStackChanged()");
@@ -652,7 +658,7 @@ public class PipManager implements BasePipManager {
}
@Override
- public void onActivityPinned(String packageName, int userId, int taskId) {
+ public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
if (DEBUG) Log.d(TAG, "onActivityPinned()");
StackInfo stackInfo = getPinnedStackInfo();
@@ -661,6 +667,7 @@ public class PipManager implements BasePipManager {
return;
}
if (DEBUG) Log.d(TAG, "PINNED_STACK:" + stackInfo);
+ mPinnedStackId = stackInfo.stackId;
mPipTaskId = stackInfo.taskIds[stackInfo.taskIds.length - 1];
mPipComponentName = ComponentName.unflattenFromString(
stackInfo.taskNames[stackInfo.taskNames.length - 1]);
diff --git a/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimates.java b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimates.java
new file mode 100644
index 000000000000..bd130f4b40f3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimates.java
@@ -0,0 +1,26 @@
+package com.android.systemui.power;
+
+public interface EnhancedEstimates {
+
+ /**
+ * Returns a boolean indicating if the hybrid notification should be used.
+ */
+ boolean isHybridNotificationEnabled();
+
+ /**
+ * Returns an estimate object if the feature is enabled.
+ */
+ Estimate getEstimate();
+
+ /**
+ * Returns a long indicating the amount of time remaining in milliseconds under which we will
+ * show a regular warning to the user.
+ */
+ long getLowWarningThreshold();
+
+ /**
+ * Returns a long indicating the amount of time remaining in milliseconds under which we will
+ * show a severe warning to the user.
+ */
+ long getSevereWarningThreshold();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java
new file mode 100644
index 000000000000..5686d801bca2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java
@@ -0,0 +1,26 @@
+package com.android.systemui.power;
+
+import android.util.Log;
+
+public class EnhancedEstimatesImpl implements EnhancedEstimates {
+
+ @Override
+ public boolean isHybridNotificationEnabled() {
+ return false;
+ }
+
+ @Override
+ public Estimate getEstimate() {
+ return null;
+ }
+
+ @Override
+ public long getLowWarningThreshold() {
+ return 0;
+ }
+
+ @Override
+ public long getSevereWarningThreshold() {
+ return 0;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/power/Estimate.java b/packages/SystemUI/src/com/android/systemui/power/Estimate.java
new file mode 100644
index 000000000000..12a8f0a435b4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/power/Estimate.java
@@ -0,0 +1,11 @@
+package com.android.systemui.power;
+
+public class Estimate {
+ public final long estimateMillis;
+ public final boolean isBasedOnUsage;
+
+ public Estimate(long estimateMillis, boolean isBasedOnUsage) {
+ this.estimateMillis = estimateMillis;
+ this.isBasedOnUsage = isBasedOnUsage;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index c29b362bda13..3a2b12f4da23 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -17,11 +17,9 @@
package com.android.systemui.power;
import android.app.Notification;
-import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
@@ -29,23 +27,19 @@ import android.content.DialogInterface.OnDismissListener;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioAttributes;
-import android.net.Uri;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Looper;
import android.os.PowerManager;
-import android.os.SystemClock;
import android.os.UserHandle;
-import android.provider.Settings;
import android.support.annotation.VisibleForTesting;
import android.util.Slog;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
-import com.android.internal.notification.SystemNotificationChannels;
import com.android.settingslib.Utils;
+import com.android.settingslib.utils.PowerUtil;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
-import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.util.NotificationChannels;
@@ -96,8 +90,11 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
private long mScreenOffTime;
private int mShowing;
- private long mBucketDroppedNegativeTimeMs;
+ private long mWarningTriggerTimeMs;
+ private Estimate mEstimate;
+ private long mLowWarningThreshold;
+ private long mSevereWarningThreshold;
private boolean mWarning;
private boolean mPlaySound;
private boolean mInvalidCharger;
@@ -130,14 +127,29 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
public void update(int batteryLevel, int bucket, long screenOffTime) {
mBatteryLevel = batteryLevel;
if (bucket >= 0) {
- mBucketDroppedNegativeTimeMs = 0;
+ mWarningTriggerTimeMs = 0;
} else if (bucket < mBucket) {
- mBucketDroppedNegativeTimeMs = System.currentTimeMillis();
+ mWarningTriggerTimeMs = System.currentTimeMillis();
}
mBucket = bucket;
mScreenOffTime = screenOffTime;
}
+ @Override
+ public void updateEstimate(Estimate estimate) {
+ mEstimate = estimate;
+ if (estimate.estimateMillis <= mLowWarningThreshold) {
+ mWarningTriggerTimeMs = System.currentTimeMillis();
+ }
+ }
+
+ @Override
+ public void updateThresholds(long lowThreshold, long severeThreshold) {
+ mLowWarningThreshold = lowThreshold;
+ mSevereWarningThreshold = severeThreshold;
+ }
+
+
private void updateNotification() {
if (DEBUG) Slog.d(TAG, "updateNotification mWarning=" + mWarning + " mPlaySound="
+ mPlaySound + " mInvalidCharger=" + mInvalidCharger);
@@ -171,25 +183,40 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
mNoMan.notifyAsUser(TAG_BATTERY, SystemMessage.NOTE_BAD_CHARGER, n, UserHandle.ALL);
}
- private void showWarningNotification() {
- final int textRes = R.string.battery_low_percent_format;
- final String percentage = NumberFormat.getPercentInstance().format((double) mBatteryLevel / 100.0);
+ protected void showWarningNotification() {
+ final String percentage = NumberFormat.getPercentInstance()
+ .format((double) mBatteryLevel / 100.0);
+
+ // get standard notification copy
+ String title = mContext.getString(R.string.battery_low_title);
+ String contentText = mContext.getString(R.string.battery_low_percent_format, percentage);
+
+ // override notification copy if hybrid notification enabled
+ if (mEstimate != null) {
+ title = mContext.getString(R.string.battery_low_title_hybrid);
+ contentText = getHybridContentString(percentage);
+ }
final Notification.Builder nb =
new Notification.Builder(mContext, NotificationChannels.BATTERY)
.setSmallIcon(R.drawable.ic_power_low)
// Bump the notification when the bucket dropped.
- .setWhen(mBucketDroppedNegativeTimeMs)
+ .setWhen(mWarningTriggerTimeMs)
.setShowWhen(false)
- .setContentTitle(mContext.getString(R.string.battery_low_title))
- .setContentText(mContext.getString(textRes, percentage))
+ .setContentTitle(title)
+ .setContentText(contentText)
.setOnlyAlertOnce(true)
.setDeleteIntent(pendingBroadcast(ACTION_DISMISSED_WARNING))
- .setVisibility(Notification.VISIBILITY_PUBLIC)
- .setColor(Utils.getColorAttr(mContext, android.R.attr.colorError));
+ .setVisibility(Notification.VISIBILITY_PUBLIC);
if (hasBatterySettings()) {
nb.setContentIntent(pendingBroadcast(ACTION_SHOW_BATTERY_SETTINGS));
}
+ // Make the notification red if the percentage goes below a certain amount or the time
+ // remaining estimate is disabled
+ if (mEstimate == null || mBucket < 0
+ || mEstimate.estimateMillis < mSevereWarningThreshold) {
+ nb.setColor(Utils.getColorAttr(mContext, android.R.attr.colorError));
+ }
nb.addAction(0,
mContext.getString(R.string.battery_saver_start_action),
pendingBroadcast(ACTION_START_SAVER));
@@ -201,6 +228,14 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
mNoMan.notifyAsUser(TAG_BATTERY, SystemMessage.NOTE_POWER_LOW, n, UserHandle.ALL);
}
+ private String getHybridContentString(String percentage) {
+ return PowerUtil.getBatteryRemainingStringFormatted(
+ mContext,
+ mEstimate.estimateMillis,
+ percentage,
+ mEstimate.isBasedOnUsage);
+ }
+
private PendingIntent pendingBroadcast(String action) {
return PendingIntent.getBroadcastAsUser(mContext,
0, new Intent(action), 0, UserHandle.CURRENT);
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index f37826853fe5..ac86c8ae097d 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -28,8 +28,14 @@ import android.database.ContentObserver;
import android.os.BatteryManager;
import android.os.Handler;
import android.os.HardwarePropertiesManager;
+import android.os.IBinder;
+import android.os.IThermalEventListener;
+import android.os.IThermalService;
import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.SystemClock;
+import android.os.Temperature;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.format.DateUtils;
@@ -38,6 +44,7 @@ import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
+import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
@@ -53,6 +60,7 @@ public class PowerUI extends SystemUI {
private static final long TEMPERATURE_INTERVAL = 30 * DateUtils.SECOND_IN_MILLIS;
private static final long TEMPERATURE_LOGGING_INTERVAL = DateUtils.HOUR_IN_MILLIS;
private static final int MAX_RECENT_TEMPS = 125; // TEMPERATURE_LOGGING_INTERVAL plus a buffer
+ static final long THREE_HOURS_IN_MILLIS = DateUtils.HOUR_IN_MILLIS * 3;
private final Handler mHandler = new Handler();
private final Receiver mReceiver = new Receiver();
@@ -62,9 +70,12 @@ public class PowerUI extends SystemUI {
private WarningsUI mWarnings;
private final Configuration mLastConfiguration = new Configuration();
private int mBatteryLevel = 100;
- private int mBatteryStatus = BatteryManager.BATTERY_STATUS_UNKNOWN;
+ private long mTimeRemaining = Long.MAX_VALUE;
private int mPlugType = 0;
private int mInvalidCharger = 0;
+ private EnhancedEstimates mEnhancedEstimates;
+ private boolean mLowWarningShownThisChargeCycle;
+ private boolean mSevereWarningShownThisChargeCycle;
private int mLowBatteryAlertCloseLevel;
private final int[] mLowBatteryReminderLevels = new int[2];
@@ -75,9 +86,12 @@ public class PowerUI extends SystemUI {
private float[] mRecentTemps = new float[MAX_RECENT_TEMPS];
private int mNumTemps;
private long mNextLogTime;
+ private IThermalService mThermalService;
+
+ @VisibleForTesting int mBatteryStatus = BatteryManager.BATTERY_STATUS_UNKNOWN;
- // We create a method reference here so that we are guaranteed that we can remove a callback
// by using the same instance (method references are not guaranteed to be the same object
+ // We create a method reference here so that we are guaranteed that we can remove a callback
// each time they are created).
private final Runnable mUpdateTempCallback = this::updateTemperatureWarning;
@@ -87,6 +101,7 @@ public class PowerUI extends SystemUI {
mContext.getSystemService(Context.HARDWARE_PROPERTIES_SERVICE);
mScreenOffTime = mPowerManager.isScreenOn() ? -1 : SystemClock.elapsedRealtime();
mWarnings = Dependency.get(WarningsUI.class);
+ mEnhancedEstimates = Dependency.get(EnhancedEstimates.class);
mLastConfiguration.setTo(mContext.getResources().getConfiguration());
ContentObserver obs = new ContentObserver(mHandler) {
@@ -124,10 +139,19 @@ public class PowerUI extends SystemUI {
com.android.internal.R.integer.config_criticalBatteryWarningLevel);
final ContentResolver resolver = mContext.getContentResolver();
- int defWarnLevel = mContext.getResources().getInteger(
+ final int defWarnLevel = mContext.getResources().getInteger(
com.android.internal.R.integer.config_lowBatteryWarningLevel);
- int warnLevel = Settings.Global.getInt(resolver,
+ final int lowPowerModeTriggerLevel = Settings.Global.getInt(resolver,
Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, defWarnLevel);
+
+ // Note LOW_POWER_MODE_TRIGGER_LEVEL can take any value between 0 and 100, but
+ // for the UI purposes, let's cap it at 15% -- i.e. even if the trigger level is higher
+ // like 50%, let's not show the "low battery" notification until it hits
+ // config_lowBatteryWarningLevel, which is 15% by default.
+ // LOW_POWER_MODE_TRIGGER_LEVEL is still used in other places as-is. For example, if it's
+ // 50, then battery saver kicks in when the battery level hits 50%.
+ int warnLevel = Math.min(defWarnLevel, lowPowerModeTriggerLevel);
+
if (warnLevel == 0) {
warnLevel = defWarnLevel;
}
@@ -196,6 +220,12 @@ public class PowerUI extends SystemUI {
final boolean plugged = mPlugType != 0;
final boolean oldPlugged = oldPlugType != 0;
+ // if we are now unplugged but we were previously plugged in we should allow the
+ // time based trigger again.
+ if (!plugged && plugged != oldPlugged) {
+ mLowWarningShownThisChargeCycle = false;
+ mSevereWarningShownThisChargeCycle = false;
+ }
int oldBucket = findBatteryLevelBucket(oldBatteryLevel);
int bucket = findBatteryLevelBucket(mBatteryLevel);
@@ -224,21 +254,11 @@ public class PowerUI extends SystemUI {
return;
}
- boolean isPowerSaver = mPowerManager.isPowerSaveMode();
- if (!plugged
- && !isPowerSaver
- && (bucket < oldBucket || oldPlugged)
- && mBatteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
- && bucket < 0) {
-
- // only play SFX when the dialog comes up or the bucket changes
- final boolean playSound = bucket != oldBucket || oldPlugged;
- mWarnings.showLowBatteryWarning(playSound);
- } else if (isPowerSaver || plugged || (bucket > oldBucket && bucket > 0)) {
- mWarnings.dismissLowBatteryWarning();
- } else {
- mWarnings.updateLowBatteryWarning();
- }
+ // Show the correct version of low battery warning if needed
+ ThreadUtils.postOnBackgroundThread(() -> {
+ maybeShowBatteryWarning(plugged, oldPlugged, oldBucket, bucket);
+ });
+
} else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
mScreenOffTime = SystemClock.elapsedRealtime();
} else if (Intent.ACTION_SCREEN_ON.equals(action)) {
@@ -249,7 +269,83 @@ public class PowerUI extends SystemUI {
Slog.w(TAG, "unknown intent: " + intent);
}
}
- };
+ }
+
+ protected void maybeShowBatteryWarning(boolean plugged, boolean oldPlugged, int oldBucket,
+ int bucket) {
+ boolean isPowerSaver = mPowerManager.isPowerSaveMode();
+ // only play SFX when the dialog comes up or the bucket changes
+ final boolean playSound = bucket != oldBucket || oldPlugged;
+ if (mEnhancedEstimates.isHybridNotificationEnabled()) {
+ final Estimate estimate = mEnhancedEstimates.getEstimate();
+ // Turbo is not always booted once SysUI is running so we have ot make sure we actually
+ // get data back
+ if (estimate != null) {
+ mTimeRemaining = estimate.estimateMillis;
+ mWarnings.updateEstimate(estimate);
+ mWarnings.updateThresholds(mEnhancedEstimates.getLowWarningThreshold(),
+ mEnhancedEstimates.getSevereWarningThreshold());
+ }
+ }
+
+ if (shouldShowLowBatteryWarning(plugged, oldPlugged, oldBucket, bucket,
+ mTimeRemaining, isPowerSaver, mBatteryStatus)) {
+ mWarnings.showLowBatteryWarning(playSound);
+
+ // mark if we've already shown a warning this cycle. This will prevent the time based
+ // trigger from spamming users since the time remaining can vary based on current
+ // device usage.
+ if (mTimeRemaining < mEnhancedEstimates.getSevereWarningThreshold()) {
+ mSevereWarningShownThisChargeCycle = true;
+ } else {
+ mLowWarningShownThisChargeCycle = true;
+ }
+ } else if (shouldDismissLowBatteryWarning(plugged, oldBucket, bucket, mTimeRemaining,
+ isPowerSaver)) {
+ mWarnings.dismissLowBatteryWarning();
+ } else {
+ mWarnings.updateLowBatteryWarning();
+ }
+ }
+
+ @VisibleForTesting
+ boolean shouldShowLowBatteryWarning(boolean plugged, boolean oldPlugged, int oldBucket,
+ int bucket, long timeRemaining, boolean isPowerSaver, int mBatteryStatus) {
+ return !plugged
+ && !isPowerSaver
+ && (((bucket < oldBucket || oldPlugged) && bucket < 0)
+ || isTimeBasedTrigger(timeRemaining))
+ && mBatteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN;
+ }
+
+ @VisibleForTesting
+ boolean shouldDismissLowBatteryWarning(boolean plugged, int oldBucket, int bucket,
+ long timeRemaining, boolean isPowerSaver) {
+ final boolean hybridWouldDismiss = mEnhancedEstimates.isHybridNotificationEnabled()
+ && timeRemaining > mEnhancedEstimates.getLowWarningThreshold();
+ final boolean standardWouldDismiss = (bucket > oldBucket && bucket > 0);
+ return isPowerSaver
+ || plugged
+ || (standardWouldDismiss && (!mEnhancedEstimates.isHybridNotificationEnabled()
+ || hybridWouldDismiss));
+ }
+
+ private boolean isTimeBasedTrigger(long timeRemaining) {
+ if (!mEnhancedEstimates.isHybridNotificationEnabled()) {
+ return false;
+ }
+
+ // Only show the time based warning once per charge cycle
+ final boolean canShowWarning = timeRemaining < mEnhancedEstimates.getLowWarningThreshold()
+ && !mLowWarningShownThisChargeCycle;
+
+ // Only show the severe time based warning once per charge cycle
+ final boolean canShowSevereWarning =
+ timeRemaining < mEnhancedEstimates.getSevereWarningThreshold()
+ && !mSevereWarningShownThisChargeCycle;
+
+ return canShowWarning || canShowSevereWarning;
+ }
private void initTemperatureWarning() {
ContentResolver resolver = mContext.getContentResolver();
@@ -263,7 +359,7 @@ public class PowerUI extends SystemUI {
resources.getInteger(R.integer.config_warningTemperature));
if (mThresholdTemp < 0f) {
- // Get the throttling temperature. No need to check if we're not throttling.
+ // Get the shutdown temperature, adjust for warning tolerance.
float[] throttlingTemps = mHardwarePropertiesManager.getDeviceTemperatures(
HardwarePropertiesManager.DEVICE_TEMPERATURE_SKIN,
HardwarePropertiesManager.TEMPERATURE_SHUTDOWN);
@@ -276,6 +372,25 @@ public class PowerUI extends SystemUI {
resources.getInteger(R.integer.config_warningTemperatureTolerance);
}
+ if (mThermalService == null) {
+ // Enable push notifications of throttling from vendor thermal
+ // management subsystem via thermalservice, in addition to our
+ // usual polling, to react to temperature jumps more quickly.
+ IBinder b = ServiceManager.getService("thermalservice");
+
+ if (b != null) {
+ mThermalService = IThermalService.Stub.asInterface(b);
+ try {
+ mThermalService.registerThermalEventListener(
+ new ThermalEventListener());
+ } catch (RemoteException e) {
+ // Should never happen.
+ }
+ } else {
+ Slog.w(TAG, "cannot find thermalservice, no throttling push notifications");
+ }
+ }
+
setNextLogTime();
// This initialization method may be called on a configuration change. Only one set of
@@ -402,6 +517,8 @@ public class PowerUI extends SystemUI {
public interface WarningsUI {
void update(int batteryLevel, int bucket, long screenOffTime);
+ void updateEstimate(Estimate estimate);
+ void updateThresholds(long lowThreshold, long severeThreshold);
void dismissLowBatteryWarning();
void showLowBatteryWarning(boolean playSound);
void dismissInvalidChargerWarning();
@@ -414,5 +531,15 @@ public class PowerUI extends SystemUI {
void dump(PrintWriter pw);
void userSwitched();
}
-}
+ // Thermal event received from vendor thermal management subsystem
+ private final class ThermalEventListener extends IThermalEventListener.Stub {
+ @Override public void notifyThrottling(boolean isThrottling, Temperature temp) {
+ // Trigger an update of the temperature warning. Only one
+ // callback can be enabled at a time, so remove any existing
+ // callback; updateTemperatureWarning will schedule another one.
+ mHandler.removeCallbacks(mUpdateTempCallback);
+ updateTemperatureWarning();
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java
index f960dc5b4a47..2a2bc0923e1b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java
@@ -51,10 +51,11 @@ public class AutoAddTracker {
public AutoAddTracker(Context context) {
mContext = context;
mAutoAdded = new ArraySet<>(getAdded());
+ // TODO: remove migration code and shared preferences keys after P release
for (String[] convertPref : CONVERT_PREFS) {
if (Prefs.getBoolean(context, convertPref[0], false)) {
setTileAdded(convertPref[1]);
- Prefs.putBoolean(context, convertPref[0], false);
+ Prefs.remove(context, convertPref[0]);
}
}
mContext.getContentResolver().registerContentObserver(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/CellTileView.java b/packages/SystemUI/src/com/android/systemui/qs/CellTileView.java
index 5f2609380085..e7eefe8d5e56 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/CellTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/CellTileView.java
@@ -19,12 +19,12 @@ import android.graphics.drawable.Drawable;
import android.service.quicksettings.Tile;
import android.widget.ImageView;
+import com.android.settingslib.graph.SignalDrawable;
import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.plugins.qs.QSTile.Icon;
import com.android.systemui.plugins.qs.QSTile.State;
import com.android.systemui.qs.tileimpl.QSTileImpl;
-import com.android.systemui.statusbar.phone.SignalDrawable;
import java.util.Objects;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index 8f1880039857..222c6e8274f5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -20,11 +20,12 @@ import android.view.View.OnAttachStateChangeListener;
import android.view.View.OnLayoutChangeListener;
import com.android.systemui.Dependency;
-import com.android.systemui.plugins.qs.*;
+import com.android.systemui.plugins.qs.QS;
+import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTileView;
import com.android.systemui.qs.PagedTileLayout.PageListener;
-import com.android.systemui.qs.QSPanel.QSTileLayout;
import com.android.systemui.qs.QSHost.Callback;
+import com.android.systemui.qs.QSPanel.QSTileLayout;
import com.android.systemui.qs.TouchAnimator.Builder;
import com.android.systemui.qs.TouchAnimator.Listener;
import com.android.systemui.tuner.TunerService;
@@ -44,7 +45,11 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
public static final float EXPANDED_TILE_DELAY = .86f;
private final ArrayList<View> mAllViews = new ArrayList<>();
- private final ArrayList<View> mTopFiveQs = new ArrayList<>();
+ /**
+ * List of {@link View}s representing Quick Settings that are being animated from the quick QS
+ * position to the normal QS panel.
+ */
+ private final ArrayList<View> mQuickQsViews = new ArrayList<>();
private final QuickQSPanel mQuickQsPanel;
private final QSPanel mQsPanel;
private final QS mQs;
@@ -57,6 +62,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
private TouchAnimator mTranslationXAnimator;
private TouchAnimator mTranslationYAnimator;
private TouchAnimator mNonfirstPageAnimator;
+ private TouchAnimator mNonfirstPageDelayedAnimator;
private TouchAnimator mBrightnessAnimator;
private boolean mOnKeyguard;
@@ -79,10 +85,10 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
QSTileLayout tileLayout = mQsPanel.getTileLayout();
if (tileLayout instanceof PagedTileLayout) {
mPagedLayout = ((PagedTileLayout) tileLayout);
- mPagedLayout.setPageListener(this);
} else {
Log.w(TAG, "QS Not using page layout");
}
+ panel.setPageListener(this);
}
public void onRtlChanged() {
@@ -157,7 +163,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
clearAnimationState();
mAllViews.clear();
- mTopFiveQs.clear();
+ mQuickQsViews.clear();
QSTileLayout tileLayout = mQsPanel.getTileLayout();
mAllViews.add((View) tileLayout);
@@ -198,7 +204,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
translationXBuilder.addFloat(tileView, "translationX", -xDiff, 0);
translationYBuilder.addFloat(tileView, "translationY", -yDiff, 0);
- mTopFiveQs.add(tileView.getIcon());
+ mQuickQsViews.add(tileView.getIconWithBackground());
mAllViews.add(tileView.getIcon());
mAllViews.add(quickTileView);
} else if (mFullRows && isIconInAnimatedRow(count)) {
@@ -244,10 +250,8 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
mFirstPageDelayedAnimator = new TouchAnimator.Builder()
.setStartDelay(EXPANDED_TILE_DELAY)
.addFloat(tileLayout, "alpha", 0, 1)
- .addFloat(mQsPanel.getPageIndicator(), "alpha", 0, 1)
.addFloat(mQsPanel.getDivider(), "alpha", 0, 1)
.addFloat(mQsPanel.getFooter().getView(), "alpha", 0, 1).build();
- mAllViews.add(mQsPanel.getPageIndicator());
mAllViews.add(mQsPanel.getDivider());
mAllViews.add(mQsPanel.getFooter().getView());
float px = 0;
@@ -265,11 +269,13 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
}
mNonfirstPageAnimator = new TouchAnimator.Builder()
.addFloat(mQuickQsPanel, "alpha", 1, 0)
- .addFloat(mQsPanel.getPageIndicator(), "alpha", 0, 1)
.addFloat(mQsPanel.getDivider(), "alpha", 0, 1)
.setListener(mNonFirstPageListener)
.setEndDelay(.5f)
.build();
+ mNonfirstPageDelayedAnimator = new TouchAnimator.Builder()
+ .setStartDelay(.14f)
+ .addFloat(tileLayout, "alpha", 0, 1).build();
}
private boolean isIconInAnimatedRow(int count) {
@@ -314,6 +320,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
}
} else {
mNonfirstPageAnimator.setPosition(position);
+ mNonfirstPageDelayedAnimator.setPosition(position);
}
}
@@ -325,9 +332,9 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
@Override
public void onAnimationAtEnd() {
mQuickQsPanel.setVisibility(View.INVISIBLE);
- final int N = mTopFiveQs.size();
+ final int N = mQuickQsViews.size();
for (int i = 0; i < N; i++) {
- mTopFiveQs.get(i).setVisibility(View.VISIBLE);
+ mQuickQsViews.get(i).setVisibility(View.VISIBLE);
}
}
@@ -335,9 +342,9 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
public void onAnimationStarted() {
mQuickQsPanel.setVisibility(mOnKeyguard ? View.INVISIBLE : View.VISIBLE);
if (mOnFirstPage) {
- final int N = mTopFiveQs.size();
+ final int N = mQuickQsViews.size();
for (int i = 0; i < N; i++) {
- mTopFiveQs.get(i).setVisibility(View.INVISIBLE);
+ mQuickQsViews.get(i).setVisibility(View.INVISIBLE);
}
}
}
@@ -351,9 +358,9 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
v.setTranslationX(0);
v.setTranslationY(0);
}
- final int N2 = mTopFiveQs.size();
+ final int N2 = mQuickQsViews.size();
for (int i = 0; i < N2; i++) {
- mTopFiveQs.get(i).setVisibility(View.VISIBLE);
+ mQuickQsViews.get(i).setVisibility(View.VISIBLE);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 33b5268e03e1..6ccb81772db9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -17,13 +17,18 @@
package com.android.systemui.qs;
import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Canvas;
+import android.graphics.Path;
import android.graphics.Point;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;
+import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.qs.customize.QSCustomizer;
+import com.android.systemui.statusbar.ExpandableOutlineView;
/**
* Wrapper view with background which contains {@link QSPanel} and {@link BaseStatusBarHeader}
@@ -39,7 +44,8 @@ public class QSContainerImpl extends FrameLayout {
protected float mQsExpansion;
private QSCustomizer mQSCustomizer;
private View mQSFooter;
- private float mFullElevation;
+ private View mBackground;
+ private int mSideMargins;
public QSContainerImpl(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -53,10 +59,12 @@ public class QSContainerImpl extends FrameLayout {
mHeader = findViewById(R.id.header);
mQSCustomizer = findViewById(R.id.qs_customize);
mQSFooter = findViewById(R.id.qs_footer);
- mFullElevation = mQSPanel.getElevation();
+ mBackground = findViewById(R.id.quick_settings_background);
+ mSideMargins = getResources().getDimensionPixelSize(R.dimen.notification_side_paddings);
setClickable(true);
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
+ setMargins();
}
@Override
@@ -68,11 +76,22 @@ public class QSContainerImpl extends FrameLayout {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ getDisplay().getRealSize(mSizePoint);
+
// Since we control our own bottom, be whatever size we want.
// Otherwise the QSPanel ends up with 0 height when the window is only the
// size of the status bar.
- mQSPanel.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(
- MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.UNSPECIFIED));
+ Configuration config = getResources().getConfiguration();
+ boolean navBelow = config.smallestScreenWidthDp >= 600
+ || config.orientation != Configuration.ORIENTATION_LANDSCAPE;
+ MarginLayoutParams params = (MarginLayoutParams) mQSPanel.getLayoutParams();
+ int maxQs = mSizePoint.y - params.topMargin - params.bottomMargin - getPaddingBottom()
+ - getResources().getDimensionPixelSize(R.dimen.qs_notif_collapsed_space);
+ if (navBelow) {
+ maxQs -= getResources().getDimensionPixelSize(R.dimen.navigation_bar_height);
+ }
+ mQSPanel.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(maxQs, MeasureSpec.AT_MOST));
+
int width = mQSPanel.getMeasuredWidth();
LayoutParams layoutParams = (LayoutParams) mQSPanel.getLayoutParams();
int height = layoutParams.topMargin + layoutParams.bottomMargin
@@ -82,7 +101,6 @@ public class QSContainerImpl extends FrameLayout {
// QSCustomizer will always be the height of the screen, but do this after
// other measuring to avoid changing the height of the QS.
- getDisplay().getRealSize(mSizePoint);
mQSCustomizer.measure(widthMeasureSpec,
MeasureSpec.makeMeasureSpec(mSizePoint.y, MeasureSpec.EXACTLY));
}
@@ -110,6 +128,8 @@ public class QSContainerImpl extends FrameLayout {
mQSDetail.setBottom(getTop() + height);
// Pin QS Footer to the bottom of the panel.
mQSFooter.setTranslationY(height - mQSFooter.getHeight());
+ mBackground.setTop(mQSPanel.getTop());
+ mBackground.setBottom(height);
}
protected int calculateContainerHeight() {
@@ -123,4 +143,19 @@ public class QSContainerImpl extends FrameLayout {
mQsExpansion = expansion;
updateExpansion();
}
+
+ private void setMargins() {
+ setMargins(mQSDetail);
+ setMargins(mBackground);
+ setMargins(mQSFooter);
+ setMargins(mQSPanel);
+ setMargins(mHeader);
+ setMargins(mQSCustomizer);
+ }
+
+ private void setMargins(View view) {
+ FrameLayout.LayoutParams lp = (LayoutParams) view.getLayoutParams();
+ lp.rightMargin = mSideMargins;
+ lp.leftMargin = mSideMargins;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
index 8869e8dd3821..ddd991023273 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
@@ -185,10 +185,10 @@ public class QSDetailItems extends FrameLayout {
}
view.setVisibility(mItemsVisible ? VISIBLE : INVISIBLE);
final ImageView iv = (ImageView) view.findViewById(android.R.id.icon);
- if (item.iconDrawable != null) {
- iv.setImageDrawable(item.iconDrawable.getDrawable(iv.getContext()));
+ if (item.icon != null) {
+ iv.setImageDrawable(item.icon.getDrawable(iv.getContext()));
} else {
- iv.setImageResource(item.icon);
+ iv.setImageResource(item.iconResId);
}
iv.getOverlay().clear();
if (item.overlay != null) {
@@ -258,8 +258,8 @@ public class QSDetailItems extends FrameLayout {
}
public static class Item {
- public int icon;
- public QSTile.Icon iconDrawable;
+ public int iconResId;
+ public QSTile.Icon icon;
public Drawable overlay;
public CharSequence line1;
public CharSequence line2;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
index 3199decb3ec1..fe3ffb926305 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
@@ -16,20 +16,15 @@
package com.android.systemui.qs;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_DATE;
+import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
-import android.app.ActivityManager;
-import android.app.AlarmManager;
-import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
-import android.content.res.Resources;
import android.graphics.PorterDuff.Mode;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.RippleDrawable;
import android.os.UserManager;
-import android.provider.AlarmClock;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.util.AttributeSet;
@@ -37,112 +32,99 @@ import android.view.View;
import android.view.View.OnClickListener;
import android.widget.FrameLayout;
import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
import android.widget.Toast;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
-import com.android.keyguard.KeyguardStatusView;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.settingslib.Utils;
import com.android.settingslib.drawable.UserIconDrawable;
import com.android.systemui.Dependency;
-import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
import com.android.systemui.R.dimen;
-import com.android.systemui.R.id;
+import com.android.systemui.SysUiServiceProvider;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.qs.TouchAnimator.Builder;
-import com.android.systemui.qs.TouchAnimator.Listener;
-import com.android.systemui.qs.TouchAnimator.ListenerAdapter;
-import com.android.systemui.statusbar.phone.ExpandableIndicator;
+import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.MultiUserSwitch;
import com.android.systemui.statusbar.phone.SettingsButton;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.NetworkController.EmergencyListener;
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
-import com.android.systemui.statusbar.policy.NextAlarmController;
-import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback;
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener;
import com.android.systemui.tuner.TunerService;
public class QSFooterImpl extends FrameLayout implements QSFooter,
- NextAlarmChangeCallback, OnClickListener, OnUserInfoChangedListener, EmergencyListener,
- SignalCallback {
- private static final float EXPAND_INDICATOR_THRESHOLD = .93f;
+ OnClickListener, OnUserInfoChangedListener, EmergencyListener,
+ SignalCallback, CommandQueue.Callbacks {
private ActivityStarter mActivityStarter;
- private NextAlarmController mNextAlarmController;
private UserInfoController mUserInfoController;
private SettingsButton mSettingsButton;
protected View mSettingsContainer;
+ private View mCarrierText;
- private TextView mAlarmStatus;
- private View mAlarmStatusCollapsed;
- private View mDate;
-
+ private boolean mQsDisabled;
private QSPanel mQsPanel;
private boolean mExpanded;
- private boolean mAlarmShowing;
-
- protected ExpandableIndicator mExpandIndicator;
private boolean mListening;
- private AlarmManager.AlarmClockInfo mNextAlarm;
private boolean mShowEmergencyCallsOnly;
+ private View mDivider;
protected MultiUserSwitch mMultiUserSwitch;
private ImageView mMultiUserAvatar;
- protected TouchAnimator mSettingsAlpha;
+ protected TouchAnimator mFooterAnimator;
private float mExpansionAmount;
protected View mEdit;
- private TouchAnimator mAnimator;
- private View mDateTimeGroup;
- private boolean mKeyguardShowing;
- private TouchAnimator mAlarmAnimator;
+ private TouchAnimator mSettingsCogAnimator;
+
+ private View mActionsContainer;
+ private View mDragHandle;
+ private final int mDragHandleExpandOffset;
+ private View mBackground;
public QSFooterImpl(Context context, AttributeSet attrs) {
super(context, attrs);
+
+ mDragHandleExpandOffset = getResources().
+ getDimensionPixelSize(R.dimen.qs_footer_drag_handle_offset);
+
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- Resources res = getResources();
-
+ mBackground = findViewById(R.id.qs_footer_background);
+ mDivider = findViewById(R.id.qs_footer_divider);
mEdit = findViewById(android.R.id.edit);
mEdit.setOnClickListener(view ->
Dependency.get(ActivityStarter.class).postQSRunnableDismissingKeyguard(() ->
mQsPanel.showEdit(view)));
- mDateTimeGroup = findViewById(id.date_time_alarm_group);
- mDate = findViewById(R.id.date);
-
- mExpandIndicator = findViewById(R.id.expand_indicator);
mSettingsButton = findViewById(R.id.settings_button);
mSettingsContainer = findViewById(R.id.settings_button_container);
mSettingsButton.setOnClickListener(this);
- mAlarmStatusCollapsed = findViewById(R.id.alarm_status_collapsed);
- mAlarmStatus = findViewById(R.id.alarm_status);
- mDateTimeGroup.setOnClickListener(this);
+ mCarrierText = findViewById(R.id.qs_carrier_text);
mMultiUserSwitch = findViewById(R.id.multi_user_switch);
mMultiUserAvatar = mMultiUserSwitch.findViewById(R.id.multi_user_avatar);
+ mDragHandle = findViewById(R.id.qs_drag_handle_view);
+ mActionsContainer = findViewById(R.id.qs_footer_actions_container);
+
// RenderThread is doing more harm than good when touching the header (to expand quick
// settings), so disable it for this view
((RippleDrawable) mSettingsButton.getBackground()).setForceSoftware(true);
- ((RippleDrawable) mExpandIndicator.getBackground()).setForceSoftware(true);
updateResources();
- mNextAlarmController = Dependency.get(NextAlarmController.class);
mUserInfoController = Dependency.get(UserInfoController.class);
mActivityStarter = Dependency.get(ActivityStarter.class);
addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight,
@@ -156,33 +138,12 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
int remaining = (width - numTiles * size) / (numTiles - 1);
int defSpace = mContext.getResources().getDimensionPixelOffset(R.dimen.default_gear_space);
- mAnimator = new Builder()
+ mSettingsCogAnimator = new Builder()
.addFloat(mSettingsContainer, "translationX",
isLayoutRtl() ? (remaining - defSpace) : -(remaining - defSpace), 0)
.addFloat(mSettingsButton, "rotation", -120, 0)
.build();
- if (mAlarmShowing) {
- int translate = isLayoutRtl() ? mDate.getWidth() : -mDate.getWidth();
- mAlarmAnimator = new Builder().addFloat(mDate, "alpha", 1, 0)
- .addFloat(mDateTimeGroup, "translationX", 0, translate)
- .addFloat(mAlarmStatus, "alpha", 0, 1)
- .setListener(new ListenerAdapter() {
- @Override
- public void onAnimationAtStart() {
- mAlarmStatus.setVisibility(View.GONE);
- }
-
- @Override
- public void onAnimationStarted() {
- mAlarmStatus.setVisibility(View.VISIBLE);
- }
- }).build();
- } else {
- mAlarmAnimator = null;
- mAlarmStatus.setVisibility(View.GONE);
- mDate.setAlpha(1);
- mDateTimeGroup.setTranslationX(0);
- }
+
setExpansion(mExpansionAmount);
}
@@ -199,40 +160,26 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
}
private void updateResources() {
- FontSizeUtils.updateFontSize(mAlarmStatus, R.dimen.qs_date_collapsed_size);
-
- updateSettingsAnimator();
+ updateFooterAnimator();
}
- private void updateSettingsAnimator() {
- mSettingsAlpha = createSettingsAlphaAnimator();
-
- final boolean isRtl = isLayoutRtl();
- if (isRtl && mDate.getWidth() == 0) {
- mDate.addOnLayoutChangeListener(new OnLayoutChangeListener() {
- @Override
- public void onLayoutChange(View v, int left, int top, int right, int bottom,
- int oldLeft, int oldTop, int oldRight, int oldBottom) {
- mDate.setPivotX(getWidth());
- mDate.removeOnLayoutChangeListener(this);
- }
- });
- } else {
- mDate.setPivotX(isRtl ? mDate.getWidth() : 0);
- }
+ private void updateFooterAnimator() {
+ mFooterAnimator = createFooterAnimator();
}
@Nullable
- private TouchAnimator createSettingsAlphaAnimator() {
+ private TouchAnimator createFooterAnimator() {
return new TouchAnimator.Builder()
- .addFloat(mEdit, "alpha", 0, 1)
- .addFloat(mMultiUserSwitch, "alpha", 0, 1)
+ .addFloat(mBackground, "alpha", 0, 0.90f)
+ .addFloat(mDivider, "alpha", 0, 1)
+ .addFloat(mCarrierText, "alpha", 0, 1)
+ .addFloat(mActionsContainer, "alpha", 0, 1)
+ .addFloat(mDragHandle, "translationY", 0, -mDragHandleExpandOffset)
.build();
}
@Override
public void setKeyguardShowing(boolean keyguardShowing) {
- mKeyguardShowing = keyguardShowing;
setExpansion(mExpansionAmount);
}
@@ -244,50 +191,29 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
}
@Override
- public void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm) {
- mNextAlarm = nextAlarm;
- if (nextAlarm != null) {
- String alarmString = KeyguardStatusView.formatNextAlarm(getContext(), nextAlarm);
- mAlarmStatus.setText(alarmString);
- mAlarmStatus.setContentDescription(mContext.getString(
- R.string.accessibility_quick_settings_alarm, alarmString));
- mAlarmStatusCollapsed.setContentDescription(mContext.getString(
- R.string.accessibility_quick_settings_alarm, alarmString));
- }
- if (mAlarmShowing != (nextAlarm != null)) {
- mAlarmShowing = nextAlarm != null;
- updateAnimator(getWidth());
- updateEverything();
- }
- }
-
- @Override
public void setExpansion(float headerExpansionFraction) {
mExpansionAmount = headerExpansionFraction;
- if (mAnimator != null) mAnimator.setPosition(headerExpansionFraction);
- if (mAlarmAnimator != null) mAlarmAnimator.setPosition(
- mKeyguardShowing ? 0 : headerExpansionFraction);
+ if (mSettingsCogAnimator != null) mSettingsCogAnimator.setPosition(headerExpansionFraction);
- if (mSettingsAlpha != null) {
- mSettingsAlpha.setPosition(headerExpansionFraction);
+ if (mFooterAnimator != null) {
+ mFooterAnimator.setPosition(headerExpansionFraction);
}
+ }
- updateAlarmVisibilities();
-
- mExpandIndicator.setExpanded(headerExpansionFraction > EXPAND_INDICATOR_THRESHOLD);
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ SysUiServiceProvider.getComponent(getContext(), CommandQueue.class).addCallbacks(this);
}
@Override
@VisibleForTesting
public void onDetachedFromWindow() {
setListening(false);
+ SysUiServiceProvider.getComponent(getContext(), CommandQueue.class).removeCallbacks(this);
super.onDetachedFromWindow();
}
- private void updateAlarmVisibilities() {
- mAlarmStatusCollapsed.setVisibility(mAlarmShowing ? View.VISIBLE : View.GONE);
- }
-
@Override
public void setListening(boolean listening) {
if (listening == mListening) {
@@ -302,6 +228,14 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
return findViewById(R.id.expand_indicator);
}
+ @Override
+ public void disable(int state1, int state2, boolean animate) {
+ final boolean disabled = (state2 & DISABLE2_QUICK_SETTINGS) != 0;
+ if (disabled == mQsDisabled) return;
+ mQsDisabled = disabled;
+ updateEverything();
+ }
+
public void updateEverything() {
post(() -> {
updateVisibilities();
@@ -310,12 +244,15 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
}
private void updateVisibilities() {
- updateAlarmVisibilities();
+ mSettingsContainer.setVisibility(mQsDisabled ? View.GONE : View.VISIBLE);
mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility(
TunerService.isTunerEnabled(mContext) ? View.VISIBLE : View.INVISIBLE);
+
final boolean isDemo = UserManager.isDeviceInDemoMode(mContext);
- mMultiUserSwitch.setVisibility(mExpanded && mMultiUserSwitch.hasMultipleUsers() && !isDemo
+
+ mMultiUserSwitch.setVisibility(mExpanded
+ && UserManager.get(mContext).isUserSwitcherEnabled()
? View.VISIBLE : View.INVISIBLE);
mEdit.setVisibility(isDemo || !mExpanded ? View.INVISIBLE : View.VISIBLE);
@@ -323,14 +260,12 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
private void updateListeners() {
if (mListening) {
- mNextAlarmController.addCallback(this);
mUserInfoController.addCallback(this);
if (Dependency.get(NetworkController.class).hasVoiceCallingFeature()) {
Dependency.get(NetworkController.class).addEmergencyListener(this);
Dependency.get(NetworkController.class).addCallback(this);
}
} else {
- mNextAlarmController.removeCallback(this);
mUserInfoController.removeCallback(this);
Dependency.get(NetworkController.class).removeEmergencyListener(this);
Dependency.get(NetworkController.class).removeCallback(this);
@@ -347,6 +282,11 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
@Override
public void onClick(View v) {
+ // Don't do anything until view are unhidden
+ if (!mExpanded) {
+ return;
+ }
+
if (v == mSettingsButton) {
if (!Dependency.get(DeviceProvisionedController.class).isCurrentUserSetup()) {
// If user isn't setup just unlock the device and dump them back at SUW.
@@ -374,16 +314,6 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
} else {
startSettingsActivity();
}
- } else if (v == mDateTimeGroup) {
- Dependency.get(MetricsLogger.class).action(ACTION_QS_DATE,
- mNextAlarm != null);
- if (mNextAlarm != null) {
- PendingIntent showIntent = mNextAlarm.getShowIntent();
- mActivityStarter.startPendingIntentDismissingKeyguard(showIntent);
- } else {
- mActivityStarter.postStartActivityDismissingKeyguard(new Intent(
- AlarmClock.ACTION_SHOW_ALARMS), 0);
- }
}
}
@@ -406,7 +336,7 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
@Override
public void onUserInfoChanged(String name, Drawable picture, String userAccount) {
if (picture != null &&
- UserManager.get(mContext).isGuestUser(ActivityManager.getCurrentUser()) &&
+ UserManager.get(mContext).isGuestUser(KeyguardUpdateMonitor.getCurrentUser()) &&
!(picture instanceof UserIconDrawable)) {
picture = picture.getConstantState().newDrawable(mContext.getResources()).mutate();
picture.setColorFilter(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 4a91ee026953..d8e10516fe69 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -25,6 +25,7 @@ import android.support.annotation.VisibleForTesting;
import android.util.Log;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
+import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
@@ -63,11 +64,12 @@ public class QSFragment extends Fragment implements QS {
private QSContainerImpl mContainer;
private int mLayoutDirection;
private QSFooter mFooter;
+ private float mLastQSExpansion = -1;
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
Bundle savedInstanceState) {
- inflater =inflater.cloneInContext(new ContextThemeWrapper(getContext(), R.style.qs_theme));
+ inflater = inflater.cloneInContext(new ContextThemeWrapper(getContext(), R.style.qs_theme));
return inflater.inflate(R.layout.qs_panel, container, false);
}
@@ -206,6 +208,11 @@ public class QSFragment extends Fragment implements QS {
}
@Override
+ public boolean onInterceptTouchEvent(MotionEvent event) {
+ return isCustomizing() || mQSPanel.onInterceptTouchEvent(event);
+ }
+
+ @Override
public void setHeaderClickable(boolean clickable) {
if (DEBUG) Log.d(TAG, "setHeaderClickable " + clickable);
@@ -227,6 +234,7 @@ public class QSFragment extends Fragment implements QS {
public void setKeyguardShowing(boolean keyguardShowing) {
if (DEBUG) Log.d(TAG, "setKeyguardShowing " + keyguardShowing);
mKeyguardShowing = keyguardShowing;
+ mLastQSExpansion = -1;
if (mQSAnimator != null) {
mQSAnimator.setOnKeyguard(keyguardShowing);
@@ -268,22 +276,31 @@ public class QSFragment extends Fragment implements QS {
getView().setTranslationY(mKeyguardShowing ? (translationScaleY * height)
: headerTranslation);
}
+ if (expansion == mLastQSExpansion) {
+ return;
+ }
+ mLastQSExpansion = expansion;
mHeader.setExpansion(mKeyguardShowing ? 1 : expansion);
mFooter.setExpansion(mKeyguardShowing ? 1 : expansion);
int heightDiff = mQSPanel.getBottom() - mHeader.getBottom() + mHeader.getPaddingBottom()
+ mFooter.getHeight();
mQSPanel.setTranslationY(translationScaleY * heightDiff);
- mQSDetail.setFullyExpanded(expansion == 1);
+ boolean fullyExpanded = expansion == 1;
+ mQSDetail.setFullyExpanded(fullyExpanded);
+ if (fullyExpanded) {
+ // Always draw within the bounds of the view when fully expanded.
+ mQSPanel.setClipBounds(null);
+ } else {
+ // Set bounds on the QS panel so it doesn't run over the header when animating.
+ mQsBounds.top = (int) -mQSPanel.getTranslationY();
+ mQsBounds.right = mQSPanel.getWidth();
+ mQsBounds.bottom = mQSPanel.getHeight();
+ mQSPanel.setClipBounds(mQsBounds);
+ }
if (mQSAnimator != null) {
mQSAnimator.setPosition(expansion);
}
-
- // Set bounds on the QS panel so it doesn't run over the header.
- mQsBounds.top = (int) -mQSPanel.getTranslationY();
- mQsBounds.right = mQSPanel.getWidth();
- mQsBounds.bottom = mQSPanel.getHeight();
- mQSPanel.setClipBounds(mQsBounds);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 8f41084b2b2c..f7c388db0840 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -26,10 +26,11 @@ import android.metrics.LogMaker;
import android.os.Handler;
import android.os.Message;
import android.service.quicksettings.Tile;
+import android.support.v4.widget.Space;
import android.util.AttributeSet;
import android.view.LayoutInflater;
+import android.view.MotionEvent;
import android.view.View;
-import android.widget.ImageView;
import android.widget.LinearLayout;
import com.android.internal.logging.MetricsLogger;
@@ -62,11 +63,8 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
protected final ArrayList<TileRecord> mRecords = new ArrayList<TileRecord>();
protected final View mBrightnessView;
private final H mHandler = new H();
- private final View mPageIndicator;
private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
- private int mPanelPaddingBottom;
- private int mBrightnessPaddingTop;
protected boolean mExpanded;
protected boolean mListening;
@@ -77,6 +75,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
protected QSSecurityFooter mFooter;
private boolean mGridContentVisible = true;
+ private QSScrollLayout mScrollLayout;
protected QSTileLayout mTileLayout;
private QSCustomizer mCustomizePanel;
@@ -95,18 +94,15 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
setOrientation(VERTICAL);
- mBrightnessView = LayoutInflater.from(context).inflate(
- R.layout.quick_settings_brightness_dialog, this, false);
- addView(mBrightnessView);
-
- setupTileLayout();
-
- mPageIndicator = LayoutInflater.from(context).inflate(
- R.layout.qs_page_indicator, this, false);
- addView(mPageIndicator);
- if (mTileLayout instanceof PagedTileLayout) {
- ((PagedTileLayout) mTileLayout).setPageIndicator((PageIndicator) mPageIndicator);
- }
+ mBrightnessView = LayoutInflater.from(mContext).inflate(
+ R.layout.quick_settings_brightness_dialog, this, false);
+ mTileLayout = new TileLayout(mContext);
+ mTileLayout.setListening(mListening);
+ Space space = new Space(mContext);
+ space.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
+ mContext.getResources().getDimensionPixelSize(R.dimen.qs_footer_height)));
+ mScrollLayout = new QSScrollLayout(mContext, mBrightnessView, (View) mTileLayout, space);
+ addView(mScrollLayout);
addDivider();
@@ -131,17 +127,6 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
return mDivider;
}
- public View getPageIndicator() {
- return mPageIndicator;
- }
-
- protected void setupTileLayout() {
- mTileLayout = (QSTileLayout) LayoutInflater.from(mContext).inflate(
- R.layout.qs_paged_tile_layout, this, false);
- mTileLayout.setListening(mListening);
- addView((View) mTileLayout);
- }
-
public boolean isShowingCustomize() {
return mCustomizePanel != null && mCustomizePanel.isCustomizing();
}
@@ -241,9 +226,13 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
public void updateResources() {
final Resources res = mContext.getResources();
- mPanelPaddingBottom = res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom);
- mBrightnessPaddingTop = res.getDimensionPixelSize(R.dimen.qs_brightness_padding_top);
- setPadding(0, mBrightnessPaddingTop, 0, mPanelPaddingBottom);
+ mBrightnessView.setPadding(
+ mBrightnessView.getPaddingLeft(),
+ res.getDimensionPixelSize(R.dimen.qs_brightness_padding_top),
+ mBrightnessView.getPaddingRight(),
+ mBrightnessView.getPaddingBottom());
+ setPadding(
+ 0, 0, 0, res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom));
for (TileRecord r : mRecords) {
r.tile.clearState();
}
@@ -282,8 +271,11 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
public void setExpanded(boolean expanded) {
if (mExpanded == expanded) return;
mExpanded = expanded;
- if (!mExpanded && mTileLayout instanceof PagedTileLayout) {
- ((PagedTileLayout) mTileLayout).setCurrentItem(0, false);
+ if (!mExpanded) {
+ if (mTileLayout instanceof PagedTileLayout) {
+ ((PagedTileLayout) mTileLayout).setCurrentItem(0, false);
+ }
+ mScrollLayout.setScrollY(0);
}
mMetricsLogger.visibility(MetricsEvent.QS_PANEL, mExpanded);
if (!mExpanded) {
@@ -293,6 +285,20 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
}
}
+ public void setPageListener(final PagedTileLayout.PageListener pageListener) {
+ if (mTileLayout instanceof PagedTileLayout) {
+ ((PagedTileLayout) mTileLayout).setPageListener(pageListener);
+ } else {
+ mScrollLayout.setOnScrollChangeListener(new OnScrollChangeListener() {
+ @Override
+ public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX,
+ int oldScrollY) {
+ pageListener.onPageChanged(scrollY == 0);
+ }
+ });
+ }
+ }
+
public boolean isExpanded() {
return mExpanded;
}
@@ -317,6 +323,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
}
public void refreshAllTiles() {
+ mBrightnessController.checkRestrictionAndSetEnabled();
for (TileRecord r : mRecords) {
r.tile.refreshState();
}
@@ -564,6 +571,11 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
mFooter.showDeviceMonitoringDialog();
}
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent event) {
+ return mExpanded && mScrollLayout.shouldIntercept(event);
+ }
+
private class H extends Handler {
private static final int SHOW_DETAIL = 1;
private static final int SET_TILE_VISIBILITY = 2;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSScrollLayout.java b/packages/SystemUI/src/com/android/systemui/qs/QSScrollLayout.java
new file mode 100644
index 000000000000..7b1509dcd173
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSScrollLayout.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.qs;
+
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.support.v4.widget.NestedScrollView;
+import android.util.Property;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewParent;
+import android.widget.LinearLayout;
+
+import com.android.systemui.R;
+import com.android.systemui.qs.touch.OverScroll;
+import com.android.systemui.qs.touch.SwipeDetector;
+
+/**
+ * Quick setting scroll view containing the brightness slider and the QS tiles.
+ *
+ * <p>Call {@link #shouldIntercept(MotionEvent)} from parent views'
+ * {@link #onInterceptTouchEvent(MotionEvent)} method to determine whether this view should
+ * consume the touch event.
+ */
+public class QSScrollLayout extends NestedScrollView {
+ private final int mTouchSlop;
+ private final int mFooterHeight;
+ private int mLastMotionY;
+ private final SwipeDetector mSwipeDetector;
+ private final OverScrollHelper mOverScrollHelper;
+ private float mContentTranslationY;
+
+ public QSScrollLayout(Context context, View... children) {
+ super(context);
+ mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+ mFooterHeight = getResources().getDimensionPixelSize(R.dimen.qs_footer_height);
+ LinearLayout linearLayout = new LinearLayout(mContext);
+ linearLayout.setLayoutParams(new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT));
+ linearLayout.setOrientation(LinearLayout.VERTICAL);
+ for (View view : children) {
+ linearLayout.addView(view);
+ }
+ addView(linearLayout);
+ setOverScrollMode(OVER_SCROLL_NEVER);
+ mOverScrollHelper = new OverScrollHelper();
+ mSwipeDetector = new SwipeDetector(context, mOverScrollHelper, SwipeDetector.VERTICAL);
+ mSwipeDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_BOTH, true);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ if (!canScrollVertically(1) && !canScrollVertically(-1)) {
+ return false;
+ }
+ mSwipeDetector.onTouchEvent(ev);
+ return super.onInterceptTouchEvent(ev) || mOverScrollHelper.isInOverScroll();
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ if (!canScrollVertically(1) && !canScrollVertically(-1)) {
+ return false;
+ }
+ mSwipeDetector.onTouchEvent(ev);
+ return super.onTouchEvent(ev);
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ canvas.translate(0, mContentTranslationY);
+ super.dispatchDraw(canvas);
+ canvas.translate(0, -mContentTranslationY);
+ }
+
+ public boolean shouldIntercept(MotionEvent ev) {
+ if (ev.getY() > (getBottom() - mFooterHeight)) {
+ // Do not intercept touches that are below the divider between QS and the footer.
+ return false;
+ }
+ if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ mLastMotionY = (int) ev.getY();
+ } else if (ev.getActionMasked() == MotionEvent.ACTION_MOVE) {
+ // Do not allow NotificationPanelView to intercept touch events when this
+ // view can be scrolled down.
+ if (mLastMotionY >= 0 && Math.abs(ev.getY() - mLastMotionY) > mTouchSlop
+ && canScrollVertically(1)) {
+ requestParentDisallowInterceptTouchEvent(true);
+ mLastMotionY = (int) ev.getY();
+ return true;
+ }
+ } else if (ev.getActionMasked() == MotionEvent.ACTION_CANCEL
+ || ev.getActionMasked() == MotionEvent.ACTION_UP) {
+ mLastMotionY = -1;
+ requestParentDisallowInterceptTouchEvent(false);
+ }
+ return false;
+ }
+
+ private void requestParentDisallowInterceptTouchEvent(boolean disallowIntercept) {
+ final ViewParent parent = getParent();
+ if (parent != null) {
+ parent.requestDisallowInterceptTouchEvent(disallowIntercept);
+ }
+ }
+
+ private void setContentTranslationY(float contentTranslationY) {
+ mContentTranslationY = contentTranslationY;
+ invalidate();
+ }
+
+ private static final Property<QSScrollLayout, Float> CONTENT_TRANS_Y =
+ new Property<QSScrollLayout, Float>(Float.class, "qsScrollLayoutContentTransY") {
+ @Override
+ public Float get(QSScrollLayout qsScrollLayout) {
+ return qsScrollLayout.mContentTranslationY;
+ }
+
+ @Override
+ public void set(QSScrollLayout qsScrollLayout, Float y) {
+ qsScrollLayout.setContentTranslationY(y);
+ }
+ };
+
+ private class OverScrollHelper implements SwipeDetector.Listener {
+ private boolean mIsInOverScroll;
+
+ // We use this value to calculate the actual amount the user has overscrolled.
+ private float mFirstDisplacement = 0;
+
+ @Override
+ public void onDragStart(boolean start) {}
+
+ @Override
+ public boolean onDrag(float displacement, float velocity) {
+ // Only overscroll if the user is scrolling down when they're already at the bottom
+ // or scrolling up when they're already at the top.
+ boolean wasInOverScroll = mIsInOverScroll;
+ mIsInOverScroll = (!canScrollVertically(1) && displacement < 0) ||
+ (!canScrollVertically(-1) && displacement > 0);
+
+ if (wasInOverScroll && !mIsInOverScroll) {
+ // Exit overscroll. This can happen when the user is in overscroll and then
+ // scrolls the opposite way. Note that this causes the reset translation animation
+ // to run while the user is dragging, which feels a bit unnatural.
+ reset();
+ } else if (mIsInOverScroll) {
+ if (Float.compare(mFirstDisplacement, 0) == 0) {
+ // Because users can scroll before entering overscroll, we need to
+ // subtract the amount where the user was not in overscroll.
+ mFirstDisplacement = displacement;
+ }
+ float overscrollY = displacement - mFirstDisplacement;
+ setContentTranslationY(getDampedOverScroll(overscrollY));
+ }
+
+ return mIsInOverScroll;
+ }
+
+ @Override
+ public void onDragEnd(float velocity, boolean fling) {
+ reset();
+ }
+
+ private void reset() {
+ if (Float.compare(mContentTranslationY, 0) != 0) {
+ ObjectAnimator.ofFloat(QSScrollLayout.this, CONTENT_TRANS_Y, 0)
+ .setDuration(100)
+ .start();
+ }
+ mIsInOverScroll = false;
+ mFirstDisplacement = 0;
+ }
+
+ public boolean isInOverScroll() {
+ return mIsInOverScroll;
+ }
+
+ private float getDampedOverScroll(float y) {
+ return OverScroll.dampedScroll(y, getHeight());
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index 00b883a541db..83148558ea1d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -20,12 +20,13 @@ import android.content.Context;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.Space;
import com.android.systemui.Dependency;
import com.android.systemui.R;
-import com.android.systemui.plugins.qs.*;
+import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTile.SignalState;
import com.android.systemui.plugins.qs.QSTile.State;
import com.android.systemui.plugins.qs.QSTileView;
@@ -43,19 +44,21 @@ public class QuickQSPanel extends QSPanel {
public static final String NUM_QUICK_TILES = "sysui_qqs_count";
+ private boolean mDisabledByPolicy;
private int mMaxTiles;
protected QSPanel mFullPanel;
public QuickQSPanel(Context context, AttributeSet attrs) {
super(context, attrs);
if (mFooter != null) {
- removeView((View) mFooter.getView());
+ removeView(mFooter.getView());
}
if (mTileLayout != null) {
for (int i = 0; i < mRecords.size(); i++) {
mTileLayout.removeTile(mRecords.get(i));
}
- removeView((View) mTileLayout);
+ View tileLayoutView = (View) mTileLayout;
+ ((ViewGroup) tileLayoutView.getParent()).removeView(tileLayoutView);
}
mTileLayout = new HeaderTileLayout(context);
mTileLayout.setListening(mListening);
@@ -151,6 +154,30 @@ public class QuickQSPanel extends QSPanel {
return Dependency.get(TunerService.class).getValue(NUM_QUICK_TILES, 6);
}
+ void setDisabledByPolicy(boolean disabled) {
+ if (disabled != mDisabledByPolicy) {
+ mDisabledByPolicy = disabled;
+ setVisibility(disabled ? View.GONE : View.VISIBLE);
+ }
+ }
+
+ /**
+ * Sets the visibility of this {@link QuickQSPanel}. This method has no effect when this panel
+ * is disabled by policy through {@link #setDisabledByPolicy(boolean)}, and in this case the
+ * visibility will always be {@link View#GONE}. This method is called externally by
+ * {@link QSAnimator} only.
+ */
+ @Override
+ public void setVisibility(int visibility) {
+ if (mDisabledByPolicy) {
+ if (getVisibility() == View.GONE) {
+ return;
+ }
+ visibility = View.GONE;
+ }
+ super.setVisibility(visibility);
+ }
+
private static class HeaderTileLayout extends LinearLayout implements QSTileLayout {
protected final ArrayList<TileRecord> mRecords = new ArrayList<>();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 0709e229bd4d..4d7333b99eee 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -14,11 +14,16 @@
package com.android.systemui.qs;
+import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
+import static android.app.StatusBarManager.DISABLE_NONE;
+
import android.content.Context;
+import android.content.Intent;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Rect;
+import android.provider.AlarmClock;
import android.support.annotation.VisibleForTesting;
import android.util.AttributeSet;
import android.view.View;
@@ -30,13 +35,17 @@ import com.android.systemui.BatteryMeterView;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.R.id;
+import com.android.systemui.SysUiServiceProvider;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.qs.QSDetail.Callback;
-import com.android.systemui.statusbar.SignalClusterView;
+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.policy.DarkIconDispatcher;
import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver;
-
-public class QuickStatusBarHeader extends RelativeLayout {
+public class QuickStatusBarHeader extends RelativeLayout
+ implements CommandQueue.Callbacks, View.OnClickListener {
private ActivityStarter mActivityStarter;
@@ -44,9 +53,16 @@ public class QuickStatusBarHeader extends RelativeLayout {
private boolean mExpanded;
private boolean mListening;
+ private boolean mQsDisabled;
protected QuickQSPanel mHeaderQsPanel;
protected QSTileHost mHost;
+ private TintedIconManager mIconManager;
+ private TouchAnimator mAlphaAnimator;
+
+ private View mQuickQsStatusIcons;
+
+ private View mDate;
public QuickStatusBarHeader(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -58,19 +74,27 @@ public class QuickStatusBarHeader extends RelativeLayout {
Resources res = getResources();
mHeaderQsPanel = findViewById(R.id.quick_qs_panel);
+ mDate = findViewById(R.id.date);
+ mDate.setOnClickListener(this);
+ mQuickQsStatusIcons = findViewById(R.id.quick_qs_status_icons);
+ mIconManager = new TintedIconManager(findViewById(R.id.statusIcons));
// RenderThread is doing more harm than good when touching the header (to expand quick
// settings), so disable it for this view
updateResources();
- // Set the light/dark theming on the header status UI to match the current theme.
+ Rect tintArea = new Rect(0, 0, 0, 0);
int colorForeground = Utils.getColorAttr(getContext(), android.R.attr.colorForeground);
float intensity = colorForeground == Color.WHITE ? 0 : 1;
- Rect tintArea = new Rect(0, 0, 0, 0);
+ int fillColor = fillColorForIntensity(intensity, getContext());
+
+ // 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);
+ applyDarkness(id.signal_cluster, tintArea, intensity, colorForeground);
- applyDarkness(R.id.battery, tintArea, intensity, colorForeground);
- applyDarkness(R.id.clock, tintArea, intensity, colorForeground);
+ // Set the correct tint for the status icons so they contrast
+ mIconManager.setTint(fillColor);
BatteryMeterView battery = findViewById(R.id.battery);
battery.setForceShowPercent(true);
@@ -85,6 +109,13 @@ public class QuickStatusBarHeader extends RelativeLayout {
}
}
+ private int fillColorForIntensity(float intensity, Context context) {
+ if (intensity == 0) {
+ return context.getColor(R.color.light_mode_icon_color_dual_tone_fill);
+ }
+ return context.getColor(R.color.dark_mode_icon_color_dual_tone_fill);
+ }
+
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
@@ -98,6 +129,13 @@ public class QuickStatusBarHeader extends RelativeLayout {
}
private void updateResources() {
+ updateAlphaAnimator();
+ }
+
+ private void updateAlphaAnimator() {
+ mAlphaAnimator = new TouchAnimator.Builder()
+ .addFloat(mQuickQsStatusIcons, "alpha", 1, 0)
+ .build();
}
public int getCollapsedHeight() {
@@ -116,12 +154,34 @@ public class QuickStatusBarHeader extends RelativeLayout {
}
public void setExpansion(float headerExpansionFraction) {
+ if (mAlphaAnimator != null ) {
+ mAlphaAnimator.setPosition(headerExpansionFraction);
+ }
+ }
+
+ @Override
+ public void disable(int state1, int state2, boolean animate) {
+ final boolean disabled = (state2 & DISABLE2_QUICK_SETTINGS) != 0;
+ if (disabled == mQsDisabled) return;
+ mQsDisabled = disabled;
+ mHeaderQsPanel.setDisabledByPolicy(disabled);
+ final int rawHeight = (int) getResources().getDimension(
+ com.android.internal.R.dimen.quick_qs_total_height);
+ getLayoutParams().height = disabled ? (rawHeight - mHeaderQsPanel.getHeight()) : rawHeight;
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ SysUiServiceProvider.getComponent(getContext(), CommandQueue.class).addCallbacks(this);
+ Dependency.get(StatusBarIconController.class).addIconGroup(mIconManager);
}
@Override
@VisibleForTesting
public void onDetachedFromWindow() {
setListening(false);
+ SysUiServiceProvider.getComponent(getContext(), CommandQueue.class).removeCallbacks(this);
+ Dependency.get(StatusBarIconController.class).removeIconGroup(mIconManager);
super.onDetachedFromWindow();
}
@@ -133,6 +193,14 @@ public class QuickStatusBarHeader extends RelativeLayout {
mListening = listening;
}
+ @Override
+ public void onClick(View v) {
+ if(v == mDate){
+ Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(new Intent(
+ AlarmClock.ACTION_SHOW_ALARMS),0);
+ }
+ }
+
public void updateEverything() {
post(() -> setClickable(false));
}
@@ -147,6 +215,11 @@ public class QuickStatusBarHeader extends RelativeLayout {
//host.setHeaderView(mExpandIndicator);
mHeaderQsPanel.setQSPanelAndHeader(mQsPanel, this);
mHeaderQsPanel.setHost(host, null /* No customization in header */);
+
+ // Use SystemUI context to get battery meter colors, and let it use the default tint (white)
+ BatteryMeterView battery = findViewById(R.id.battery);
+ battery.setColorsFromContext(mHost.getContext());
+ battery.onDarkChanged(new Rect(), 0, DarkIconDispatcher.DEFAULT_ICON_TINT);
}
public void setCallback(Callback qsPanelCallback) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/SignalTileView.java b/packages/SystemUI/src/com/android/systemui/qs/SignalTileView.java
index 9ee40ccf8893..d9583af65df6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/SignalTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/SignalTileView.java
@@ -41,6 +41,7 @@ public class SignalTileView extends QSIconViewImpl {
private ImageView mOut;
private int mWideOverlayIconStartPadding;
+ private int mSignalIndicatorToIconFrameSpacing;
public SignalTileView(Context context) {
super(context);
@@ -48,8 +49,13 @@ public class SignalTileView extends QSIconViewImpl {
mIn = addTrafficView(R.drawable.ic_qs_signal_in);
mOut = addTrafficView(R.drawable.ic_qs_signal_out);
+ setClipChildren(false);
+ setClipToPadding(false);
+
mWideOverlayIconStartPadding = context.getResources().getDimensionPixelSize(
R.dimen.wide_type_icon_start_padding_qs);
+ mSignalIndicatorToIconFrameSpacing = context.getResources().getDimensionPixelSize(
+ R.dimen.signal_indicator_to_icon_frame_spacing);
}
private ImageView addTrafficView(int icon) {
@@ -99,10 +105,10 @@ public class SignalTileView extends QSIconViewImpl {
boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
int left, right;
if (isRtl) {
- right = mIconFrame.getLeft();
+ right = getLeft() - mSignalIndicatorToIconFrameSpacing;
left = right - indicator.getMeasuredWidth();
} else {
- left = mIconFrame.getRight();
+ left = getRight() + mSignalIndicatorToIconFrameSpacing;
right = left + indicator.getMeasuredWidth();
}
indicator.layout(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
index 3b9e7bcfb9b4..65135ab142d7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
@@ -104,6 +104,11 @@ public class TileLayout extends ViewGroup implements QSTileLayout {
setMeasuredDimension(width, height);
}
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+
private static int exactly(int size) {
return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/car/CarQSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/car/CarQSFooter.java
index 142aab2626a6..23d3ebbbfe80 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/car/CarQSFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/car/CarQSFooter.java
@@ -47,7 +47,7 @@ public class CarQSFooter extends RelativeLayout implements QSFooter,
private MultiUserSwitch mMultiUserSwitch;
private TextView mUserName;
private ImageView mMultiUserAvatar;
- private UserGridView mUserGridView;
+ private CarQSFragment.UserSwitchCallback mUserSwitchCallback;
public CarQSFooter(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -63,15 +63,15 @@ public class CarQSFooter extends RelativeLayout implements QSFooter,
mUserInfoController = Dependency.get(UserInfoController.class);
mMultiUserSwitch.setOnClickListener(v -> {
- if (mUserGridView == null) {
+ if (mUserSwitchCallback == null) {
Log.e(TAG, "CarQSFooter not properly set up; cannot display user switcher.");
return;
}
- if (!mUserGridView.isShowing()) {
- mUserGridView.show();
+ if (!mUserSwitchCallback.isShowing()) {
+ mUserSwitchCallback.show();
} else {
- mUserGridView.hide();
+ mUserSwitchCallback.hide();
}
});
@@ -102,8 +102,8 @@ public class CarQSFooter extends RelativeLayout implements QSFooter,
}
}
- public void setUserGridView(UserGridView view) {
- mUserGridView = view;
+ public void setUserSwitchCallback(CarQSFragment.UserSwitchCallback callback) {
+ mUserSwitchCallback = callback;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/car/CarQSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/car/CarQSFragment.java
index 13298d378845..0ee6d1fb6664 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/car/CarQSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/car/CarQSFragment.java
@@ -13,6 +13,12 @@
*/
package com.android.systemui.qs.car;
+import android.animation.Animator;
+import android.animation.AnimatorInflater;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
import android.app.Fragment;
import android.os.Bundle;
import android.support.annotation.Nullable;
@@ -26,18 +32,29 @@ import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.qs.QSFooter;
+import com.android.systemui.statusbar.car.PageIndicator;
import com.android.systemui.statusbar.car.UserGridView;
import com.android.systemui.statusbar.policy.UserSwitcherController;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* A quick settings fragment for the car. For auto, there is no row for quick settings or ability
* to expand the quick settings panel. Instead, the only thing is that displayed is the
* status bar, and a static row with access to the user switcher and settings.
*/
public class CarQSFragment extends Fragment implements QS {
+ private ViewGroup mPanel;
private View mHeader;
+ private View mUserSwitcherContainer;
private CarQSFooter mFooter;
+ private View mFooterUserName;
+ private View mFooterExpandIcon;
private UserGridView mUserGridView;
+ private PageIndicator mPageIndicator;
+ private AnimatorSet mAnimatorSet;
+ private UserSwitchCallback mUserSwitchCallback;
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@@ -48,14 +65,26 @@ public class CarQSFragment extends Fragment implements QS {
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
+ mPanel = (ViewGroup) view;
mHeader = view.findViewById(R.id.header);
mFooter = view.findViewById(R.id.qs_footer);
+ mFooterUserName = mFooter.findViewById(R.id.user_name);
+ mFooterExpandIcon = mFooter.findViewById(R.id.user_switch_expand_icon);
+
+ mUserSwitcherContainer = view.findViewById(R.id.user_switcher_container);
+
+ updateUserSwitcherHeight(0);
mUserGridView = view.findViewById(R.id.user_grid);
mUserGridView.init(null, Dependency.get(UserSwitcherController.class),
- false /* showInitially */);
+ false /* overrideAlpha */);
- mFooter.setUserGridView(mUserGridView);
+ mPageIndicator = view.findViewById(R.id.user_switcher_page_indicator);
+ mPageIndicator.setupWithViewPager(mUserGridView);
+
+ mUserSwitchCallback = new UserSwitchCallback();
+ mFooter.setUserSwitchCallback(mUserSwitchCallback);
+ mUserGridView.setUserSwitchCallback(mUserSwitchCallback);
}
@Override
@@ -82,11 +111,13 @@ public class CarQSFragment extends Fragment implements QS {
@Override
public void setHeaderListening(boolean listening) {
mFooter.setListening(listening);
+ mUserGridView.setListening(listening);
}
@Override
public void setListening(boolean listening) {
mFooter.setListening(listening);
+ mUserGridView.setListening(listening);
}
@Override
@@ -171,4 +202,126 @@ public class CarQSFragment extends Fragment implements QS {
public void setExpandClickListener(OnClickListener onClickListener) {
// No ability to expand the quick settings.
}
+
+ public class UserSwitchCallback {
+ private boolean mShowing;
+
+ public boolean isShowing() {
+ return mShowing;
+ }
+
+ public void show() {
+ mShowing = true;
+ animateHeightChange(true /* opening */);
+ }
+
+ public void hide() {
+ mShowing = false;
+ animateHeightChange(false /* opening */);
+ }
+
+ public void resetShowing() {
+ if (mShowing) {
+ for (int i = 0; i < mUserGridView.getChildCount(); i++) {
+ ViewGroup podContainer = (ViewGroup) mUserGridView.getChildAt(i);
+ // Need to bring the last child to the front to maintain the order in the pod
+ // container. Why? ¯\_(ツ)_/¯
+ if (podContainer.getChildCount() > 0) {
+ podContainer.getChildAt(podContainer.getChildCount() - 1).bringToFront();
+ }
+ // The alpha values are default to 0, so if the pods have been refreshed, they
+ // need to be set to 1 when showing.
+ for (int j = 0; j < podContainer.getChildCount(); j++) {
+ podContainer.getChildAt(j).setAlpha(1f);
+ }
+ }
+ }
+ }
+ }
+
+ private void updateUserSwitcherHeight(int height) {
+ ViewGroup.LayoutParams layoutParams = mUserSwitcherContainer.getLayoutParams();
+ layoutParams.height = height;
+ mUserSwitcherContainer.requestLayout();
+ }
+
+ private void animateHeightChange(boolean opening) {
+ // Animation in progress; cancel it to avoid contention.
+ if (mAnimatorSet != null){
+ mAnimatorSet.cancel();
+ }
+
+ List<Animator> allAnimators = new ArrayList<>();
+ ValueAnimator heightAnimator = (ValueAnimator) AnimatorInflater.loadAnimator(getContext(),
+ opening ? R.anim.car_user_switcher_open_animation
+ : R.anim.car_user_switcher_close_animation);
+ heightAnimator.addUpdateListener(valueAnimator -> {
+ updateUserSwitcherHeight((Integer) valueAnimator.getAnimatedValue());
+ });
+ allAnimators.add(heightAnimator);
+
+ // The user grid contains pod containers that each contain a number of pods. Animate
+ // all pods to avoid any discrepancy/race conditions with possible changes during the
+ // animation.
+ int cascadeDelay = getResources().getInteger(
+ R.integer.car_user_switcher_anim_cascade_delay_ms);
+ for (int i = 0; i < mUserGridView.getChildCount(); i++) {
+ ViewGroup podContainer = (ViewGroup) mUserGridView.getChildAt(i);
+ for (int j = 0; j < podContainer.getChildCount(); j++) {
+ View pod = podContainer.getChildAt(j);
+ Animator podAnimator = AnimatorInflater.loadAnimator(getContext(),
+ opening ? R.anim.car_user_switcher_open_pod_animation
+ : R.anim.car_user_switcher_close_pod_animation);
+ // Add the cascading delay between pods
+ if (opening) {
+ podAnimator.setStartDelay(podAnimator.getStartDelay() + j * cascadeDelay);
+ }
+ podAnimator.setTarget(pod);
+ allAnimators.add(podAnimator);
+ }
+ }
+
+ Animator nameAnimator = AnimatorInflater.loadAnimator(getContext(),
+ opening ? R.anim.car_user_switcher_open_name_animation
+ : R.anim.car_user_switcher_close_name_animation);
+ nameAnimator.setTarget(mFooterUserName);
+ allAnimators.add(nameAnimator);
+
+ Animator iconAnimator = AnimatorInflater.loadAnimator(getContext(),
+ opening ? R.anim.car_user_switcher_open_icon_animation
+ : R.anim.car_user_switcher_close_icon_animation);
+ iconAnimator.setTarget(mFooterExpandIcon);
+ allAnimators.add(iconAnimator);
+
+ Animator pageAnimator = AnimatorInflater.loadAnimator(getContext(),
+ opening ? R.anim.car_user_switcher_open_pages_animation
+ : R.anim.car_user_switcher_close_pages_animation);
+ pageAnimator.setTarget(mPageIndicator);
+ allAnimators.add(pageAnimator);
+
+ mAnimatorSet = new AnimatorSet();
+ mAnimatorSet.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mAnimatorSet = null;
+ }
+ });
+ mAnimatorSet.playTogether(allAnimators.toArray(new Animator[0]));
+
+ // Setup all values to the start values in the animations, since there are delays, but need
+ // to have all values start at the beginning.
+ setupInitialValues(mAnimatorSet);
+
+ mAnimatorSet.start();
+ }
+
+ private void setupInitialValues(Animator anim) {
+ if (anim instanceof AnimatorSet) {
+ for (Animator a : ((AnimatorSet) anim).getChildAnimations()) {
+ setupInitialValues(a);
+ }
+ } else if (anim instanceof ObjectAnimator) {
+ ((ObjectAnimator) anim).setCurrentFraction(0.0f);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index 5a3081cd6664..3847040271e2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -171,8 +171,6 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene
queryTiles();
mNotifQsContainer.setCustomizerAnimating(true);
mNotifQsContainer.setCustomizerShowing(true);
- announceForAccessibility(mContext.getString(
- R.string.accessibility_desc_quick_settings_edit));
Dependency.get(KeyguardMonitor.class).addCallback(mKeyguardCallback);
updateNavColors();
}
@@ -213,8 +211,6 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene
mClipper.animateCircularClip(mX, mY, false, mCollapseAnimationListener);
mNotifQsContainer.setCustomizerAnimating(true);
mNotifQsContainer.setCustomizerShowing(false);
- announceForAccessibility(mContext.getString(
- R.string.accessibility_desc_quick_settings));
Dependency.get(KeyguardMonitor.class).removeCallback(mKeyguardCallback);
updateNavColors();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index 77c3bfab8de9..bf9746eafaf0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -23,6 +23,7 @@ import com.android.systemui.plugins.qs.*;
import com.android.systemui.plugins.qs.QSTileView;
import com.android.systemui.qs.external.CustomTile;
import com.android.systemui.qs.tiles.AirplaneModeTile;
+import com.android.systemui.qs.tiles.AlarmTile;
import com.android.systemui.qs.tiles.BatterySaverTile;
import com.android.systemui.qs.tiles.BluetoothTile;
import com.android.systemui.qs.tiles.CastTile;
@@ -69,6 +70,7 @@ public class QSFactoryImpl implements QSFactory {
else if (tileSpec.equals("saver")) return new DataSaverTile(mHost);
else if (tileSpec.equals("night")) return new NightDisplayTile(mHost);
else if (tileSpec.equals("nfc")) return new NfcTile(mHost);
+ else if (tileSpec.equals("alarm")) return new AlarmTile(mHost);
// Intent tiles.
else if (tileSpec.startsWith(IntentTile.PREFIX)) return IntentTile.create(mHost, tileSpec);
else if (tileSpec.startsWith(CustomTile.PREFIX)) return CustomTile.create(mHost, tileSpec);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index c249e3778c0a..0f83078e0738 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -103,7 +103,7 @@ public class QSIconViewImpl extends QSIconView {
if (iv instanceof SlashImageView) {
((SlashImageView) iv).setAnimationEnabled(shouldAnimate);
- ((SlashImageView) iv).setState(state.slash, d);
+ ((SlashImageView) iv).setState(null, d);
} else {
iv.setImageDrawable(d);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
index 4d0e60d505f6..c9c678c2ad25 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
@@ -13,7 +13,12 @@
*/
package com.android.systemui.qs.tileimpl;
+import static com.android.systemui.qs.tileimpl.QSIconViewImpl.QS_ANIM_LENGTH;
+
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
import android.content.Context;
+import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.RippleDrawable;
@@ -22,16 +27,21 @@ import android.os.Looper;
import android.os.Message;
import android.service.quicksettings.Tile;
import android.text.TextUtils;
+import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.ImageView.ScaleType;
import android.widget.Switch;
+import com.android.settingslib.Utils;
import com.android.systemui.R;
-import com.android.systemui.plugins.qs.*;
+import com.android.systemui.plugins.qs.QSIconView;
+import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView {
@@ -47,6 +57,12 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView {
private boolean mCollapsedView;
private boolean mClicked;
+ private final ImageView mBg;
+ private final int mColorActive;
+ private final int mColorInactive;
+ private final int mColorDisabled;
+ private int mCircleColor;
+
public QSTileBaseView(Context context, QSIconView icon) {
this(context, icon, false);
}
@@ -60,11 +76,17 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView {
mIconFrame.setForegroundGravity(Gravity.CENTER);
int size = context.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_size);
addView(mIconFrame, new LayoutParams(size, size));
+ mBg = new ImageView(getContext());
+ mBg.setScaleType(ScaleType.FIT_CENTER);
+ mBg.setImageResource(R.drawable.ic_qs_circle);
+ mIconFrame.addView(mBg);
mIcon = icon;
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.setMargins(0, padding, 0, padding);
mIconFrame.addView(mIcon, params);
+ mIconFrame.setClipChildren(false);
+ mIconFrame.setClipToPadding(false);
mTileBackground = newTileBackground();
if (mTileBackground instanceof RippleDrawable) {
@@ -73,6 +95,11 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView {
setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
setBackground(mTileBackground);
+ mColorActive = Utils.getColorAttr(context, android.R.attr.colorAccent);
+ mColorDisabled = Utils.getDisabled(context,
+ Utils.getColorAttr(context, android.R.attr.textColorTertiary));
+ mColorInactive = Utils.getColorAttr(context, android.R.attr.textColorSecondary);
+
setPadding(0, 0, 0, 0);
setClipChildren(false);
setClipToPadding(false);
@@ -80,6 +107,10 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView {
setFocusable(true);
}
+ public View getBgCicle() {
+ return mBg;
+ }
+
protected Drawable newTileBackground() {
final int[] attrs = new int[]{android.R.attr.selectableItemBackgroundBorderless};
final TypedArray ta = getContext().obtainStyledAttributes(attrs);
@@ -150,6 +181,20 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView {
}
protected void handleStateChanged(QSTile.State state) {
+ int circleColor = getCircleColor(state.state);
+ if (circleColor != mCircleColor) {
+ if (mBg.isShown()) {
+ ValueAnimator animator = ValueAnimator.ofArgb(mCircleColor, circleColor)
+ .setDuration(QS_ANIM_LENGTH);
+ animator.addUpdateListener(animation -> mBg.setImageTintList(ColorStateList.valueOf(
+ (Integer) animation.getAnimatedValue())));
+ animator.start();
+ } else {
+ QSIconViewImpl.setTint(mBg, circleColor);
+ }
+ mCircleColor = circleColor;
+ }
+
setClickable(state.state != Tile.STATE_UNAVAILABLE);
mIcon.setIcon(state);
setContentDescription(state.contentDescription);
@@ -163,6 +208,19 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView {
}
}
+ private int getCircleColor(int state) {
+ switch (state) {
+ case Tile.STATE_ACTIVE:
+ return mColorActive;
+ case Tile.STATE_INACTIVE:
+ case Tile.STATE_UNAVAILABLE:
+ return mColorDisabled;
+ default:
+ Log.e(TAG, "Invalid state " + state);
+ return 0;
+ }
+ }
+
@Override
public void setClickable(boolean clickable) {
super.setClickable(clickable);
@@ -178,6 +236,10 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView {
return mIcon;
}
+ public View getIconWithBackground() {
+ return mIconFrame;
+ }
+
@Override
public boolean performClick() {
mClicked = true;
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 576a447447b5..7259282935a0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -373,11 +373,11 @@ public abstract class QSTileImpl<TState extends State> implements QSTile {
switch (state) {
case Tile.STATE_UNAVAILABLE:
return Utils.getDisabled(context,
- Utils.getColorAttr(context, android.R.attr.colorForeground));
+ Utils.getColorAttr(context, android.R.attr.textColorSecondary));
case Tile.STATE_INACTIVE:
- return Utils.getColorAttr(context, android.R.attr.textColorHint);
+ return Utils.getColorAttr(context, android.R.attr.textColorSecondary);
case Tile.STATE_ACTIVE:
- return Utils.getColorAttr(context, android.R.attr.textColorPrimary);
+ return Utils.getColorAttr(context, android.R.attr.colorPrimary);
default:
Log.e("QSTile", "Invalid state " + state);
return 0;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java
index 263dac0f44ec..9eb9906ba3b5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java
@@ -18,6 +18,7 @@ import android.content.Context;
import android.content.res.Configuration;
import android.service.quicksettings.Tile;
import android.text.SpannableStringBuilder;
+import android.text.TextUtils;
import android.text.style.ForegroundColorSpan;
import android.view.Gravity;
import android.view.LayoutInflater;
@@ -36,8 +37,10 @@ import libcore.util.Objects;
/** View that represents a standard quick settings tile. **/
public class QSTileView extends QSTileBaseView {
+ private static final boolean DUAL_TARGET_ALLOWED = false;
private View mDivider;
protected TextView mLabel;
+ private TextView mSecondLine;
private ImageView mPadLock;
private int mState;
private ViewGroup mLabelContainer;
@@ -86,6 +89,8 @@ public class QSTileView extends QSTileBaseView {
mDivider = mLabelContainer.findViewById(R.id.underline);
mExpandIndicator = mLabelContainer.findViewById(R.id.expand_indicator);
mExpandSpace = mLabelContainer.findViewById(R.id.expand_space);
+ mSecondLine = mLabelContainer.findViewById(R.id.app_label);
+ mSecondLine.setAlpha(.6f);
addView(mLabelContainer);
}
@@ -103,14 +108,20 @@ public class QSTileView extends QSTileBaseView {
mState = state.state;
mLabel.setText(state.label);
}
- mExpandIndicator.setVisibility(state.dualTarget ? View.VISIBLE : View.GONE);
- mExpandSpace.setVisibility(state.dualTarget ? View.VISIBLE : View.GONE);
- mLabelContainer.setContentDescription(state.dualTarget ? state.dualLabelContentDescription
+ if (!Objects.equal(mSecondLine.getText(), state.secondaryLabel)) {
+ mSecondLine.setText(state.secondaryLabel);
+ mSecondLine.setVisibility(TextUtils.isEmpty(state.secondaryLabel) ? View.GONE
+ : View.VISIBLE);
+ }
+ boolean dualTarget = DUAL_TARGET_ALLOWED && state.dualTarget;
+ mExpandIndicator.setVisibility(dualTarget ? View.VISIBLE : View.GONE);
+ mExpandSpace.setVisibility(dualTarget ? View.VISIBLE : View.GONE);
+ mLabelContainer.setContentDescription(dualTarget ? state.dualLabelContentDescription
: null);
- if (state.dualTarget != mLabelContainer.isClickable()) {
- mLabelContainer.setClickable(state.dualTarget);
- mLabelContainer.setLongClickable(state.dualTarget);
- mLabelContainer.setBackground(state.dualTarget ? newTileBackground() : null);
+ if (dualTarget != mLabelContainer.isClickable()) {
+ mLabelContainer.setClickable(dualTarget);
+ mLabelContainer.setLongClickable(dualTarget);
+ mLabelContainer.setBackground(dualTarget ? newTileBackground() : null);
}
mLabel.setEnabled(!state.disabledByPolicy);
mPadLock.setVisibility(state.disabledByPolicy ? View.VISIBLE : View.GONE);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
index bef1aff571f3..9883da6f3a35 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
@@ -21,6 +21,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
+import android.os.UserManager;
import android.provider.Settings;
import android.provider.Settings.Global;
import android.service.quicksettings.Tile;
@@ -82,6 +83,7 @@ public class AirplaneModeTile extends QSTileImpl<BooleanState> {
@Override
protected void handleUpdateState(BooleanState state, Object arg) {
+ checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_AIRPLANE_MODE);
final int value = arg instanceof Integer ? (Integer)arg : mSetting.getValue();
final boolean airplaneMode = value != 0;
state.value = airplaneMode;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.java
new file mode 100644
index 000000000000..ff3fe731ad4f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles;
+
+import static android.service.quicksettings.Tile.STATE_ACTIVE;
+import static android.service.quicksettings.Tile.STATE_UNAVAILABLE;
+
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.QS_ALARM;
+import static com.android.systemui.keyguard.KeyguardSliceProvider.formatNextAlarm;
+
+import android.app.AlarmManager.AlarmClockInfo;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.provider.AlarmClock;
+
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.statusbar.policy.NextAlarmController;
+import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback;
+
+public class AlarmTile extends QSTileImpl implements NextAlarmChangeCallback {
+ private final NextAlarmController mController;
+ private String mNextAlarm;
+ private PendingIntent mIntent;
+
+ public AlarmTile(QSTileHost host) {
+ super(host);
+ mController = Dependency.get(NextAlarmController.class);
+ }
+
+ @Override
+ public State newTileState() {
+ return new BooleanState();
+ }
+
+ @Override
+ protected void handleClick() {
+ if (mIntent != null) {
+ Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(mIntent);
+ }
+ }
+
+ @Override
+ protected void handleUpdateState(State state, Object arg) {
+ state.state = mNextAlarm != null ? STATE_ACTIVE : STATE_UNAVAILABLE;
+ state.label = getTileLabel();
+ state.secondaryLabel = mNextAlarm;
+ state.icon = ResourceIcon.get(R.drawable.stat_sys_alarm);
+ ((BooleanState) state).value = mNextAlarm != null;
+ }
+
+ @Override
+ public void onNextAlarmChanged(AlarmClockInfo nextAlarm) {
+ if (nextAlarm != null) {
+ mNextAlarm = formatNextAlarm(mContext, nextAlarm);
+ mIntent = nextAlarm.getShowIntent();
+ } else {
+ mNextAlarm = null;
+ mIntent = null;
+ }
+ refreshState();
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return QS_ALARM;
+ }
+
+ @Override
+ public Intent getLongClickIntent() {
+ return new Intent(AlarmClock.ACTION_SET_ALARM);
+ }
+
+ @Override
+ protected void handleSetListening(boolean listening) {
+ if (listening) {
+ mController.addCallback(this);
+ } else {
+ mController.removeCallback(this);
+ }
+ }
+
+ @Override
+ public CharSequence getTileLabel() {
+ return mContext.getString(R.string.status_bar_alarm);
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index bc3ccb41cce0..7fe9e3584ee5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -18,7 +18,9 @@ package com.android.systemui.qs.tiles;
import static com.android.settingslib.graph.BluetoothDeviceLayerDrawable.createLayerDrawable;
+import android.annotation.Nullable;
import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
@@ -26,7 +28,6 @@ import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.provider.Settings;
import android.service.quicksettings.Tile;
-import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Switch;
@@ -35,6 +36,7 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.Utils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.graph.BluetoothDeviceLayerDrawable;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.plugins.ActivityStarter;
@@ -125,50 +127,80 @@ public class BluetoothTile extends QSTileImpl<BooleanState> {
state.slash = new SlashState();
}
state.slash.isSlashed = !enabled;
+ state.label = mContext.getString(R.string.quick_settings_bluetooth_label);
+
if (enabled) {
- state.label = null;
if (connected) {
- state.icon = ResourceIcon.get(R.drawable.ic_qs_bluetooth_connected);
- state.label = mController.getLastDeviceName();
- CachedBluetoothDevice lastDevice = mController.getLastDevice();
- if (lastDevice != null) {
- int batteryLevel = lastDevice.getBatteryLevel();
- if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
- state.icon = new BluetoothBatteryDrawable(batteryLevel,
- mContext.getResources().getFraction(
- R.fraction.bt_battery_scale_fraction, 1, 1));
- }
- }
+ state.icon = new BluetoothConnectedTileIcon();
state.contentDescription = mContext.getString(
R.string.accessibility_bluetooth_name, state.label);
+
+ state.label = mController.getLastDeviceName();
} else if (state.isTransient) {
state.icon = ResourceIcon.get(R.drawable.ic_bluetooth_transient_animation);
state.contentDescription = mContext.getString(
R.string.accessibility_quick_settings_bluetooth_connecting);
- state.label = mContext.getString(R.string.quick_settings_bluetooth_label);
} else {
state.icon = ResourceIcon.get(R.drawable.ic_qs_bluetooth_on);
state.contentDescription = mContext.getString(
R.string.accessibility_quick_settings_bluetooth_on) + ","
+ mContext.getString(R.string.accessibility_not_connected);
}
- if (TextUtils.isEmpty(state.label)) {
- state.label = mContext.getString(R.string.quick_settings_bluetooth_label);
- }
state.state = Tile.STATE_ACTIVE;
} else {
state.icon = ResourceIcon.get(R.drawable.ic_qs_bluetooth_on);
- state.label = mContext.getString(R.string.quick_settings_bluetooth_label);
state.contentDescription = mContext.getString(
R.string.accessibility_quick_settings_bluetooth_off);
state.state = Tile.STATE_INACTIVE;
}
+ state.secondaryLabel = getSecondaryLabel(enabled, connected);
+
state.dualLabelContentDescription = mContext.getResources().getString(
R.string.accessibility_quick_settings_open_settings, getTileLabel());
state.expandedAccessibilityClassName = Switch.class.getName();
}
+ /**
+ * Returns the secondary label to use for the given bluetooth connection in the form of the
+ * battery level or bluetooth profile name. If the bluetooth is disabled, there's no connected
+ * devices, or we can't map the bluetooth class to a profile, this instead returns {@code null}.
+ *
+ * @param enabled whether bluetooth is enabled
+ * @param connected whether there's a device connected via bluetooth
+ */
+ @Nullable
+ private String getSecondaryLabel(boolean enabled, boolean connected) {
+ final CachedBluetoothDevice lastDevice = mController.getLastDevice();
+
+ if (enabled && connected && lastDevice != null) {
+ final int batteryLevel = lastDevice.getBatteryLevel();
+
+ if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
+ return mContext.getString(
+ R.string.quick_settings_bluetooth_secondary_label_battery_level,
+ Utils.formatPercentage(batteryLevel));
+
+ } else {
+ final BluetoothClass bluetoothClass = lastDevice.getBtClass();
+ if (bluetoothClass != null) {
+ if (bluetoothClass.doesClassMatch(BluetoothClass.PROFILE_A2DP)) {
+ return mContext.getString(
+ R.string.quick_settings_bluetooth_secondary_label_audio);
+ } else if (bluetoothClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) {
+ return mContext.getString(
+ R.string.quick_settings_bluetooth_secondary_label_headset);
+ } else if (bluetoothClass.doesClassMatch(BluetoothClass.PROFILE_HID)) {
+ return mContext.getString(
+ R.string.quick_settings_bluetooth_secondary_label_input);
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
@Override
public int getMetricsCategory() {
return MetricsEvent.QS_BLUETOOTH;
@@ -212,23 +244,48 @@ public class BluetoothTile extends QSTileImpl<BooleanState> {
return new BluetoothDetailAdapter();
}
- private class BluetoothBatteryDrawable extends Icon {
- private int mLevel;
+ /**
+ * Bluetooth icon wrapper for Quick Settings with a battery indicator that reflects the
+ * connected device's battery level. This is used instead of
+ * {@link com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon} in order to use a context
+ * that reflects dark/light theme attributes.
+ */
+ private class BluetoothBatteryTileIcon extends Icon {
+ private int mBatteryLevel;
private float mIconScale;
- BluetoothBatteryDrawable(int level) {
- this(level, 1 /* iconScale */);
+ BluetoothBatteryTileIcon(int batteryLevel, float iconScale) {
+ mBatteryLevel = batteryLevel;
+ mIconScale = iconScale;
}
- BluetoothBatteryDrawable(int level, float iconScale) {
- mLevel = level;
- mIconScale = iconScale;
+ @Override
+ public Drawable getDrawable(Context context) {
+ // This method returns Pair<Drawable, String> while first value is the drawable
+ return BluetoothDeviceLayerDrawable.createLayerDrawable(
+ context,
+ R.drawable.ic_qs_bluetooth_connected,
+ mBatteryLevel,
+ mIconScale);
+ }
+ }
+
+
+ /**
+ * Bluetooth icon wrapper (when connected with no battery indicator) for Quick Settings. This is
+ * used instead of {@link com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon} in order to
+ * use a context that reflects dark/light theme attributes.
+ */
+ private class BluetoothConnectedTileIcon extends Icon {
+
+ BluetoothConnectedTileIcon() {
+ // Do nothing. Default constructor to limit visibility.
}
@Override
public Drawable getDrawable(Context context) {
- return createLayerDrawable(context,
- R.drawable.ic_qs_bluetooth_connected, mLevel, mIconScale);
+ // This method returns Pair<Drawable, String> - the first value is the drawable.
+ return context.getDrawable(R.drawable.ic_qs_bluetooth_connected);
}
}
@@ -302,15 +359,15 @@ public class BluetoothTile extends QSTileImpl<BooleanState> {
for (CachedBluetoothDevice device : devices) {
if (mController.getBondState(device) == BluetoothDevice.BOND_NONE) continue;
final Item item = new Item();
- item.icon = R.drawable.ic_qs_bluetooth_on;
+ item.iconResId = R.drawable.ic_qs_bluetooth_on;
item.line1 = device.getName();
item.tag = device;
int state = device.getMaxConnectionState();
if (state == BluetoothProfile.STATE_CONNECTED) {
- item.icon = R.drawable.ic_qs_bluetooth_connected;
+ item.iconResId = R.drawable.ic_qs_bluetooth_connected;
int batteryLevel = device.getBatteryLevel();
if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
- item.iconDrawable = new BluetoothBatteryDrawable(batteryLevel);
+ item.icon = new BluetoothBatteryTileIcon(batteryLevel,1 /* iconScale */);
item.line2 = mContext.getString(
R.string.quick_settings_connected_battery_level,
Utils.formatPercentage(batteryLevel));
@@ -321,7 +378,7 @@ public class BluetoothTile extends QSTileImpl<BooleanState> {
items.add(connectedDevices, item);
connectedDevices++;
} else if (state == BluetoothProfile.STATE_CONNECTING) {
- item.icon = R.drawable.ic_qs_bluetooth_connecting;
+ item.iconResId = R.drawable.ic_qs_bluetooth_connecting;
item.line2 = mContext.getString(R.string.quick_settings_connecting);
items.add(connectedDevices, item);
} 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 fb396b9ddb07..678aa7116f73 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -19,26 +19,17 @@ package com.android.systemui.qs.tiles;
import static android.media.MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY;
import android.app.Dialog;
-import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
-import android.media.MediaRouter;
-import android.os.UserHandle;
import android.provider.Settings;
import android.service.quicksettings.Tile;
import android.util.Log;
-import android.view.ContextThemeWrapper;
import android.view.View;
import android.view.View.OnAttachStateChangeListener;
-import android.view.View.OnClickListener;
import android.view.ViewGroup;
-import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.widget.Button;
-import com.android.internal.app.MediaRouteChooserDialog;
-import com.android.internal.app.MediaRouteControllerDialog;
import com.android.internal.app.MediaRouteDialogPresenter;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -280,7 +271,7 @@ public class CastTile extends QSTileImpl<BooleanState> {
for (CastDevice device : devices) {
if (device.state == CastDevice.STATE_CONNECTED) {
final Item item = new Item();
- item.icon = R.drawable.ic_qs_cast_on;
+ item.iconResId = R.drawable.ic_qs_cast_on;
item.line1 = getDeviceName(device);
item.line2 = mContext.getString(R.string.quick_settings_connected);
item.tag = device;
@@ -300,7 +291,7 @@ public class CastTile extends QSTileImpl<BooleanState> {
final CastDevice device = mVisibleOrder.get(id);
if (!devices.contains(device)) continue;
final Item item = new Item();
- item.icon = R.drawable.ic_qs_cast_off;
+ item.iconResId = R.drawable.ic_qs_cast_off;
item.line1 = getDeviceName(device);
if (device.state == CastDevice.STATE_CONNECTING) {
item.line2 = mContext.getString(R.string.quick_settings_connecting);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
index 0b0f58bc3ef1..0ce3e6aa9211 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -112,6 +112,9 @@ public class CellularTile extends QSTileImpl<SignalState> {
@Override
protected void handleClick() {
+ if (getState().state == Tile.STATE_UNAVAILABLE) {
+ return;
+ }
if (mDataController.isMobileDataEnabled()) {
if (mKeyguardMonitor.isSecure() && !mKeyguardMonitor.canSkipBouncer()) {
mActivityStarter.postQSRunnableDismissingKeyguard(this::showDisableDialog);
@@ -190,7 +193,7 @@ public class CellularTile extends QSTileImpl<SignalState> {
}
if (cb.airplaneModeEnabled | cb.noSim) {
- state.state = Tile.STATE_INACTIVE;
+ state.state = Tile.STATE_UNAVAILABLE;
} else {
state.state = Tile.STATE_ACTIVE;
}
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 9e265e2295c3..6205e9afcb03 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -19,6 +19,7 @@ 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.Dialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -33,29 +34,29 @@ import android.provider.Settings.Global;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenModeConfig.ZenRule;
import android.service.quicksettings.Tile;
-import android.util.Log;
import android.util.Slog;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnAttachStateChangeListener;
import android.view.ViewGroup;
+import android.view.WindowManager;
import android.widget.Switch;
import android.widget.Toast;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.settingslib.notification.EnableZenModeDialog;
import com.android.systemui.Dependency;
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.SysUIToast;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.DetailAdapter;
-import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.ZenModeController;
-import com.android.systemui.statusbar.policy.ZenModeController.Callback;
import com.android.systemui.volume.ZenModePanel;
/** Quick settings tile: Do not disturb **/
@@ -70,9 +71,6 @@ public class DndTile extends QSTileImpl<BooleanState> {
private static final String ACTION_SET_VISIBLE = "com.android.systemui.dndtile.SET_VISIBLE";
private static final String EXTRA_VISIBLE = "visible";
- private static final QSTile.Icon TOTAL_SILENCE =
- ResourceIcon.get(R.drawable.ic_qs_dnd_on_total_silence);
-
private final ZenModeController mController;
private final DndDetailAdapter mDetailAdapter;
@@ -131,15 +129,28 @@ public class DndTile extends QSTileImpl<BooleanState> {
@Override
protected void handleClick() {
+ // Zen is currently on
if (mState.value) {
mController.setZen(ZEN_MODE_OFF, null, TAG);
} else {
- int zen = Prefs.getInt(mContext, Prefs.Key.DND_FAVORITE_ZEN, Global.ZEN_MODE_ALARMS);
- mController.setZen(zen, null, TAG);
+ showDetail(true);
}
}
@Override
+ public void showDetail(boolean show) {
+ mUiHandler.post(() -> {
+ Dialog mDialog = new EnableZenModeDialog(mContext).createDialog();
+ mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+ SystemUIDialog.setShowForAllUsers(mDialog, true);
+ SystemUIDialog.registerDismissListener(mDialog);
+ SystemUIDialog.setWindowOnTop(mDialog);
+ mUiHandler.post(() -> mDialog.show());
+ mHost.collapsePanels();
+ });
+ }
+
+ @Override
protected void handleSecondaryClick() {
if (mController.isVolumeRestricted()) {
// Collapse the panels, so the user can see the toast.
@@ -159,9 +170,7 @@ public class DndTile extends QSTileImpl<BooleanState> {
showDetail(true);
}
});
- int zen = Prefs.getInt(mContext, Prefs.Key.DND_FAVORITE_ZEN,
- Global.ZEN_MODE_ALARMS);
- mController.setZen(zen, null, TAG);
+ mController.setZen(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG);
} else {
showDetail(true);
}
@@ -182,29 +191,25 @@ public class DndTile extends QSTileImpl<BooleanState> {
state.value = newValue;
state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
state.slash.isSlashed = !state.value;
+ state.label = getTileLabel();
+ state.secondaryLabel = ZenModeConfig.getDescription(mContext,zen != Global.ZEN_MODE_OFF,
+ mController.getConfig());
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_dnd_on);
checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_ADJUST_VOLUME);
switch (zen) {
case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
- state.icon = ResourceIcon.get(R.drawable.ic_qs_dnd_on);
- state.label = mContext.getString(R.string.quick_settings_dnd_priority_label);
state.contentDescription = mContext.getString(
R.string.accessibility_quick_settings_dnd_priority_on);
break;
case Global.ZEN_MODE_NO_INTERRUPTIONS:
- state.icon = TOTAL_SILENCE;
- state.label = mContext.getString(R.string.quick_settings_dnd_none_label);
state.contentDescription = mContext.getString(
R.string.accessibility_quick_settings_dnd_none_on);
break;
case ZEN_MODE_ALARMS:
- state.icon = ResourceIcon.get(R.drawable.ic_qs_dnd_on);
- state.label = mContext.getString(R.string.quick_settings_dnd_alarms_label);
state.contentDescription = mContext.getString(
R.string.accessibility_quick_settings_dnd_alarms_on);
break;
default:
- state.icon = ResourceIcon.get(R.drawable.ic_qs_dnd_on);
- state.label = mContext.getString(R.string.quick_settings_dnd_label);
state.contentDescription = mContext.getString(
R.string.accessibility_quick_settings_dnd);
break;
@@ -313,9 +318,7 @@ public class DndTile extends QSTileImpl<BooleanState> {
mController.setZen(ZEN_MODE_OFF, null, TAG);
mAuto = false;
} else {
- int zen = Prefs.getInt(mContext, Prefs.Key.DND_FAVORITE_ZEN,
- ZEN_MODE_ALARMS);
- mController.setZen(zen, null, TAG);
+ mController.setZen(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
index 910b6b174062..080e320802e6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -16,6 +16,7 @@
package com.android.systemui.qs.tiles;
+import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentFilter;
@@ -37,7 +38,7 @@ import com.android.systemui.statusbar.policy.HotspotController;
/** Quick settings tile: Hotspot **/
public class HotspotTile extends QSTileImpl<AirplaneBooleanState> {
static final Intent TETHER_SETTINGS = new Intent().setComponent(new ComponentName(
- "com.android.settings", "com.android.settings.TetherSettings"));
+ "com.android.settings", "com.android.settings.TetherSettings"));
private final Icon mEnabledStatic = ResourceIcon.get(R.drawable.ic_hotspot);
private final Icon mUnavailable = ResourceIcon.get(R.drawable.ic_hotspot_unavailable);
@@ -112,17 +113,25 @@ public class HotspotTile extends QSTileImpl<AirplaneBooleanState> {
if (state.slash == null) {
state.slash = new SlashState();
}
- state.label = mContext.getString(R.string.quick_settings_hotspot_label);
+
+ final int numConnectedDevices;
+ final boolean isTransient = mController.isHotspotTransient();
checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_CONFIG_TETHERING);
- if (arg instanceof Boolean) {
- state.value = (boolean) arg;
+ if (arg instanceof CallbackInfo) {
+ CallbackInfo info = (CallbackInfo) arg;
+ state.value = info.enabled;
+ numConnectedDevices = info.numConnectedDevices;
} else {
state.value = mController.isHotspotEnabled();
+ numConnectedDevices = mController.getNumConnectedDevices();
}
+
state.icon = mEnabledStatic;
+ state.label = mContext.getString(R.string.quick_settings_hotspot_label);
+ state.secondaryLabel = getSecondaryLabel(state.value, isTransient, numConnectedDevices);
state.isAirplaneMode = mAirplaneMode.getValue() != 0;
- state.isTransient = mController.isHotspotTransient();
+ state.isTransient = isTransient;
state.slash.isSlashed = !state.value && !state.isTransient;
if (state.isTransient) {
state.icon = ResourceIcon.get(R.drawable.ic_hotspot_transient_animation);
@@ -133,6 +142,21 @@ public class HotspotTile extends QSTileImpl<AirplaneBooleanState> {
: state.value || state.isTransient ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
}
+ @Nullable
+ private String getSecondaryLabel(
+ boolean enabled, boolean isTransient, int numConnectedDevices) {
+ if (isTransient) {
+ return mContext.getString(R.string.quick_settings_hotspot_secondary_label_transient);
+ } else if (numConnectedDevices > 0 && enabled) {
+ return mContext.getResources().getQuantityString(
+ R.plurals.quick_settings_hotspot_secondary_label_num_devices,
+ numConnectedDevices,
+ numConnectedDevices);
+ }
+
+ return null;
+ }
+
@Override
public int getMetricsCategory() {
return MetricsEvent.QS_HOTSPOT;
@@ -148,9 +172,30 @@ public class HotspotTile extends QSTileImpl<AirplaneBooleanState> {
}
private final class Callback implements HotspotController.Callback {
+ final CallbackInfo mCallbackInfo = new CallbackInfo();
+
@Override
- public void onHotspotChanged(boolean enabled) {
- refreshState(enabled);
+ public void onHotspotChanged(boolean enabled, int numConnectedDevices) {
+ mCallbackInfo.enabled = enabled;
+ mCallbackInfo.numConnectedDevices = numConnectedDevices;
+ refreshState(mCallbackInfo);
}
- };
+ }
+
+ /**
+ * Holder for any hotspot state info that needs to passed from the callback to
+ * {@link #handleUpdateState(State, Object)}.
+ */
+ protected static final class CallbackInfo {
+ boolean enabled;
+ int numConnectedDevices;
+
+ @Override
+ public String toString() {
+ return new StringBuilder("CallbackInfo[")
+ .append("enabled=").append(enabled)
+ .append(",numConnectedDevices=").append(numConnectedDevices)
+ .append(']').toString();
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
index c35f5917d6cc..d7f2a2642180 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
@@ -101,6 +101,9 @@ public class LocationTile extends QSTileImpl<BooleanState> {
// state.visible = !(mKeyguard.isSecure() && mKeyguard.isShowing());
state.value = locationEnabled;
checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_SHARE_LOCATION);
+ if (state.disabledByPolicy == false) {
+ checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_CONFIG_LOCATION);
+ }
state.icon = mIcon;
state.slash.isSlashed = !state.value;
if (locationEnabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
index 4c2036141daf..3597929229e6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
@@ -16,34 +16,46 @@
package com.android.systemui.qs.tiles;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.Intent;
import android.provider.Settings;
import android.service.quicksettings.Tile;
+import android.support.annotation.StringRes;
import android.widget.Switch;
-import com.android.internal.app.NightDisplayController;
-import com.android.internal.logging.MetricsLogger;
+import com.android.internal.app.ColorDisplayController;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.R;
import com.android.systemui.qs.QSHost;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
import com.android.systemui.qs.tileimpl.QSTileImpl;
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+
public class NightDisplayTile extends QSTileImpl<BooleanState>
- implements NightDisplayController.Callback {
+ implements ColorDisplayController.Callback {
+
+ /**
+ * Pattern for {@link java.time.format.DateTimeFormatter} used to approximate the time to the
+ * nearest hour and add on the AM/PM indicator.
+ */
+ private static final String HOUR_MINUTE_DATE_TIME_PATTERN = "h a";
+ private static final String APPROXIMATE_HOUR_DATE_TIME_PATTERN = "h:m a";
+
- private NightDisplayController mController;
+ private ColorDisplayController mController;
private boolean mIsListening;
public NightDisplayTile(QSHost host) {
super(host);
- mController = new NightDisplayController(mContext, ActivityManager.getCurrentUser());
+ mController = new ColorDisplayController(mContext, ActivityManager.getCurrentUser());
}
@Override
public boolean isAvailable() {
- return NightDisplayController.isAvailable(mContext);
+ return ColorDisplayController.isAvailable(mContext);
}
@Override
@@ -65,7 +77,7 @@ public class NightDisplayTile extends QSTileImpl<BooleanState>
}
// Make a new controller for the new user.
- mController = new NightDisplayController(mContext, newUserId);
+ mController = new ColorDisplayController(mContext, newUserId);
if (mIsListening) {
mController.setListener(this);
}
@@ -75,13 +87,58 @@ public class NightDisplayTile extends QSTileImpl<BooleanState>
@Override
protected void handleUpdateState(BooleanState state, Object arg) {
- final boolean isActivated = mController.isActivated();
- state.value = isActivated;
+ state.value = mController.isActivated();
state.label = state.contentDescription =
mContext.getString(R.string.quick_settings_night_display_label);
state.icon = ResourceIcon.get(R.drawable.ic_qs_night_display_on);
state.expandedAccessibilityClassName = Switch.class.getName();
state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
+ state.secondaryLabel = getSecondaryLabel(state.value);
+ }
+
+ /**
+ * Returns a {@link String} for the secondary label that reflects when the light will be turned
+ * on or off based on the current auto mode and night light activated status.
+ */
+ @Nullable
+ private String getSecondaryLabel(boolean isNightLightActivated) {
+ switch(mController.getAutoMode()) {
+ case ColorDisplayController.AUTO_MODE_TWILIGHT:
+ // Auto mode related to sunrise & sunset. If the light is on, it's guaranteed to be
+ // turned off at sunrise. If it's off, it's guaranteed to be turned on at sunset.
+ return isNightLightActivated
+ ? mContext.getString(
+ R.string.quick_settings_night_secondary_label_until_sunrise)
+ : mContext.getString(
+ R.string.quick_settings_night_secondary_label_on_at_sunset);
+
+ case ColorDisplayController.AUTO_MODE_CUSTOM:
+ // User-specified time, approximated to the nearest hour.
+ final @StringRes int toggleTimeStringRes;
+ final LocalTime toggleTime;
+ final DateTimeFormatter toggleTimeFormat;
+
+ if (isNightLightActivated) {
+ toggleTime = mController.getCustomEndTime();
+ toggleTimeStringRes = R.string.quick_settings_night_secondary_label_until;
+ } else {
+ toggleTime = mController.getCustomStartTime();
+ toggleTimeStringRes = R.string.quick_settings_night_secondary_label_on_at;
+ }
+
+ // Choose between just showing the hour or also showing the minutes (based on the
+ // user-selected toggle time). This helps reduce how much space the label takes.
+ toggleTimeFormat = DateTimeFormatter.ofPattern(
+ toggleTime.getMinute() == 0
+ ? HOUR_MINUTE_DATE_TIME_PATTERN
+ : APPROXIMATE_HOUR_DATE_TIME_PATTERN);
+
+ return mContext.getString(toggleTimeStringRes, toggleTime.format(toggleTimeFormat));
+
+ default:
+ // No secondary label when auto mode is disabled.
+ return null;
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
index 1e00894483ac..60422ee61aa5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
@@ -36,20 +36,8 @@ import com.android.systemui.statusbar.policy.RotationLockController.RotationLock
/** Quick settings tile: Rotation **/
public class RotationLockTile extends QSTileImpl<BooleanState> {
- private final AnimationIcon mPortraitToAuto
- = new AnimationIcon(R.drawable.ic_portrait_to_auto_rotate_animation,
- R.drawable.ic_portrait_from_auto_rotate);
- private final AnimationIcon mAutoToPortrait
- = new AnimationIcon(R.drawable.ic_portrait_from_auto_rotate_animation,
- R.drawable.ic_portrait_to_auto_rotate);
-
- private final AnimationIcon mLandscapeToAuto
- = new AnimationIcon(R.drawable.ic_landscape_to_auto_rotate_animation,
- R.drawable.ic_landscape_from_auto_rotate);
- private final AnimationIcon mAutoToLandscape
- = new AnimationIcon(R.drawable.ic_landscape_from_auto_rotate_animation,
- R.drawable.ic_landscape_to_auto_rotate);
+ private final Icon mIcon = ResourceIcon.get(R.drawable.ic_qs_auto_rotate);
private final RotationLockController mController;
public RotationLockTile(QSHost host) {
@@ -93,19 +81,10 @@ public class RotationLockTile extends QSTileImpl<BooleanState> {
protected void handleUpdateState(BooleanState state, Object arg) {
if (mController == null) return;
final boolean rotationLocked = mController.isRotationLocked();
- // TODO: Handle accessibility rotation lock and whatnot.
state.value = !rotationLocked;
- final boolean portrait = isCurrentOrientationLockPortrait(mController, mContext);
- if (rotationLocked) {
- final int label = portrait ? R.string.quick_settings_rotation_locked_portrait_label
- : R.string.quick_settings_rotation_locked_landscape_label;
- state.label = mContext.getString(label);
- state.icon = portrait ? mAutoToPortrait : mAutoToLandscape;
- } else {
- state.label = mContext.getString(R.string.quick_settings_rotation_unlocked_label);
- state.icon = portrait ? mPortraitToAuto : mLandscapeToAuto;
- }
+ state.label = mContext.getString(R.string.quick_settings_rotation_unlocked_label);
+ state.icon = mIcon;
state.contentDescription = getAccessibilityString(rotationLocked);
state.expandedAccessibilityClassName = Switch.class.getName();
state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
@@ -134,18 +113,7 @@ public class RotationLockTile extends QSTileImpl<BooleanState> {
* @param locked Whether or not rotation is locked.
*/
private String getAccessibilityString(boolean locked) {
- if (locked) {
- return mContext.getString(R.string.accessibility_quick_settings_rotation_value,
- isCurrentOrientationLockPortrait(mController, mContext)
- ? mContext.getString(
- R.string.quick_settings_rotation_locked_portrait_label)
- : mContext.getString(
- R.string.quick_settings_rotation_locked_landscape_label))
- + "," + mContext.getString(R.string.accessibility_quick_settings_rotation);
-
- } else {
- return mContext.getString(R.string.accessibility_quick_settings_rotation);
- }
+ return mContext.getString(R.string.accessibility_quick_settings_rotation);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index 23702736b0db..977a725829bb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -402,7 +402,7 @@ public class WifiTile extends QSTileImpl<SignalState> {
final AccessPoint ap = mAccessPoints[i];
final Item item = new Item();
item.tag = ap;
- item.icon = mWifiController.getIcon(ap);
+ item.iconResId = mWifiController.getIcon(ap);
item.line1 = ap.getSsid();
item.line2 = ap.isActive() ? ap.getSummary() : null;
item.icon2 = ap.getSecurity() != AccessPoint.SECURITY_NONE
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
index 5f7d6fb41311..e098fd876735 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
@@ -83,7 +83,7 @@ public class WorkModeTile extends QSTileImpl<BooleanState> implements
@Override
public CharSequence getTileLabel() {
- return mContext.getString(R.string.quick_settings_work_mode_label);
+ return mContext.getString(R.string.quick_settings_work_mode_on_label);
}
@Override
@@ -98,16 +98,17 @@ public class WorkModeTile extends QSTileImpl<BooleanState> implements
state.value = mProfileController.isWorkModeEnabled();
}
- state.label = mContext.getString(R.string.quick_settings_work_mode_label);
state.icon = mIcon;
if (state.value) {
state.slash.isSlashed = false;
state.contentDescription = mContext.getString(
R.string.accessibility_quick_settings_work_mode_on);
+ state.label = mContext.getString(R.string.quick_settings_work_mode_on_label);
} else {
state.slash.isSlashed = true;
state.contentDescription = mContext.getString(
R.string.accessibility_quick_settings_work_mode_off);
+ state.label = mContext.getString(R.string.quick_settings_work_mode_off_label);
}
state.expandedAccessibilityClassName = Switch.class.getName();
state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/touch/OverScroll.java b/packages/SystemUI/src/com/android/systemui/qs/touch/OverScroll.java
new file mode 100644
index 000000000000..046488679725
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/touch/OverScroll.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.qs.touch;
+
+/**
+ * Utility methods for overscroll damping and related effect.
+ *
+ * Copied from packages/apps/Launcher3/src/com/android/launcher3/touch/OverScroll.java
+ */
+public class OverScroll {
+
+ private static final float OVERSCROLL_DAMP_FACTOR = 0.07f;
+
+ /**
+ * This curve determines how the effect of scrolling over the limits of the page diminishes
+ * as the user pulls further and further from the bounds
+ *
+ * @param f The percentage of how much the user has overscrolled.
+ * @return A transformed percentage based on the influence curve.
+ */
+ private static float overScrollInfluenceCurve(float f) {
+ f -= 1.0f;
+ return f * f * f + 1.0f;
+ }
+
+ /**
+ * @param amount The original amount overscrolled.
+ * @param max The maximum amount that the View can overscroll.
+ * @return The dampened overscroll amount.
+ */
+ public static int dampedScroll(float amount, int max) {
+ if (Float.compare(amount, 0) == 0) return 0;
+
+ float f = amount / max;
+ f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f)));
+
+ // Clamp this factor, f, to -1 < f < 1
+ if (Math.abs(f) >= 1) {
+ f /= Math.abs(f);
+ }
+
+ return Math.round(OVERSCROLL_DAMP_FACTOR * f * max);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/touch/SwipeDetector.java b/packages/SystemUI/src/com/android/systemui/qs/touch/SwipeDetector.java
new file mode 100644
index 000000000000..252205201e5d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/touch/SwipeDetector.java
@@ -0,0 +1,356 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.qs.touch;
+
+import static android.view.MotionEvent.INVALID_POINTER_ID;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.support.annotation.NonNull;
+import android.support.annotation.VisibleForTesting;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+/**
+ * One dimensional scroll/drag/swipe gesture detector.
+ *
+ * Definition of swipe is different from android system in that this detector handles
+ * 'swipe to dismiss', 'swiping up/down a container' but also keeps scrolling state before
+ * swipe action happens
+ *
+ * Copied from packages/apps/Launcher3/src/com/android/launcher3/touch/SwipeDetector.java
+ */
+public class SwipeDetector {
+
+ private static final boolean DBG = false;
+ private static final String TAG = "SwipeDetector";
+
+ private int mScrollConditions;
+ public static final int DIRECTION_POSITIVE = 1 << 0;
+ public static final int DIRECTION_NEGATIVE = 1 << 1;
+ public static final int DIRECTION_BOTH = DIRECTION_NEGATIVE | DIRECTION_POSITIVE;
+
+ private static final float ANIMATION_DURATION = 1200;
+
+ protected int mActivePointerId = INVALID_POINTER_ID;
+
+ /**
+ * The minimum release velocity in pixels per millisecond that triggers fling..
+ */
+ public static final float RELEASE_VELOCITY_PX_MS = 1.0f;
+
+ /**
+ * The time constant used to calculate dampening in the low-pass filter of scroll velocity.
+ * Cutoff frequency is set at 10 Hz.
+ */
+ public static final float SCROLL_VELOCITY_DAMPENING_RC = 1000f / (2f * (float) Math.PI * 10);
+
+ /* Scroll state, this is set to true during dragging and animation. */
+ private ScrollState mState = ScrollState.IDLE;
+
+ enum ScrollState {
+ IDLE,
+ DRAGGING, // onDragStart, onDrag
+ SETTLING // onDragEnd
+ }
+
+ public static abstract class Direction {
+
+ abstract float getDisplacement(MotionEvent ev, int pointerIndex, PointF refPoint);
+
+ /**
+ * Distance in pixels a touch can wander before we think the user is scrolling.
+ */
+ abstract float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos);
+ }
+
+ public static final Direction VERTICAL = new Direction() {
+
+ @Override
+ float getDisplacement(MotionEvent ev, int pointerIndex, PointF refPoint) {
+ return ev.getY(pointerIndex) - refPoint.y;
+ }
+
+ @Override
+ float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos) {
+ return Math.abs(ev.getX(pointerIndex) - downPos.x);
+ }
+ };
+
+ public static final Direction HORIZONTAL = new Direction() {
+
+ @Override
+ float getDisplacement(MotionEvent ev, int pointerIndex, PointF refPoint) {
+ return ev.getX(pointerIndex) - refPoint.x;
+ }
+
+ @Override
+ float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos) {
+ return Math.abs(ev.getY(pointerIndex) - downPos.y);
+ }
+ };
+
+ //------------------- ScrollState transition diagram -----------------------------------
+ //
+ // IDLE -> (mDisplacement > mTouchSlop) -> DRAGGING
+ // DRAGGING -> (MotionEvent#ACTION_UP, MotionEvent#ACTION_CANCEL) -> SETTLING
+ // SETTLING -> (MotionEvent#ACTION_DOWN) -> DRAGGING
+ // SETTLING -> (View settled) -> IDLE
+
+ private void setState(ScrollState newState) {
+ if (DBG) {
+ Log.d(TAG, "setState:" + mState + "->" + newState);
+ }
+ // onDragStart and onDragEnd is reported ONLY on state transition
+ if (newState == ScrollState.DRAGGING) {
+ initializeDragging();
+ if (mState == ScrollState.IDLE) {
+ reportDragStart(false /* recatch */);
+ } else if (mState == ScrollState.SETTLING) {
+ reportDragStart(true /* recatch */);
+ }
+ }
+ if (newState == ScrollState.SETTLING) {
+ reportDragEnd();
+ }
+
+ mState = newState;
+ }
+
+ public boolean isDraggingOrSettling() {
+ return mState == ScrollState.DRAGGING || mState == ScrollState.SETTLING;
+ }
+
+ /**
+ * There's no touch and there's no animation.
+ */
+ public boolean isIdleState() {
+ return mState == ScrollState.IDLE;
+ }
+
+ public boolean isSettlingState() {
+ return mState == ScrollState.SETTLING;
+ }
+
+ public boolean isDraggingState() {
+ return mState == ScrollState.DRAGGING;
+ }
+
+ private final PointF mDownPos = new PointF();
+ private final PointF mLastPos = new PointF();
+ private final Direction mDir;
+
+ private final float mTouchSlop;
+
+ /* Client of this gesture detector can register a callback. */
+ private final Listener mListener;
+
+ private long mCurrentMillis;
+
+ private float mVelocity;
+ private float mLastDisplacement;
+ private float mDisplacement;
+
+ private float mSubtractDisplacement;
+ private boolean mIgnoreSlopWhenSettling;
+
+ public interface Listener {
+ void onDragStart(boolean start);
+
+ boolean onDrag(float displacement, float velocity);
+
+ void onDragEnd(float velocity, boolean fling);
+ }
+
+ public SwipeDetector(@NonNull Context context, @NonNull Listener l, @NonNull Direction dir) {
+ this(ViewConfiguration.get(context).getScaledTouchSlop(), l, dir);
+ }
+
+ @VisibleForTesting
+ protected SwipeDetector(float touchSlope, @NonNull Listener l, @NonNull Direction dir) {
+ mTouchSlop = touchSlope;
+ mListener = l;
+ mDir = dir;
+ }
+
+ public void setDetectableScrollConditions(int scrollDirectionFlags, boolean ignoreSlop) {
+ mScrollConditions = scrollDirectionFlags;
+ mIgnoreSlopWhenSettling = ignoreSlop;
+ }
+
+ private boolean shouldScrollStart(MotionEvent ev, int pointerIndex) {
+ // reject cases where the angle or slop condition is not met.
+ if (Math.max(mDir.getActiveTouchSlop(ev, pointerIndex, mDownPos), mTouchSlop)
+ > Math.abs(mDisplacement)) {
+ return false;
+ }
+
+ // Check if the client is interested in scroll in current direction.
+ if (((mScrollConditions & DIRECTION_NEGATIVE) > 0 && mDisplacement > 0) ||
+ ((mScrollConditions & DIRECTION_POSITIVE) > 0 && mDisplacement < 0)) {
+ return true;
+ }
+ return false;
+ }
+
+ public boolean onTouchEvent(MotionEvent ev) {
+ switch (ev.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ mActivePointerId = ev.getPointerId(0);
+ mDownPos.set(ev.getX(), ev.getY());
+ mLastPos.set(mDownPos);
+ mLastDisplacement = 0;
+ mDisplacement = 0;
+ mVelocity = 0;
+
+ if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) {
+ setState(ScrollState.DRAGGING);
+ }
+ break;
+ //case MotionEvent.ACTION_POINTER_DOWN:
+ case MotionEvent.ACTION_POINTER_UP:
+ int ptrIdx = ev.getActionIndex();
+ int ptrId = ev.getPointerId(ptrIdx);
+ if (ptrId == mActivePointerId) {
+ final int newPointerIdx = ptrIdx == 0 ? 1 : 0;
+ mDownPos.set(
+ ev.getX(newPointerIdx) - (mLastPos.x - mDownPos.x),
+ ev.getY(newPointerIdx) - (mLastPos.y - mDownPos.y));
+ mLastPos.set(ev.getX(newPointerIdx), ev.getY(newPointerIdx));
+ mActivePointerId = ev.getPointerId(newPointerIdx);
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ int pointerIndex = ev.findPointerIndex(mActivePointerId);
+ if (pointerIndex == INVALID_POINTER_ID) {
+ break;
+ }
+ mDisplacement = mDir.getDisplacement(ev, pointerIndex, mDownPos);
+ computeVelocity(mDir.getDisplacement(ev, pointerIndex, mLastPos),
+ ev.getEventTime());
+
+ // handle state and listener calls.
+ if (mState != ScrollState.DRAGGING && shouldScrollStart(ev, pointerIndex)) {
+ setState(ScrollState.DRAGGING);
+ }
+ if (mState == ScrollState.DRAGGING) {
+ reportDragging();
+ }
+ mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ // These are synthetic events and there is no need to update internal values.
+ if (mState == ScrollState.DRAGGING) {
+ setState(ScrollState.SETTLING);
+ }
+ break;
+ default:
+ break;
+ }
+ return true;
+ }
+
+ public void finishedScrolling() {
+ setState(ScrollState.IDLE);
+ }
+
+ private boolean reportDragStart(boolean recatch) {
+ mListener.onDragStart(!recatch);
+ if (DBG) {
+ Log.d(TAG, "onDragStart recatch:" + recatch);
+ }
+ return true;
+ }
+
+ private void initializeDragging() {
+ if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) {
+ mSubtractDisplacement = 0;
+ }
+ if (mDisplacement > 0) {
+ mSubtractDisplacement = mTouchSlop;
+ } else {
+ mSubtractDisplacement = -mTouchSlop;
+ }
+ }
+
+ private boolean reportDragging() {
+ if (mDisplacement != mLastDisplacement) {
+ if (DBG) {
+ Log.d(TAG, String.format("onDrag disp=%.1f, velocity=%.1f",
+ mDisplacement, mVelocity));
+ }
+
+ mLastDisplacement = mDisplacement;
+ return mListener.onDrag(mDisplacement - mSubtractDisplacement, mVelocity);
+ }
+ return true;
+ }
+
+ private void reportDragEnd() {
+ if (DBG) {
+ Log.d(TAG, String.format("onScrollEnd disp=%.1f, velocity=%.1f",
+ mDisplacement, mVelocity));
+ }
+ mListener.onDragEnd(mVelocity, Math.abs(mVelocity) > RELEASE_VELOCITY_PX_MS);
+
+ }
+
+ /**
+ * Computes the damped velocity.
+ */
+ public float computeVelocity(float delta, long currentMillis) {
+ long previousMillis = mCurrentMillis;
+ mCurrentMillis = currentMillis;
+
+ float deltaTimeMillis = mCurrentMillis - previousMillis;
+ float velocity = (deltaTimeMillis > 0) ? (delta / deltaTimeMillis) : 0;
+ if (Math.abs(mVelocity) < 0.001f) {
+ mVelocity = velocity;
+ } else {
+ float alpha = computeDampeningFactor(deltaTimeMillis);
+ mVelocity = interpolate(mVelocity, velocity, alpha);
+ }
+ return mVelocity;
+ }
+
+ /**
+ * Returns a time-dependent dampening factor using delta time.
+ */
+ private static float computeDampeningFactor(float deltaTime) {
+ return deltaTime / (SCROLL_VELOCITY_DAMPENING_RC + deltaTime);
+ }
+
+ /**
+ * Returns the linear interpolation between two values
+ */
+ private static float interpolate(float from, float to, float alpha) {
+ return (1.0f - alpha) * from + alpha * to;
+ }
+
+ public static long calculateDuration(float velocity, float progressNeeded) {
+ // TODO: make these values constants after tuning.
+ float velocityDivisor = Math.max(2f, Math.abs(0.5f * velocity));
+ float travelDistance = Math.max(0.2f, progressNeeded);
+ long duration = (long) Math.max(100, ANIMATION_DURATION / velocityDivisor * travelDistance);
+ if (DBG) {
+ Log.d(TAG, String.format("calculateDuration=%d, v=%f, d=%f", duration, velocity, progressNeeded));
+ }
+ return duration;
+ }
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl b/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl
index 9214eef61df5..fc1831d55c9d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl
+++ b/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl
@@ -27,11 +27,11 @@ oneway interface IRecentsNonSystemUserCallbacks {
void preloadRecents();
void cancelPreloadingRecents();
void showRecents(boolean triggeredFromAltTab, boolean draggingInRecents, boolean animate,
- boolean reloadTasks, boolean fromHome, int recentsGrowTarget);
+ int recentsGrowTarget);
void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey);
void toggleRecents(int recentsGrowTarget);
void onConfigurationChanged();
- void dockTopTask(int topTaskId, int dragMode, int stackCreateMode,
+ void splitPrimaryTask(int topTaskId, int dragMode, int stackCreateMode,
in Rect initialBounds);
void onDraggingInRecents(float distanceFromTop);
void onDraggingInRecentsEnded(float velocity);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/IRecentsSystemUserCallbacks.aidl b/packages/SystemUI/src/com/android/systemui/recents/IRecentsSystemUserCallbacks.aidl
index cc7798e8721b..58d8d8fd600a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/IRecentsSystemUserCallbacks.aidl
+++ b/packages/SystemUI/src/com/android/systemui/recents/IRecentsSystemUserCallbacks.aidl
@@ -31,5 +31,6 @@ oneway interface IRecentsSystemUserCallbacks {
void sendRecentsDrawnEvent();
void sendDockingTopTaskEvent(int dragMode, in Rect initialRect);
void sendLaunchRecentsEvent();
+ void sendDockedFirstAnimationFrameEvent();
void setWaitingForTransitionStartEvent(boolean waitingForTransitionStart);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index 406bcac08daf..409c753c147c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -16,6 +16,9 @@
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 static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS;
import android.app.ActivityManager;
@@ -26,14 +29,13 @@ import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
-import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
-import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.EventLog;
@@ -50,6 +52,7 @@ import com.android.systemui.RecentsComponent;
import com.android.systemui.SystemUI;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.ConfigurationChangedEvent;
+import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent;
import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
@@ -59,7 +62,8 @@ import com.android.systemui.recents.events.component.SetWaitingForTransitionStar
import com.android.systemui.recents.events.component.ShowUserToastEvent;
import com.android.systemui.recents.events.ui.RecentsDrawnEvent;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.model.RecentsTaskLoader;
+import com.android.systemui.shared.recents.model.RecentsTaskLoader;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.stackdivider.Divider;
import com.android.systemui.statusbar.CommandQueue;
@@ -78,23 +82,15 @@ public class Recents extends SystemUI
implements RecentsComponent, CommandQueue.Callbacks {
private final static String TAG = "Recents";
- private final static boolean DEBUG = false;
public final static int EVENT_BUS_PRIORITY = 1;
public final static int BIND_TO_SYSTEM_USER_RETRY_DELAY = 5000;
- public final static int RECENTS_GROW_TARGET_INVALID = -1;
public final static Set<String> RECENTS_ACTIVITIES = new HashSet<>();
static {
RECENTS_ACTIVITIES.add(RecentsImpl.RECENTS_ACTIVITY);
}
- // Purely for experimentation
- private final static String RECENTS_OVERRIDE_SYSPROP_KEY = "persist.recents_override_pkg";
- private final static String ACTION_SHOW_RECENTS = "com.android.systemui.recents.ACTION_SHOW";
- private final static String ACTION_HIDE_RECENTS = "com.android.systemui.recents.ACTION_HIDE";
- private final static String ACTION_TOGGLE_RECENTS = "com.android.systemui.recents.ACTION_TOGGLE";
-
private static final String COUNTER_WINDOW_SUPPORTED = "window_enter_supported";
private static final String COUNTER_WINDOW_UNSUPPORTED = "window_enter_unsupported";
private static final String COUNTER_WINDOW_INCOMPATIBLE = "window_enter_incompatible";
@@ -104,11 +100,6 @@ public class Recents extends SystemUI
private static RecentsTaskLoader sTaskLoader;
private static RecentsConfiguration sConfiguration;
- // For experiments only, allows another package to handle recents if it is defined in the system
- // properties. This is limited to show/toggle/hide, and does not tie into the ActivityManager,
- // and does not reside in the home stack.
- private String mOverrideRecentsPackageName;
-
private Handler mHandler;
private RecentsImpl mImpl;
private int mDraggingInRecentsCurrentUser;
@@ -201,21 +192,23 @@ public class Recents extends SystemUI
@Override
public void start() {
- sDebugFlags = new RecentsDebugFlags(mContext);
+ final Resources res = mContext.getResources();
+ final int defaultTaskBarBackgroundColor =
+ mContext.getColor(R.color.recents_task_bar_default_background_color);
+ final int defaultTaskViewBackgroundColor =
+ mContext.getColor(R.color.recents_task_view_default_background_color);
+ sDebugFlags = new RecentsDebugFlags();
sSystemServicesProxy = SystemServicesProxy.getInstance(mContext);
sConfiguration = new RecentsConfiguration(mContext);
- sTaskLoader = new RecentsTaskLoader(mContext);
+ sTaskLoader = new RecentsTaskLoader(mContext,
+ // TODO: Once we start building the AAR, move these into the loader
+ res.getInteger(R.integer.config_recents_max_thumbnail_count),
+ res.getInteger(R.integer.config_recents_max_icon_count),
+ res.getInteger(R.integer.recents_svelte_level));
+ sTaskLoader.setDefaultColors(defaultTaskBarBackgroundColor, defaultTaskViewBackgroundColor);
mHandler = new Handler();
mImpl = new RecentsImpl(mContext);
- // Check if there is a recents override package
- if (Build.IS_USERDEBUG || Build.IS_ENG) {
- String cnStr = SystemProperties.get(RECENTS_OVERRIDE_SYSPROP_KEY);
- if (!cnStr.isEmpty()) {
- mOverrideRecentsPackageName = cnStr;
- }
- }
-
// Register with the event bus
EventBus.getDefault().register(this, EVENT_BUS_PRIORITY);
EventBus.getDefault().register(sSystemServicesProxy, EVENT_BUS_PRIORITY);
@@ -247,27 +240,19 @@ public class Recents extends SystemUI
* Shows the Recents.
*/
@Override
- public void showRecentApps(boolean triggeredFromAltTab, boolean fromHome) {
+ public void showRecentApps(boolean triggeredFromAltTab) {
// Ensure the device has been provisioned before allowing the user to interact with
// recents
if (!isUserSetup()) {
return;
}
- if (proxyToOverridePackage(ACTION_SHOW_RECENTS)) {
- return;
- }
- try {
- ActivityManager.getService().closeSystemDialogs(SYSTEM_DIALOG_REASON_RECENT_APPS);
- } catch (RemoteException e) {
- }
-
+ ActivityManagerWrapper.getInstance().closeSystemWindows(SYSTEM_DIALOG_REASON_RECENT_APPS);
int recentsGrowTarget = getComponent(Divider.class).getView().growsRecents();
-
int currentUser = sSystemServicesProxy.getCurrentUser();
if (sSystemServicesProxy.isSystemUser(currentUser)) {
mImpl.showRecents(triggeredFromAltTab, false /* draggingInRecents */,
- true /* animate */, false /* reloadTasks */, fromHome, recentsGrowTarget);
+ true /* animate */, recentsGrowTarget);
} else {
if (mSystemToUserCallbacks != null) {
IRecentsNonSystemUserCallbacks callbacks =
@@ -275,8 +260,7 @@ public class Recents extends SystemUI
if (callbacks != null) {
try {
callbacks.showRecents(triggeredFromAltTab, false /* draggingInRecents */,
- true /* animate */, false /* reloadTasks */, fromHome,
- recentsGrowTarget);
+ true /* animate */, recentsGrowTarget);
} catch (RemoteException e) {
Log.e(TAG, "Callback failed", e);
}
@@ -298,10 +282,6 @@ public class Recents extends SystemUI
return;
}
- if (proxyToOverridePackage(ACTION_HIDE_RECENTS)) {
- return;
- }
-
int currentUser = sSystemServicesProxy.getCurrentUser();
if (sSystemServicesProxy.isSystemUser(currentUser)) {
mImpl.hideRecents(triggeredFromAltTab, triggeredFromHomeKey);
@@ -333,12 +313,7 @@ public class Recents extends SystemUI
return;
}
- if (proxyToOverridePackage(ACTION_TOGGLE_RECENTS)) {
- return;
- }
-
int growTarget = getComponent(Divider.class).getView().growsRecents();
-
int currentUser = sSystemServicesProxy.getCurrentUser();
if (sSystemServicesProxy.isSystemUser(currentUser)) {
mImpl.toggleRecents(growTarget);
@@ -419,7 +394,7 @@ public class Recents extends SystemUI
}
@Override
- public boolean dockTopTask(int dragMode, int stackCreateMode, Rect initialBounds,
+ public boolean splitPrimaryTask(int dragMode, int stackCreateMode, Rect initialBounds,
int metricsDockAction) {
// Ensure the device has been provisioned before allowing the user to interact with
// recents
@@ -435,11 +410,14 @@ public class Recents extends SystemUI
}
int currentUser = sSystemServicesProxy.getCurrentUser();
- SystemServicesProxy ssp = Recents.getSystemServices();
- ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
- boolean screenPinningActive = ssp.isScreenPinningActive();
- boolean isRunningTaskInHomeOrRecentsStack = runningTask != null &&
- ActivityManager.StackId.isHomeOrRecentsStack(runningTask.stackId);
+ ActivityManager.RunningTaskInfo runningTask =
+ ActivityManagerWrapper.getInstance().getRunningTask();
+ final int activityType = runningTask != null
+ ? runningTask.configuration.windowConfiguration.getActivityType()
+ : ACTIVITY_TYPE_UNDEFINED;
+ boolean screenPinningActive = ActivityManagerWrapper.getInstance().isLockToAppActive();
+ boolean isRunningTaskInHomeOrRecentsStack =
+ activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS;
if (runningTask != null && !isRunningTaskInHomeOrRecentsStack && !screenPinningActive) {
logDockAttempt(mContext, runningTask.topActivity, runningTask.resizeMode);
if (runningTask.supportsSplitScreenMultiWindow) {
@@ -448,15 +426,16 @@ public class Recents extends SystemUI
runningTask.topActivity.flattenToShortString());
}
if (sSystemServicesProxy.isSystemUser(currentUser)) {
- mImpl.dockTopTask(runningTask.id, dragMode, stackCreateMode, initialBounds);
+ mImpl.splitPrimaryTask(runningTask.id, dragMode, stackCreateMode,
+ initialBounds);
} else {
if (mSystemToUserCallbacks != null) {
IRecentsNonSystemUserCallbacks callbacks =
mSystemToUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
if (callbacks != null) {
try {
- callbacks.dockTopTask(runningTask.id, dragMode, stackCreateMode,
- initialBounds);
+ callbacks.splitPrimaryTask(runningTask.id, dragMode,
+ stackCreateMode, initialBounds);
} catch (RemoteException e) {
Log.e(TAG, "Callback failed", e);
}
@@ -628,6 +607,23 @@ public class Recents extends SystemUI
}
}
+ public final void onBusEvent(DockedFirstAnimationFrameEvent event) {
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ int processUser = ssp.getProcessUser();
+ if (!ssp.isSystemUser(processUser)) {
+ postToSystemUser(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ mUserToSystemCallbacks.sendDockedFirstAnimationFrameEvent();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Callback failed", e);
+ }
+ }
+ });
+ }
+ }
+
/**
* Handle screen pinning request.
*/
@@ -814,21 +810,6 @@ public class Recents extends SystemUI
(Settings.Secure.getInt(cr, Settings.Secure.USER_SETUP_COMPLETE, 0) != 0);
}
- /**
- * Attempts to proxy the following action to the override recents package.
- * @return whether the proxying was successful
- */
- private boolean proxyToOverridePackage(String action) {
- if (mOverrideRecentsPackageName != null) {
- Intent intent = new Intent(action);
- intent.setPackage(mOverrideRecentsPackageName);
- intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- mContext.sendBroadcast(intent);
- return true;
- }
- return false;
- }
-
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("Recents");
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 86b77900fdbc..b0a2fadf1f84 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -16,8 +16,9 @@
package com.android.systemui.recents;
+import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_HOME_KEY;
+
import android.app.Activity;
-import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.TaskStackBuilder;
import android.app.WallpaperManager;
@@ -29,10 +30,10 @@ import android.content.res.Configuration;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
+import android.os.Looper;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
-import android.provider.Settings.Secure;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
@@ -42,9 +43,10 @@ import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import com.android.internal.colorextraction.ColorExtractor;
+import com.android.internal.content.PackageMonitor;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.keyguard.LatencyTracker;
+import com.android.internal.util.LatencyTracker;
import com.android.systemui.DejankUtils;
import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
@@ -53,7 +55,6 @@ import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
import com.android.systemui.recents.events.activity.ConfigurationChangedEvent;
-import com.android.systemui.recents.events.activity.DebugFlagsChangedEvent;
import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent;
import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
@@ -61,10 +62,10 @@ import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationC
import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
import com.android.systemui.recents.events.activity.ExitRecentsWindowFirstAnimationFrameEvent;
import com.android.systemui.recents.events.activity.HideRecentsEvent;
-import com.android.systemui.recents.events.activity.IterateRecentsEvent;
import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
import com.android.systemui.recents.events.activity.LaunchTaskSucceededEvent;
import com.android.systemui.recents.events.activity.MultiWindowStateChangedEvent;
+import com.android.systemui.recents.events.activity.PackagesChangedEvent;
import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
import com.android.systemui.recents.events.component.ActivityUnpinnedEvent;
@@ -79,28 +80,24 @@ import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent;
import com.android.systemui.recents.events.ui.ShowIncompatibleAppOverlayEvent;
import com.android.systemui.recents.events.ui.StackViewScrolledEvent;
import com.android.systemui.recents.events.ui.TaskViewDismissedEvent;
-import com.android.systemui.recents.events.ui.UpdateFreeformTaskViewVisibilityEvent;
import com.android.systemui.recents.events.ui.UserInteractionEvent;
import com.android.systemui.recents.events.ui.focus.DismissFocusedTaskViewEvent;
import com.android.systemui.recents.events.ui.focus.FocusNextTaskViewEvent;
import com.android.systemui.recents.events.ui.focus.FocusPreviousTaskViewEvent;
import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent;
import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent.Direction;
-import com.android.systemui.recents.misc.DozeTrigger;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.RecentsPackageMonitor;
-import com.android.systemui.recents.model.RecentsTaskLoadPlan;
-import com.android.systemui.recents.model.RecentsTaskLoader;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
+import com.android.systemui.shared.recents.model.RecentsTaskLoader;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.TaskStack;
import com.android.systemui.recents.views.RecentsView;
import com.android.systemui.recents.views.SystemBarScrimViews;
-import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.util.List;
/**
* The main Recents activity that is started from RecentsComponent.
@@ -114,7 +111,23 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
public final static int EVENT_BUS_PRIORITY = Recents.EVENT_BUS_PRIORITY + 1;
public final static int INCOMPATIBLE_APP_ALPHA_DURATION = 150;
- private RecentsPackageMonitor mPackageMonitor;
+ private PackageMonitor mPackageMonitor = new PackageMonitor() {
+ @Override
+ public void onPackageRemoved(String packageName, int uid) {
+ RecentsActivity.this.onPackageChanged(packageName, getChangingUserId());
+ }
+
+ @Override
+ public boolean onPackageChanged(String packageName, int uid, String[] components) {
+ RecentsActivity.this.onPackageChanged(packageName, getChangingUserId());
+ return true;
+ }
+
+ @Override
+ public void onPackageModified(String packageName) {
+ RecentsActivity.this.onPackageChanged(packageName, getChangingUserId());
+ }
+ };
private Handler mHandler = new Handler();
private long mLastTabKeyEventTime;
private boolean mFinishedOnStartup;
@@ -133,7 +146,6 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
// The trigger to automatically launch the current task
private int mFocusTimerDuration;
- private DozeTrigger mIterateTrigger;
private final UserInteractionEvent mUserInteractionEvent = new UserInteractionEvent();
// Theme and colors
@@ -188,41 +200,6 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
} else if (action.equals(Intent.ACTION_USER_SWITCHED)) {
// When switching users, dismiss Recents to Home similar to screen off
finish();
- } else if (action.equals(Intent.ACTION_TIME_CHANGED)) {
- // If the time shifts but the currentTime >= lastStackActiveTime, then that boundary
- // is still valid. Otherwise, we need to reset the lastStackactiveTime to the
- // currentTime and remove the old tasks in between which would not be previously
- // visible, but currently would be in the new currentTime
- int currentUser = SystemServicesProxy.getInstance(RecentsActivity.this)
- .getCurrentUser();
- long oldLastStackActiveTime = Settings.Secure.getLongForUser(getContentResolver(),
- Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, -1, currentUser);
- if (oldLastStackActiveTime != -1) {
- long currentTime = System.currentTimeMillis();
- if (currentTime < oldLastStackActiveTime) {
- // We are only removing tasks that are between the new current time
- // and the old last stack active time, they were not visible and in the
- // TaskStack so we don't need to remove any associated TaskViews but we do
- // need to load the task id's from the system
- RecentsTaskLoader loader = Recents.getTaskLoader();
- RecentsTaskLoadPlan loadPlan = loader.createLoadPlan(ctx);
- loader.preloadRawTasks(loadPlan, false /* includeFrontMostExcludedTask */);
- List<ActivityManager.RecentTaskInfo> tasks = loadPlan.getRawTasks();
- for (int i = tasks.size() - 1; i >= 0; i--) {
- ActivityManager.RecentTaskInfo task = tasks.get(i);
- if (currentTime <= task.lastActiveTime && task.lastActiveTime <
- oldLastStackActiveTime) {
- Recents.getSystemServices().removeTask(task.persistentId);
- }
- }
- Recents.getSystemServices().updateOverviewLastStackActiveTimeAsync(
- currentTime, currentUser);
-
- // Clear the last PiP task time, it's an edge case and we'd rather it
- // not relaunch the PiP task if the user double taps
- RecentsImpl.clearLastPipTime();
- }
- }
}
}
};
@@ -305,8 +282,7 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
new DismissRecentsToHomeAnimationStarted(animateTaskViews);
dismissEvent.addPostAnimationCallback(new LaunchHomeRunnable(mHomeIntent,
overrideAnimation));
- Recents.getSystemServices().sendCloseSystemWindows(
- StatusBar.SYSTEM_DIALOG_REASON_HOME_KEY);
+ ActivityManagerWrapper.getInstance().closeSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
EventBus.getDefault().send(dismissEvent);
}
@@ -340,8 +316,8 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
EventBus.getDefault().register(this, EVENT_BUS_PRIORITY);
// Initialize the package monitor
- mPackageMonitor = new RecentsPackageMonitor();
- mPackageMonitor.register(this);
+ mPackageMonitor.register(this, Looper.getMainLooper(), UserHandle.ALL,
+ true /* externalStorage */);
// Select theme based on wallpaper colors
mColorExtractor = Dependency.get(SysuiColorExtractor.class);
@@ -363,13 +339,6 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
}
mLastConfig = new Configuration(Utilities.getAppConfiguration(this));
- mFocusTimerDuration = getResources().getInteger(R.integer.recents_auto_advance_duration);
- mIterateTrigger = new DozeTrigger(mFocusTimerDuration, new Runnable() {
- @Override
- public void run() {
- dismissRecentsToFocusedTask(MetricsEvent.OVERVIEW_SELECT_TIMEOUT);
- }
- });
// Set the window background
mRecentsView.updateBackgroundScrim(getWindow(), isInMultiWindowMode());
@@ -383,20 +352,19 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
// Register the broadcast receiver to handle messages when the screen is turned off
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
- filter.addAction(Intent.ACTION_TIME_CHANGED);
filter.addAction(Intent.ACTION_USER_SWITCHED);
registerReceiver(mSystemBroadcastReceiver, filter);
getWindow().addPrivateFlags(LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION);
-
- // Reload the stack view
- reloadStackView();
}
@Override
protected void onStart() {
super.onStart();
+ // Reload the stack view whenever we are made visible again
+ reloadStackView();
+
// Notify that recents is now visible
EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, true));
MetricsLogger.visible(this, MetricsEvent.OVERVIEW_ACTIVITY);
@@ -443,14 +411,6 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
}
}
- @Override
- protected void onNewIntent(Intent intent) {
- super.onNewIntent(intent);
-
- // Reload the stack view
- reloadStackView();
- }
-
/**
* Reloads the stack views upon launching Recents.
*/
@@ -460,22 +420,21 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
RecentsTaskLoader loader = Recents.getTaskLoader();
RecentsTaskLoadPlan loadPlan = RecentsImpl.consumeInstanceLoadPlan();
if (loadPlan == null) {
- loadPlan = loader.createLoadPlan(this);
+ loadPlan = new RecentsTaskLoadPlan(this);
}
// Start loading tasks according to the load plan
RecentsConfiguration config = Recents.getConfiguration();
RecentsActivityLaunchState launchState = config.getLaunchState();
if (!loadPlan.hasTasks()) {
- loader.preloadTasks(loadPlan, launchState.launchedToTaskId,
- !launchState.launchedFromHome && !launchState.launchedViaDockGesture);
+ loader.preloadTasks(loadPlan, launchState.launchedToTaskId);
}
RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options();
loadOpts.runningTaskId = launchState.launchedToTaskId;
loadOpts.numVisibleTasks = launchState.launchedNumVisibleTasks;
loadOpts.numVisibleTaskThumbnails = launchState.launchedNumVisibleThumbnails;
- loader.loadTasks(this, loadPlan, loadOpts);
+ loader.loadTasks(loadPlan, loadOpts);
TaskStack stack = loadPlan.getTaskStack();
mRecentsView.onReload(stack, mIsVisible);
@@ -502,7 +461,7 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
if (launchState.launchedFromApp) {
Task launchTarget = stack.getLaunchTarget();
int launchTaskIndexInStack = launchTarget != null
- ? stack.indexOfStackTask(launchTarget)
+ ? stack.indexOfTask(launchTarget)
: 0;
MetricsLogger.count(this, "overview_source_app", 1);
// If from an app, track the stack index of the app in the stack (for affiliated tasks)
@@ -540,7 +499,6 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
super.onPause();
mIgnoreAltTabRelease = false;
- mIterateTrigger.stopDozing();
}
@Override
@@ -549,7 +507,7 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
// Notify of the config change
Configuration newDeviceConfiguration = Utilities.getAppConfiguration(this);
- int numStackTasks = mRecentsView.getStack().getStackTaskCount();
+ int numStackTasks = mRecentsView.getStack().getTaskCount();
EventBus.getDefault().send(new ConfigurationChangedEvent(false /* fromMultiWindow */,
mLastConfig.orientation != newDeviceConfiguration.orientation,
mLastConfig.densityDpi != newDeviceConfiguration.densityDpi, numStackTasks > 0));
@@ -564,7 +522,11 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
// Set the window background
mRecentsView.updateBackgroundScrim(getWindow(), isInMultiWindowMode);
- reloadTaskStack(isInMultiWindowMode, true /* sendConfigChangedEvent */);
+ // Reload the task stack view if we are still visible to pick up the change in tasks that
+ // result from entering/exiting multi-window
+ if (mIsVisible) {
+ reloadTaskStack(isInMultiWindowMode, true /* sendConfigChangedEvent */);
+ }
}
@Override
@@ -643,8 +605,7 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
if (backward) {
EventBus.getDefault().send(new FocusPreviousTaskViewEvent());
} else {
- EventBus.getDefault().send(
- new FocusNextTaskViewEvent(0 /* timerIndicatorDuration */));
+ EventBus.getDefault().send(new FocusNextTaskViewEvent());
}
mLastTabKeyEventTime = SystemClock.elapsedRealtime();
@@ -702,38 +663,10 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
}
}
- public final void onBusEvent(IterateRecentsEvent event) {
- final RecentsDebugFlags debugFlags = Recents.getDebugFlags();
-
- // Start dozing after the recents button is clicked
- int timerIndicatorDuration = 0;
- if (debugFlags.isFastToggleRecentsEnabled()) {
- timerIndicatorDuration = getResources().getInteger(
- R.integer.recents_subsequent_auto_advance_duration);
-
- mIterateTrigger.setDozeDuration(timerIndicatorDuration);
- if (!mIterateTrigger.isDozing()) {
- mIterateTrigger.startDozing();
- } else {
- mIterateTrigger.poke();
- }
- }
-
- // Focus the next task
- EventBus.getDefault().send(new FocusNextTaskViewEvent(timerIndicatorDuration));
-
- MetricsLogger.action(this, MetricsEvent.ACTION_OVERVIEW_PAGE);
- }
-
public final void onBusEvent(RecentsActivityStartingEvent event) {
mRecentsStartRequested = true;
}
- public final void onBusEvent(UserInteractionEvent event) {
- // Stop the fast-toggle dozer
- mIterateTrigger.stopDozing();
- }
-
public final void onBusEvent(HideRecentsEvent event) {
if (event.triggeredFromAltTab) {
// If we are hiding from releasing Alt-Tab, dismiss Recents to the focused app
@@ -751,15 +684,11 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
}
public final void onBusEvent(EnterRecentsWindowLastAnimationFrameEvent event) {
- EventBus.getDefault().send(new UpdateFreeformTaskViewVisibilityEvent(true));
mRecentsView.getViewTreeObserver().addOnPreDrawListener(this);
mRecentsView.invalidate();
}
public final void onBusEvent(ExitRecentsWindowFirstAnimationFrameEvent event) {
- if (mRecentsView.isLastTaskLaunchedFreeform()) {
- EventBus.getDefault().send(new UpdateFreeformTaskViewVisibilityEvent(false));
- }
mRecentsView.getViewTreeObserver().addOnPreDrawListener(this);
mRecentsView.invalidate();
}
@@ -774,9 +703,8 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
int launchToTaskId = launchState.launchedToTaskId;
if (launchToTaskId != -1 &&
(event.launchTask == null || launchToTaskId != event.launchTask.key.id)) {
- SystemServicesProxy ssp = Recents.getSystemServices();
- ssp.cancelWindowTransition(launchState.launchedToTaskId);
- ssp.cancelThumbnailTransition(getTaskId());
+ ActivityManagerWrapper am = ActivityManagerWrapper.getInstance();
+ am.cancelWindowTransition(launchState.launchedToTaskId);
}
}
@@ -823,8 +751,7 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
loader.deleteTaskData(event.task, false);
// Remove the task from activity manager
- SystemServicesProxy ssp = Recents.getSystemServices();
- ssp.removeTask(event.task.key.id);
+ ActivityManagerWrapper.getInstance().removeTask(event.task.key.id);
}
public final void onBusEvent(TaskViewDismissedEvent event) {
@@ -859,11 +786,6 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
MetricsLogger.count(this, "overview_screen_pinned", 1);
}
- public final void onBusEvent(DebugFlagsChangedEvent event) {
- // Just finish recents so that we can reload the flags anew on the next instantiation
- finish();
- }
-
public final void onBusEvent(StackViewScrolledEvent event) {
// Once the user has scrolled while holding alt-tab, then we should ignore the release of
// the key
@@ -888,17 +810,16 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
RecentsConfiguration config = Recents.getConfiguration();
RecentsActivityLaunchState launchState = config.getLaunchState();
RecentsTaskLoader loader = Recents.getTaskLoader();
- RecentsTaskLoadPlan loadPlan = loader.createLoadPlan(this);
- loader.preloadTasks(loadPlan, -1 /* runningTaskId */,
- false /* includeFrontMostExcludedTask */);
+ RecentsTaskLoadPlan loadPlan = new RecentsTaskLoadPlan(this);
+ loader.preloadTasks(loadPlan, -1 /* runningTaskId */);
RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options();
loadOpts.numVisibleTasks = launchState.launchedNumVisibleTasks;
loadOpts.numVisibleTaskThumbnails = launchState.launchedNumVisibleThumbnails;
- loader.loadTasks(this, loadPlan, loadOpts);
+ loader.loadTasks(loadPlan, loadOpts);
TaskStack stack = loadPlan.getTaskStack();
- int numStackTasks = stack.getStackTaskCount();
+ int numStackTasks = stack.getTaskCount();
boolean showDeferredAnimation = numStackTasks > 0;
if (sendConfigChangedEvent) {
@@ -924,6 +845,11 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
return true;
}
+ public void onPackageChanged(String packageName, int userId) {
+ Recents.getTaskLoader().onPackageChanged(packageName);
+ EventBus.getDefault().send(new PackagesChangedEvent(packageName, userId));
+ }
+
@Override
public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
super.dump(prefix, fd, writer, args);
@@ -931,13 +857,9 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
Recents.getTaskLoader().dump(prefix, writer);
String id = Integer.toHexString(System.identityHashCode(this));
- long lastStackActiveTime = Settings.Secure.getLongForUser(getContentResolver(),
- Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, -1,
- SystemServicesProxy.getInstance(this).getCurrentUser());
writer.print(prefix); writer.print(TAG);
writer.print(" visible="); writer.print(mIsVisible ? "Y" : "N");
- writer.print(" lastStackTaskActiveTime="); writer.print(lastStackActiveTime);
writer.print(" currentTime="); writer.print(System.currentTimeMillis());
writer.print(" [0x"); writer.print(id); writer.print("]");
writer.println();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
index 5b8ed94d5df0..14fda952b7ac 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
@@ -33,7 +33,6 @@ public class RecentsActivityLaunchState {
public boolean launchedFromPipApp;
// Set if the next activity that quick-switch will launch is the PiP activity
public boolean launchedWithNextPipApp;
- public boolean launchedFromBlacklistedApp;
public boolean launchedFromHome;
public boolean launchedViaDragGesture;
public boolean launchedViaDockGesture;
@@ -44,7 +43,6 @@ public class RecentsActivityLaunchState {
public void reset() {
launchedFromHome = false;
launchedFromApp = false;
- launchedFromBlacklistedApp = false;
launchedFromPipApp = false;
launchedWithNextPipApp = false;
launchedToTaskId = -1;
@@ -52,42 +50,4 @@ public class RecentsActivityLaunchState {
launchedViaDragGesture = false;
launchedViaDockGesture = false;
}
-
- /**
- * Returns the task to focus given the current launch state.
- */
- public int getInitialFocusTaskIndex(int numTasks, boolean useGridLayout) {
- RecentsDebugFlags debugFlags = Recents.getDebugFlags();
- RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
- if (launchedFromApp) {
- if (!launchState.launchedWithAltTab && debugFlags.isFastToggleRecentsEnabled()) {
- // If fast toggling, focus the front most task so that the next tap will launch the
- // task
- return numTasks - 1;
- }
-
- if (launchState.launchedFromBlacklistedApp) {
- // If we are launching from a blacklisted app, focus the front most task so that the
- // next tap will launch the task
- return numTasks - 1;
- }
-
- if (useGridLayout) {
- // If coming from another app to the grid layout, focus the front most task
- return numTasks - 1;
- }
-
- // If coming from another app, focus the next task
- return Math.max(0, numTasks - 2);
- } else {
- if (!launchState.launchedWithAltTab && debugFlags.isFastToggleRecentsEnabled()) {
- // If fast toggling, defer focusing until the next tap (which will automatically
- // focus the front most task)
- return -1;
- }
-
- // If coming from home, focus the front most task
- return numTasks - 1;
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index 5dc6f31cae9a..68df1d5bd322 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -20,31 +20,31 @@ import android.app.ActivityManager;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.graphics.Rect;
import android.os.SystemProperties;
import com.android.systemui.R;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.recents.views.DockState;
+import com.android.systemui.shared.recents.model.TaskStack;
/**
* Represents the dock regions for each orientation.
*/
class DockRegion {
- public static TaskStack.DockState[] PHONE_LANDSCAPE = {
+ public static DockState[] PHONE_LANDSCAPE = {
// We only allow docking to the left in landscape for now on small devices
- TaskStack.DockState.LEFT
+ DockState.LEFT
};
- public static TaskStack.DockState[] PHONE_PORTRAIT = {
+ public static DockState[] PHONE_PORTRAIT = {
// We only allow docking to the top for now on small devices
- TaskStack.DockState.TOP
+ DockState.TOP
};
- public static TaskStack.DockState[] TABLET_LANDSCAPE = {
- TaskStack.DockState.LEFT,
- TaskStack.DockState.RIGHT
+ public static DockState[] TABLET_LANDSCAPE = {
+ DockState.LEFT,
+ DockState.RIGHT
};
- public static TaskStack.DockState[] TABLET_PORTRAIT = PHONE_PORTRAIT;
+ public static DockState[] TABLET_PORTRAIT = PHONE_PORTRAIT;
}
/**
@@ -56,18 +56,6 @@ public class RecentsConfiguration {
private static final int LARGE_SCREEN_MIN_DP = 600;
private static final int XLARGE_SCREEN_MIN_DP = 720;
- /** Levels of svelte in increasing severity/austerity. */
- // No svelting.
- public static final int SVELTE_NONE = 0;
- // Limit thumbnail cache to number of visible thumbnails when Recents was loaded, disable
- // caching thumbnails as you scroll.
- public static final int SVELTE_LIMIT_CACHE = 1;
- // Disable the thumbnail cache, load thumbnails asynchronously when the activity loads and
- // evict all thumbnails when hidden.
- public static final int SVELTE_DISABLE_CACHE = 2;
- // Disable all thumbnail loading.
- public static final int SVELTE_DISABLE_LOADING = 3;
-
// Launch states
public RecentsActivityLaunchState mLaunchState = new RecentsActivityLaunchState();
@@ -125,7 +113,7 @@ public class RecentsConfiguration {
* Returns the preferred dock states for the current orientation.
* @return a list of dock states for device and its orientation
*/
- public TaskStack.DockState[] getDockStatesForCurrentOrientation() {
+ public DockState[] getDockStatesForCurrentOrientation() {
boolean isLandscape = mAppContext.getResources().getConfiguration().orientation ==
Configuration.ORIENTATION_LANDSCAPE;
RecentsConfiguration config = Recents.getConfiguration();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
index 0262a098b8d2..19185939c553 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
@@ -16,75 +16,14 @@
package com.android.systemui.recents;
-import android.content.Context;
-
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.events.activity.DebugFlagsChangedEvent;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.tuner.TunerService;
-
-/**
- * Tunable debug flags
- */
-public class RecentsDebugFlags implements TunerService.Tunable {
+public class RecentsDebugFlags {
public static class Static {
// Enables debug drawing for the transition thumbnail
public static final boolean EnableTransitionThumbnailDebugMode = false;
- // This disables the bitmap and icon caches
- public static final boolean DisableBackgroundCache = false;
- // Enables the task affiliations
- public static final boolean EnableAffiliatedTaskGroups = false;
- // Enables the button above the stack
- public static final boolean EnableStackActionButton = true;
- // Overrides the Tuner flags and enables the timeout
- private static final boolean EnableFastToggleTimeout = false;
- // Overrides the Tuner flags and enables the paging via the Recents button
- private static final boolean EnablePaging = false;
+
// Disables enter and exit transitions for other tasks for low ram devices
public static final boolean DisableRecentsLowRamEnterExitAnimation = false;
- // Enables us to create mock recents tasks
- public static final boolean EnableMockTasks = false;
- // Defines the number of mock recents packages to create
- public static final int MockTasksPackageCount = 3;
- // Defines the number of mock recents tasks to create
- public static final int MockTaskCount = 100;
- // Enables the simulated task affiliations
- public static final boolean EnableMockTaskGroups = false;
- // Defines the number of mock task affiliations per group
- public static final int MockTaskGroupsTaskCount = 12;
- }
-
- /**
- * We read the prefs once when we start the activity, then update them as the tuner changes
- * the flags.
- */
- public RecentsDebugFlags(Context context) {
- // Register all our flags, this will also call onTuningChanged() for each key, which will
- // initialize the current state of each flag
- }
-
- /**
- * @return whether we are enabling fast toggling.
- */
- public boolean isFastToggleRecentsEnabled() {
- SystemServicesProxy ssp = Recents.getSystemServices();
- if (ssp.hasFreeformWorkspaceSupport() || ssp.isTouchExplorationEnabled()) {
- return false;
- }
- return Static.EnableFastToggleTimeout;
- }
-
- /**
- * @return whether we are enabling paging.
- */
- public boolean isPagingEnabled() {
- return Static.EnablePaging;
- }
-
- @Override
- public void onTuningChanged(String key, String newValue) {
- EventBus.getDefault().send(new DebugFlagsChangedEvent());
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 5d5c4a0c69c6..3f6f30bba8c4 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -16,30 +16,31 @@
package com.android.systemui.recents;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-import static android.app.ActivityManager.StackId.isHomeOrRecentsStack;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.view.View.MeasureSpec;
+import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS;
+
import android.app.ActivityManager;
-import android.app.ActivityManager.TaskSnapshot;
import android.app.ActivityOptions;
-import android.app.ActivityOptions.OnAnimationStartedListener;
+import android.app.KeyguardManager;
+import android.app.trust.TrustManager;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
-import android.graphics.GraphicBuffer;
+import android.graphics.Bitmap;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
+import android.os.AsyncTask.Status;
import android.os.Handler;
import android.os.SystemClock;
import android.util.ArraySet;
import android.util.Log;
import android.util.MutableBoolean;
import android.util.Pair;
-import android.view.AppTransitionAnimationSpec;
import android.view.LayoutInflater;
import android.view.ViewConfiguration;
import android.view.WindowManager;
@@ -56,7 +57,6 @@ import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
import com.android.systemui.recents.events.activity.HideRecentsEvent;
-import com.android.systemui.recents.events.activity.IterateRecentsEvent;
import com.android.systemui.recents.events.activity.LaunchMostRecentTaskRequestEvent;
import com.android.systemui.recents.events.activity.LaunchNextTaskRequestEvent;
import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
@@ -72,28 +72,28 @@ import com.android.systemui.recents.events.ui.TaskSnapshotChangedEvent;
import com.android.systemui.recents.misc.DozeTrigger;
import com.android.systemui.recents.misc.ForegroundThread;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
-import com.android.systemui.recents.model.RecentsTaskLoadPlan;
-import com.android.systemui.recents.model.RecentsTaskLoader;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.Task.TaskKey;
-import com.android.systemui.recents.model.TaskGrouping;
-import com.android.systemui.recents.model.TaskStack;
-import com.android.systemui.recents.model.ThumbnailData;
-import com.android.systemui.recents.views.RecentsTransitionHelper;
-import com.android.systemui.recents.views.RecentsTransitionHelper.AppTransitionAnimationSpecsFuture;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
+import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
+import com.android.systemui.shared.recents.model.RecentsTaskLoader;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task.TaskKey;
+import com.android.systemui.shared.recents.model.TaskStack;
+import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
import com.android.systemui.recents.views.TaskStackLayoutAlgorithm.VisibilityReport;
import com.android.systemui.recents.views.TaskStackView;
-import com.android.systemui.recents.views.TaskStackViewScroller;
import com.android.systemui.recents.views.TaskViewHeader;
import com.android.systemui.recents.views.TaskViewTransform;
import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm;
+import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat;
+import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
+import com.android.systemui.shared.recents.view.RecentsTransition;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.stackdivider.DividerView;
-import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
import com.android.systemui.statusbar.phone.StatusBar;
import java.util.ArrayList;
+import java.util.List;
/**
* An implementation of the Recents component for the current user. For secondary users, this can
@@ -117,10 +117,10 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
public final static String RECENTS_ACTIVITY = "com.android.systemui.recents.RecentsActivity";
/**
- * An implementation of TaskStackListener, that allows us to listen for changes to the system
+ * An implementation of SysUiTaskStackChangeListener, that allows us to listen for changes to the system
* task stacks and update recents accordingly.
*/
- class TaskStackListenerImpl extends TaskStackListener {
+ class TaskStackListenerImpl extends SysUiTaskStackChangeListener {
@Override
public void onTaskStackChangedBackground() {
@@ -131,18 +131,18 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
// Preloads the next task
RecentsConfiguration config = Recents.getConfiguration();
- if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
+ if (config.svelteLevel == RecentsTaskLoader.SVELTE_NONE) {
Rect windowRect = getWindowRect(null /* windowRectOverride */);
if (windowRect.isEmpty()) {
return;
}
// Load the next task only if we aren't svelte
- SystemServicesProxy ssp = Recents.getSystemServices();
- ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getRunningTask();
+ ActivityManager.RunningTaskInfo runningTaskInfo =
+ ActivityManagerWrapper.getInstance().getRunningTask();
RecentsTaskLoader loader = Recents.getTaskLoader();
- RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
- loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
+ RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(mContext);
+ loader.preloadTasks(plan, -1);
TaskStack stack = plan.getTaskStack();
RecentsActivityLaunchState launchState = new RecentsActivityLaunchState();
RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
@@ -156,10 +156,11 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
// Launched from app is always the worst case (in terms of how many
// thumbnails/tasks visible)
launchState.launchedFromApp = true;
- mBackgroundLayoutAlgorithm.update(plan.getTaskStack(), EMPTY_SET, launchState);
+ mBackgroundLayoutAlgorithm.update(plan.getTaskStack(), EMPTY_SET, launchState,
+ -1 /* lastScrollPPresent */);
VisibilityReport visibilityReport =
mBackgroundLayoutAlgorithm.computeStackVisibilityReport(
- stack.getStackTasks());
+ stack.getTasks());
launchOpts.runningTaskId = runningTaskInfo != null ? runningTaskInfo.id : -1;
launchOpts.numVisibleTasks = visibilityReport.numVisibleTasks;
@@ -168,12 +169,12 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
launchOpts.onlyLoadPausedActivities = true;
launchOpts.loadThumbnails = true;
}
- loader.loadTasks(mContext, plan, launchOpts);
+ loader.loadTasks(plan, launchOpts);
}
}
@Override
- public void onActivityPinned(String packageName, int userId, int taskId) {
+ public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
// Check this is for the right user
if (!checkCurrentUserId(mContext, false /* debug */)) {
return;
@@ -200,14 +201,13 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
}
@Override
- public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) {
+ public void onTaskSnapshotChanged(int taskId, ThumbnailData snapshot) {
// Check this is for the right user
if (!checkCurrentUserId(mContext, false /* debug */)) {
return;
}
- EventBus.getDefault().send(new TaskSnapshotChangedEvent(taskId,
- ThumbnailData.createFromTaskSnapshot(snapshot)));
+ EventBus.getDefault().send(new TaskSnapshotChangedEvent(taskId, snapshot));
}
}
@@ -221,14 +221,14 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
// recents. In this case, we defer the toggle state until then and apply it immediately after.
private static boolean mToggleFollowingTransitionStart = true;
- private ActivityOptions.OnAnimationStartedListener mResetToggleFlagListener =
- new OnAnimationStartedListener() {
- @Override
- public void onAnimationStarted() {
- setWaitingForTransitionStart(false);
- }
- };
+ private Runnable mResetToggleFlagListener = new Runnable() {
+ @Override
+ public void run() {
+ setWaitingForTransitionStart(false);
+ }
+ };
+ private TrustManager mTrustManager;
protected Context mContext;
protected Handler mHandler;
TaskStackListenerImpl mTaskStackListener;
@@ -255,7 +255,6 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
// When this fires, then the user has not released alt-tab for at least
// FAST_ALT_TAB_DELAY_MS milliseconds
showRecents(mTriggeredFromAltTab, false /* draggingInRecents */, true /* animate */,
- false /* reloadTasks */, false /* fromHome */,
DividerView.INVALID_RECENTS_GROW_TARGET);
}
});
@@ -270,25 +269,26 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
// Register the task stack listener
mTaskStackListener = new TaskStackListenerImpl();
- SystemServicesProxy ssp = Recents.getSystemServices();
- ssp.registerTaskStackListener(mTaskStackListener);
+ ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
// Initialize the static configuration resources
mDummyStackView = new TaskStackView(mContext);
reloadResources();
+
+ mTrustManager = (TrustManager) mContext.getSystemService(Context.TRUST_SERVICE);
}
public void onBootCompleted() {
// When we start, preload the data associated with the previous recent tasks.
// We can use a new plan since the caches will be the same.
RecentsTaskLoader loader = Recents.getTaskLoader();
- RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
- loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
+ RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(mContext);
+ loader.preloadTasks(plan, -1);
RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
launchOpts.numVisibleTasks = loader.getIconCacheSize();
launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize();
launchOpts.onlyLoadForCache = true;
- loader.loadTasks(mContext, plan, launchOpts);
+ loader.loadTasks(plan, launchOpts);
}
public void onConfigurationChanged() {
@@ -314,16 +314,22 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
* {@link Recents#onBusEvent(ScreenPinningRequestEvent)}.
*/
public void onStartScreenPinning(Context context, int taskId) {
- SystemUIApplication app = (SystemUIApplication) context;
- StatusBar statusBar = app.getComponent(StatusBar.class);
+ final StatusBar statusBar = getStatusBar();
if (statusBar != null) {
statusBar.showScreenPinningRequest(taskId, false);
}
}
public void showRecents(boolean triggeredFromAltTab, boolean draggingInRecents,
- boolean animate, boolean launchedWhileDockingTask, boolean fromHome,
- int growTarget) {
+ boolean animate, int growTarget) {
+ final SystemServicesProxy ssp = Recents.getSystemServices();
+ final MutableBoolean isHomeStackVisible = new MutableBoolean(true);
+ final boolean isRecentsVisible = Recents.getSystemServices().isRecentsActivityVisible(
+ isHomeStackVisible);
+ final boolean fromHome = isHomeStackVisible.value;
+ final boolean launchedWhileDockingTask =
+ Recents.getSystemServices().getSplitScreenPrimaryStack() != null;
+
mTriggeredFromAltTab = triggeredFromAltTab;
mDraggingInRecents = draggingInRecents;
mLaunchedWhileDocking = launchedWhileDockingTask;
@@ -349,13 +355,12 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
try {
// Check if the top task is in the home stack, and start the recents activity
- SystemServicesProxy ssp = Recents.getSystemServices();
- boolean forceVisible = launchedWhileDockingTask || draggingInRecents;
- MutableBoolean isHomeStackVisible = new MutableBoolean(forceVisible);
- if (forceVisible || !ssp.isRecentsActivityVisible(isHomeStackVisible)) {
- ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
- startRecentsActivity(runningTask, isHomeStackVisible.value || fromHome, animate,
- growTarget);
+ final boolean forceVisible = launchedWhileDockingTask || draggingInRecents;
+ if (forceVisible || !isRecentsVisible) {
+ ActivityManager.RunningTaskInfo runningTask =
+ ActivityManagerWrapper.getInstance().getRunningTask();
+ startRecentsActivityAndDismissKeyguardIfNeeded(runningTask,
+ isHomeStackVisible.value || fromHome, animate, growTarget);
}
} catch (ActivityNotFoundException e) {
Log.e(TAG, "Failed to launch RecentsActivity", e);
@@ -381,8 +386,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
public void toggleRecents(int growTarget) {
// Skip preloading if the task is locked
- SystemServicesProxy ssp = Recents.getSystemServices();
- if (ssp.isScreenPinningActive()) {
+ if (ActivityManagerWrapper.getInstance().isLockToAppActive()) {
return;
}
@@ -404,27 +408,22 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
MutableBoolean isHomeStackVisible = new MutableBoolean(true);
long elapsedTime = SystemClock.elapsedRealtime() - mLastToggleTime;
+ SystemServicesProxy ssp = Recents.getSystemServices();
if (ssp.isRecentsActivityVisible(isHomeStackVisible)) {
- RecentsDebugFlags debugFlags = Recents.getDebugFlags();
RecentsConfiguration config = Recents.getConfiguration();
RecentsActivityLaunchState launchState = config.getLaunchState();
if (!launchState.launchedWithAltTab) {
- // Has the user tapped quickly?
- boolean isQuickTap = elapsedTime < ViewConfiguration.getDoubleTapTimeout();
if (Recents.getConfiguration().isGridEnabled) {
+ // Has the user tapped quickly?
+ boolean isQuickTap = elapsedTime < ViewConfiguration.getDoubleTapTimeout();
if (isQuickTap) {
EventBus.getDefault().post(new LaunchNextTaskRequestEvent());
} else {
EventBus.getDefault().post(new LaunchMostRecentTaskRequestEvent());
}
} else {
- if (!debugFlags.isPagingEnabled() || isQuickTap) {
- // Launch the next focused task
- EventBus.getDefault().post(new LaunchNextTaskRequestEvent());
- } else {
- // Notify recents to move onto the next task
- EventBus.getDefault().post(new IterateRecentsEvent());
- }
+ // Launch the next focused task
+ EventBus.getDefault().post(new LaunchNextTaskRequestEvent());
}
} else {
// If the user has toggled it too quickly, then just eat up the event here (it's
@@ -449,12 +448,14 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
}
// Otherwise, start the recents activity
- ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
- startRecentsActivity(runningTask, isHomeStackVisible.value, true /* animate */,
- growTarget);
+ ActivityManager.RunningTaskInfo runningTask =
+ ActivityManagerWrapper.getInstance().getRunningTask();
+ startRecentsActivityAndDismissKeyguardIfNeeded(runningTask,
+ isHomeStackVisible.value, true /* animate */, growTarget);
// Only close the other system windows if we are actually showing recents
- ssp.sendCloseSystemWindows(StatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS);
+ ActivityManagerWrapper.getInstance().closeSystemWindows(
+ SYSTEM_DIALOG_REASON_RECENT_APPS);
mLastToggleTime = SystemClock.elapsedRealtime();
}
} catch (ActivityNotFoundException e) {
@@ -464,8 +465,13 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
public void preloadRecents() {
// Skip preloading if the task is locked
- SystemServicesProxy ssp = Recents.getSystemServices();
- if (ssp.isScreenPinningActive()) {
+ if (ActivityManagerWrapper.getInstance().isLockToAppActive()) {
+ return;
+ }
+
+ // Skip preloading recents when keyguard is showing
+ final StatusBar statusBar = getStatusBar();
+ if (statusBar != null && statusBar.isKeyguardShowing()) {
return;
}
@@ -473,16 +479,17 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
// RecentsActivity) only if there is a task to animate to. Post this to ensure that we
// don't block the touch feedback on the nav bar button which triggers this.
mHandler.post(() -> {
- MutableBoolean isHomeStackVisible = new MutableBoolean(true);
- if (!ssp.isRecentsActivityVisible(isHomeStackVisible)) {
- ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ if (!ssp.isRecentsActivityVisible(null)) {
+ ActivityManager.RunningTaskInfo runningTask =
+ ActivityManagerWrapper.getInstance().getRunningTask();
if (runningTask == null) {
return;
}
RecentsTaskLoader loader = Recents.getTaskLoader();
- sInstanceLoadPlan = loader.createLoadPlan(mContext);
- loader.preloadTasks(sInstanceLoadPlan, runningTask.id, !isHomeStackVisible.value);
+ sInstanceLoadPlan = new RecentsTaskLoadPlan(mContext);
+ loader.preloadTasks(sInstanceLoadPlan, runningTask.id);
TaskStack stack = sInstanceLoadPlan.getTaskStack();
if (stack.getTaskCount() > 0) {
// Only preload the icon (but not the thumbnail since it may not have been taken
@@ -521,20 +528,23 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
public void showNextTask() {
SystemServicesProxy ssp = Recents.getSystemServices();
RecentsTaskLoader loader = Recents.getTaskLoader();
- RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
- loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
+ RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(mContext);
+ loader.preloadTasks(plan, -1);
TaskStack focusedStack = plan.getTaskStack();
// Return early if there are no tasks in the focused stack
if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
// Return early if there is no running task
- ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
+ ActivityManager.RunningTaskInfo runningTask =
+ ActivityManagerWrapper.getInstance().getRunningTask();
if (runningTask == null) return;
// Find the task in the recents list
- boolean isRunningTaskInHomeStack = SystemServicesProxy.isHomeStack(runningTask.stackId);
- ArrayList<Task> tasks = focusedStack.getStackTasks();
+ boolean isRunningTaskInHomeStack =
+ runningTask.configuration.windowConfiguration.getActivityType()
+ == ACTIVITY_TYPE_HOME;
+ ArrayList<Task> tasks = focusedStack.getTasks();
Task toTask = null;
ActivityOptions launchOpts = null;
int taskCount = tasks.size();
@@ -564,9 +574,8 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
}
// Launch the task
- ssp.startActivityFromRecents(
- mContext, toTask.key, toTask.title, launchOpts, INVALID_STACK_ID,
- null /* resultListener */);
+ ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(toTask.key, launchOpts,
+ null /* resultCallback */, null /* resultCallbackHandler */);
}
/**
@@ -575,61 +584,58 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
public void showRelativeAffiliatedTask(boolean showNextTask) {
SystemServicesProxy ssp = Recents.getSystemServices();
RecentsTaskLoader loader = Recents.getTaskLoader();
- RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
- loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
+ RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(mContext);
+ loader.preloadTasks(plan, -1);
TaskStack focusedStack = plan.getTaskStack();
// Return early if there are no tasks in the focused stack
if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
// Return early if there is no running task (can't determine affiliated tasks in this case)
- ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
+ ActivityManager.RunningTaskInfo runningTask =
+ ActivityManagerWrapper.getInstance().getRunningTask();
+ final int activityType = runningTask.configuration.windowConfiguration.getActivityType();
if (runningTask == null) return;
// Return early if the running task is in the home/recents stack (optimization)
- if (isHomeOrRecentsStack(runningTask.stackId)) return;
+ if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) return;
// Find the task in the recents list
- ArrayList<Task> tasks = focusedStack.getStackTasks();
+ ArrayList<Task> tasks = focusedStack.getTasks();
Task toTask = null;
ActivityOptions launchOpts = null;
int taskCount = tasks.size();
- int numAffiliatedTasks = 0;
for (int i = 0; i < taskCount; i++) {
Task task = tasks.get(i);
if (task.key.id == runningTask.id) {
- TaskGrouping group = task.group;
- Task.TaskKey toTaskKey;
if (showNextTask) {
- toTaskKey = group.getNextTaskInGroup(task);
- launchOpts = ActivityOptions.makeCustomAnimation(mContext,
- R.anim.recents_launch_next_affiliated_task_target,
- R.anim.recents_launch_next_affiliated_task_source);
+ if ((i + 1) < taskCount) {
+ toTask = tasks.get(i + 1);
+ launchOpts = ActivityOptions.makeCustomAnimation(mContext,
+ R.anim.recents_launch_next_affiliated_task_target,
+ R.anim.recents_launch_next_affiliated_task_source);
+ }
} else {
- toTaskKey = group.getPrevTaskInGroup(task);
- launchOpts = ActivityOptions.makeCustomAnimation(mContext,
- R.anim.recents_launch_prev_affiliated_task_target,
- R.anim.recents_launch_prev_affiliated_task_source);
- }
- if (toTaskKey != null) {
- toTask = focusedStack.findTaskWithId(toTaskKey.id);
+ if ((i - 1) >= 0) {
+ toTask = tasks.get(i - 1);
+ launchOpts = ActivityOptions.makeCustomAnimation(mContext,
+ R.anim.recents_launch_prev_affiliated_task_target,
+ R.anim.recents_launch_prev_affiliated_task_source);
+ }
}
- numAffiliatedTasks = group.getTaskCount();
break;
}
}
// Return early if there is no next task
if (toTask == null) {
- if (numAffiliatedTasks > 1) {
- if (showNextTask) {
- ssp.startInPlaceAnimationOnFrontMostApplication(
- ActivityOptions.makeCustomInPlaceAnimation(mContext,
- R.anim.recents_launch_next_affiliated_task_bounce));
- } else {
- ssp.startInPlaceAnimationOnFrontMostApplication(
- ActivityOptions.makeCustomInPlaceAnimation(mContext,
- R.anim.recents_launch_prev_affiliated_task_bounce));
- }
+ if (showNextTask) {
+ ssp.startInPlaceAnimationOnFrontMostApplication(
+ ActivityOptions.makeCustomInPlaceAnimation(mContext,
+ R.anim.recents_launch_next_affiliated_task_bounce));
+ } else {
+ ssp.startInPlaceAnimationOnFrontMostApplication(
+ ActivityOptions.makeCustomInPlaceAnimation(mContext,
+ R.anim.recents_launch_prev_affiliated_task_bounce));
}
return;
}
@@ -638,9 +644,8 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
MetricsLogger.count(mContext, "overview_affiliated_task_launch", 1);
// Launch the task
- ssp.startActivityFromRecents(
- mContext, toTask.key, toTask.title, launchOpts, INVALID_STACK_ID,
- null /* resultListener */);
+ ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(toTask.key, launchOpts,
+ null /* resultListener */, null /* resultCallbackHandler */);
}
public void showNextAffiliatedTask() {
@@ -655,21 +660,14 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
showRelativeAffiliatedTask(false);
}
- public void dockTopTask(int topTaskId, int dragMode,
- int stackCreateMode, Rect initialBounds) {
+ public void splitPrimaryTask(int taskId, int dragMode, int stackCreateMode,
+ Rect initialBounds) {
SystemServicesProxy ssp = Recents.getSystemServices();
// Make sure we inform DividerView before we actually start the activity so we can change
// the resize mode already.
- if (ssp.moveTaskToDockedStack(topTaskId, stackCreateMode, initialBounds)) {
+ if (ssp.setTaskWindowingModeSplitScreenPrimary(taskId, stackCreateMode, initialBounds)) {
EventBus.getDefault().send(new DockedTopTaskEvent(dragMode, initialBounds));
- showRecents(
- false /* triggeredFromAltTab */,
- dragMode == NavigationBarGestureHelper.DRAG_MODE_RECENTS,
- false /* animate */,
- true /* launchedWhileDockingTask*/,
- false /* fromHome */,
- DividerView.INVALID_RECENTS_GROW_TARGET);
}
}
@@ -740,7 +738,10 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
// However, the window bounds include the insets, so we need to subtract them here to make
// them identical.
if (ssp.hasDockedTask()) {
- windowRect.bottom -= systemInsets.bottom;
+ if (systemInsets.bottom < windowRect.height()) {
+ // Only apply inset if it isn't going to cause the rect height to go negative.
+ windowRect.bottom -= systemInsets.bottom;
+ }
systemInsets.bottom = 0;
}
calculateWindowStableInsets(systemInsets, windowRect, displayRect);
@@ -752,8 +753,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
stackLayout.getTaskStackBounds(displayRect, windowRect, systemInsets.top,
systemInsets.left, systemInsets.right, mTmpBounds);
stackLayout.reset();
- stackLayout.initialize(displayRect, windowRect, mTmpBounds,
- TaskStackLayoutAlgorithm.StackState.getStackStateForStack(stack));
+ stackLayout.initialize(displayRect, windowRect, mTmpBounds);
}
}
@@ -842,7 +842,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
launchOpts.runningTaskId = runningTaskId;
launchOpts.loadThumbnails = false;
launchOpts.onlyLoadForCache = true;
- Recents.getTaskLoader().loadTasks(mContext, sInstanceLoadPlan, launchOpts);
+ Recents.getTaskLoader().loadTasks(sInstanceLoadPlan, launchOpts);
}
/**
@@ -872,59 +872,29 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
getThumbnailTransitionActivityOptions(ActivityManager.RunningTaskInfo runningTask,
Rect windowOverrideRect) {
final boolean isLowRamDevice = Recents.getConfiguration().isLowRamDevice;
- if (runningTask != null && runningTask.stackId == FREEFORM_WORKSPACE_STACK_ID) {
- ArrayList<AppTransitionAnimationSpec> specs = new ArrayList<>();
- ArrayList<Task> tasks = mDummyStackView.getStack().getStackTasks();
- TaskStackLayoutAlgorithm stackLayout = mDummyStackView.getStackAlgorithm();
- TaskStackViewScroller stackScroller = mDummyStackView.getScroller();
-
- mDummyStackView.updateLayoutAlgorithm(true /* boundScroll */);
- mDummyStackView.updateToInitialState();
-
- for (int i = tasks.size() - 1; i >= 0; i--) {
- Task task = tasks.get(i);
- if (task.isFreeformTask()) {
- mTmpTransform = stackLayout.getStackTransformScreenCoordinates(task,
- stackScroller.getStackScroll(), mTmpTransform, null,
- windowOverrideRect);
- GraphicBuffer thumbnail = drawThumbnailTransitionBitmap(task, mTmpTransform);
- Rect toTaskRect = new Rect();
- mTmpTransform.rect.round(toTaskRect);
- specs.add(new AppTransitionAnimationSpec(task.key.id, thumbnail, toTaskRect));
- }
+
+ // Update the destination rect
+ Task toTask = new Task();
+ TaskViewTransform toTransform = getThumbnailTransitionTransform(mDummyStackView, toTask,
+ windowOverrideRect);
+
+ RectF toTaskRect = toTransform.rect;
+ AppTransitionAnimationSpecsFuture future = new AppTransitionAnimationSpecsFuture(mHandler) {
+ @Override
+ public List<AppTransitionAnimationSpecCompat> composeSpecs() {
+ Rect rect = new Rect();
+ toTaskRect.round(rect);
+ Bitmap thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform);
+ return Lists.newArrayList(new AppTransitionAnimationSpecCompat(toTask.key.id,
+ thumbnail, rect));
}
- AppTransitionAnimationSpec[] specsArray = new AppTransitionAnimationSpec[specs.size()];
- specs.toArray(specsArray);
-
- // For low end ram devices, wait for transition flag is reset when Recents entrance
- // animation is complete instead of when the transition animation starts
- return new Pair<>(ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
- specsArray, mHandler, isLowRamDevice ? null : mResetToggleFlagListener, this),
- null);
- } else {
- // Update the destination rect
- Task toTask = new Task();
- TaskViewTransform toTransform = getThumbnailTransitionTransform(mDummyStackView, toTask,
- windowOverrideRect);
-
- RectF toTaskRect = toTransform.rect;
- AppTransitionAnimationSpecsFuture future =
- new RecentsTransitionHelper(mContext).getAppTransitionFuture(
- () -> {
- Rect rect = new Rect();
- toTaskRect.round(rect);
- GraphicBuffer thumbnail = drawThumbnailTransitionBitmap(toTask,
- toTransform);
- return Lists.newArrayList(new AppTransitionAnimationSpec(
- toTask.key.id, thumbnail, rect));
- });
-
- // For low end ram devices, wait for transition flag is reset when Recents entrance
- // animation is complete instead of when the transition animation starts
- return new Pair<>(ActivityOptions.makeMultiThumbFutureAspectScaleAnimation(mContext,
- mHandler, future.getFuture(), isLowRamDevice ? null : mResetToggleFlagListener,
- false /* scaleUp */), future);
- }
+ };
+
+ // For low end ram devices, wait for transition flag is reset when Recents entrance
+ // animation is complete instead of when the transition animation starts
+ return new Pair<>(RecentsTransition.createAspectScaleAnimation(mContext, mHandler,
+ false /* scaleUp */, future, isLowRamDevice ? null : mResetToggleFlagListener),
+ future);
}
/**
@@ -939,7 +909,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
runningTaskOut.copyFrom(launchTask);
} else {
// If no task is specified or we can not find the task just use the front most one
- launchTask = stack.getStackFrontMostTask(true /* includeFreeform */);
+ launchTask = stack.getFrontMostTask();
runningTaskOut.copyFrom(launchTask);
}
@@ -954,7 +924,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
/**
* Draws the header of a task used for the window animation into a bitmap.
*/
- private GraphicBuffer drawThumbnailTransitionBitmap(Task toTask,
+ private Bitmap drawThumbnailTransitionBitmap(Task toTask,
TaskViewTransform toTransform) {
SystemServicesProxy ssp = Recents.getSystemServices();
int width = (int) toTransform.rect.width();
@@ -964,7 +934,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
boolean disabledInSafeMode = !toTask.isSystemApp && ssp.isInSafeMode();
mHeaderBar.onTaskViewSizeChanged(width, height);
if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
- return RecentsTransitionHelper.drawViewIntoGraphicBuffer(width, mTaskBarHeight,
+ return RecentsTransition.drawViewIntoHardwareBitmap(width, mTaskBarHeight,
null, 1f, 0xFFff0000);
} else {
// Workaround for b/27815919, reset the callback so that we do not trigger an
@@ -977,7 +947,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
disabledInSafeMode);
mHeaderBar.onTaskDataLoaded();
mHeaderBar.setDimAlpha(toTransform.dimAlpha);
- return RecentsTransitionHelper.drawViewIntoGraphicBuffer(width, mTaskBarHeight,
+ return RecentsTransition.drawViewIntoHardwareBitmap(width, mTaskBarHeight,
mHeaderBar, 1f, 0);
}
}
@@ -986,18 +956,32 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
}
/**
- * Shows the recents activity
+ * Shows the recents activity after dismissing the keyguard if visible
*/
- protected void startRecentsActivity(ActivityManager.RunningTaskInfo runningTask,
+ protected void startRecentsActivityAndDismissKeyguardIfNeeded(
+ final ActivityManager.RunningTaskInfo runningTask, final boolean isHomeStackVisible,
+ final boolean animate, final int growTarget) {
+ // Preload only if device for current user is unlocked
+ final StatusBar statusBar = getStatusBar();
+ if (statusBar != null && statusBar.isKeyguardShowing()) {
+ statusBar.executeRunnableDismissingKeyguard(() -> {
+ // Flush trustmanager before checking device locked per user when preloading
+ mTrustManager.reportKeyguardShowingChanged();
+ mHandler.post(() -> startRecentsActivity(runningTask, isHomeStackVisible,
+ animate, growTarget));
+ }, null, true /* dismissShade */, false /* afterKeyguardGone */,
+ true /* deferred */);
+ } else {
+ startRecentsActivity(runningTask, isHomeStackVisible, animate, growTarget);
+ }
+ }
+
+ private void startRecentsActivity(ActivityManager.RunningTaskInfo runningTask,
boolean isHomeStackVisible, boolean animate, int growTarget) {
RecentsTaskLoader loader = Recents.getTaskLoader();
RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
- SystemServicesProxy ssp = Recents.getSystemServices();
- boolean isBlacklisted = (runningTask != null)
- ? ssp.isBlackListedActivity(runningTask.baseActivity.getClassName())
- : false;
- int runningTaskId = !mLaunchedWhileDocking && !isBlacklisted && (runningTask != null)
+ int runningTaskId = !mLaunchedWhileDocking && (runningTask != null)
? runningTask.id
: -1;
@@ -1006,10 +990,10 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
// the stacks might have changed.
if (mLaunchedWhileDocking || mTriggeredFromAltTab || sInstanceLoadPlan == null) {
// Create a new load plan if preloadRecents() was never triggered
- sInstanceLoadPlan = loader.createLoadPlan(mContext);
+ sInstanceLoadPlan = new RecentsTaskLoadPlan(mContext);
}
if (mLaunchedWhileDocking || mTriggeredFromAltTab || !sInstanceLoadPlan.hasTasks()) {
- loader.preloadTasks(sInstanceLoadPlan, runningTaskId, !isHomeStackVisible);
+ loader.preloadTasks(sInstanceLoadPlan, runningTaskId);
}
TaskStack stack = sInstanceLoadPlan.getTaskStack();
@@ -1020,7 +1004,6 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
// Update the launch state that we need in updateHeaderBarLayout()
launchState.launchedFromHome = !useThumbnailTransition && !mLaunchedWhileDocking;
launchState.launchedFromApp = useThumbnailTransition || mLaunchedWhileDocking;
- launchState.launchedFromBlacklistedApp = launchState.launchedFromApp && isBlacklisted;
launchState.launchedFromPipApp = false;
launchState.launchedWithNextPipApp =
stack.isNextLaunchTargetPip(RecentsImpl.getLastPipTime());
@@ -1056,9 +1039,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
}
Pair<ActivityOptions, AppTransitionAnimationSpecsFuture> pair;
- if (isBlacklisted) {
- pair = new Pair<>(getUnknownTransitionActivityOptions(), null);
- } else if (useThumbnailTransition) {
+ if (useThumbnailTransition) {
// Try starting with a thumbnail transition
pair = getThumbnailTransitionActivityOptions(runningTask, windowOverrideRect);
} else {
@@ -1084,6 +1065,10 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
return result;
}
+ private StatusBar getStatusBar() {
+ return ((SystemUIApplication) mContext).getComponent(StatusBar.class);
+ }
+
/**
* Starts the recents activity.
*/
@@ -1099,7 +1084,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
Recents.getSystemServices().startActivityAsUserAsync(intent, opts);
EventBus.getDefault().send(new RecentsActivityStartingEvent());
if (future != null) {
- future.precacheSpecs();
+ future.composeSpecsSynchronous();
}
});
EventBus.getDefault().send(hideMenuEvent);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImplProxy.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImplProxy.java
index ff9e89e9e00b..beec4b395e9c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImplProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImplProxy.java
@@ -58,15 +58,12 @@ public class RecentsImplProxy extends IRecentsNonSystemUserCallbacks.Stub {
@Override
public void showRecents(boolean triggeredFromAltTab, boolean draggingInRecents, boolean animate,
- boolean reloadTasks, boolean fromHome, int growTarget)
- throws RemoteException {
+ int growTarget) throws RemoteException {
SomeArgs args = SomeArgs.obtain();
args.argi1 = triggeredFromAltTab ? 1 : 0;
args.argi2 = draggingInRecents ? 1 : 0;
args.argi3 = animate ? 1 : 0;
- args.argi4 = reloadTasks ? 1 : 0;
- args.argi5 = fromHome ? 1 : 0;
- args.argi6 = growTarget;
+ args.argi4 = growTarget;
mHandler.sendMessage(mHandler.obtainMessage(MSG_SHOW_RECENTS, args));
}
@@ -90,7 +87,7 @@ public class RecentsImplProxy extends IRecentsNonSystemUserCallbacks.Stub {
}
@Override
- public void dockTopTask(int topTaskId, int dragMode, int stackCreateMode,
+ public void splitPrimaryTask(int topTaskId, int dragMode, int stackCreateMode,
Rect initialBounds) throws RemoteException {
SomeArgs args = SomeArgs.obtain();
args.argi1 = topTaskId;
@@ -130,7 +127,7 @@ public class RecentsImplProxy extends IRecentsNonSystemUserCallbacks.Stub {
case MSG_SHOW_RECENTS:
args = (SomeArgs) msg.obj;
mImpl.showRecents(args.argi1 != 0, args.argi2 != 0, args.argi3 != 0,
- args.argi4 != 0, args.argi5 != 0, args.argi6);
+ args.argi4);
break;
case MSG_HIDE_RECENTS:
mImpl.hideRecents(msg.arg1 != 0, msg.arg2 != 0);
@@ -144,7 +141,7 @@ public class RecentsImplProxy extends IRecentsNonSystemUserCallbacks.Stub {
break;
case MSG_DOCK_TOP_TASK:
args = (SomeArgs) msg.obj;
- mImpl.dockTopTask(args.argi1, args.argi2, args.argi3 = 0,
+ mImpl.splitPrimaryTask(args.argi1, args.argi2, args.argi3 = 0,
(Rect) args.arg1);
break;
case MSG_ON_DRAGGING_IN_RECENTS:
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
new file mode 100644
index 000000000000..0ff8b085f035
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+
+import android.annotation.TargetApi;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.RippleDrawable;
+import android.os.Build;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.systemui.OverviewProxyService;
+import com.android.systemui.Prefs;
+import com.android.systemui.R;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+
+/**
+ * Shows onboarding for the new recents interaction in P (codenamed quickstep).
+ */
+@TargetApi(Build.VERSION_CODES.P)
+public class RecentsOnboarding {
+
+ private static final String TAG = "RecentsOnboarding";
+ private static final boolean RESET_PREFS_FOR_DEBUG = false;
+ private static final long SHOW_DELAY_MS = 500;
+ private static final long SHOW_HIDE_DURATION_MS = 300;
+ // Don't show the onboarding until the user has launched this number of apps.
+ private static final int SHOW_ON_APP_LAUNCH = 2;
+
+ private final Context mContext;
+ private final WindowManager mWindowManager;
+ private final OverviewProxyService mOverviewProxyService;
+ private final View mLayout;
+ private final TextView mTextView;
+ private final ImageView mDismissView;
+ private final ColorDrawable mBackgroundDrawable;
+ private final int mDarkBackgroundColor;
+ private final int mLightBackgroundColor;
+ private final int mDarkContentColor;
+ private final int mLightContentColor;
+ private final RippleDrawable mDarkRipple;
+ private final RippleDrawable mLightRipple;
+
+ private boolean mTaskListenerRegistered;
+ private boolean mLayoutAttachedToWindow;
+ private boolean mBackgroundIsLight;
+
+ private final SysUiTaskStackChangeListener mTaskListener = new SysUiTaskStackChangeListener() {
+ @Override
+ public void onTaskStackChanged() {
+ ActivityManager.RunningTaskInfo info = ActivityManagerWrapper.getInstance()
+ .getRunningTask(ACTIVITY_TYPE_UNDEFINED /* ignoreActivityType */);
+ int activityType = info.configuration.windowConfiguration.getActivityType();
+ int numAppsLaunched = Prefs.getInt(mContext, Prefs.Key.NUM_APPS_LAUNCHED, 0);
+ if (activityType == ACTIVITY_TYPE_STANDARD) {
+ numAppsLaunched++;
+ if (numAppsLaunched >= SHOW_ON_APP_LAUNCH) {
+ show();
+ } else {
+ Prefs.putInt(mContext, Prefs.Key.NUM_APPS_LAUNCHED, numAppsLaunched);
+ }
+ } else {
+ hide(false);
+ }
+ }
+ };
+
+ private final View.OnAttachStateChangeListener mOnAttachStateChangeListener
+ = new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View view) {
+ if (view == mLayout) {
+ mLayoutAttachedToWindow = true;
+ }
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View view) {
+ if (view == mLayout) {
+ mLayoutAttachedToWindow = false;
+ }
+ }
+ };
+
+ public RecentsOnboarding(Context context, OverviewProxyService overviewProxyService) {
+ mContext = context;
+ mOverviewProxyService = overviewProxyService;
+ final Resources res = context.getResources();
+ mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+ mLayout = LayoutInflater.from(mContext).inflate(R.layout.recents_onboarding, null);
+ mTextView = mLayout.findViewById(R.id.onboarding_text);
+ mDismissView = mLayout.findViewById(R.id.dismiss);
+ mDarkBackgroundColor = res.getColor(android.R.color.background_dark);
+ mLightBackgroundColor = res.getColor(android.R.color.background_light);
+ mDarkContentColor = res.getColor(R.color.primary_text_default_material_light);
+ mLightContentColor = res.getColor(R.color.primary_text_default_material_dark);
+ mDarkRipple = new RippleDrawable(res.getColorStateList(R.color.ripple_material_light),
+ null, null);
+ mLightRipple = new RippleDrawable(res.getColorStateList(R.color.ripple_material_dark),
+ null, null);
+ mBackgroundDrawable = new ColorDrawable(mDarkBackgroundColor);
+
+ mLayout.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
+ mLayout.setBackground(mBackgroundDrawable);
+ mDismissView.setOnClickListener(v -> hide(true));
+
+ if (RESET_PREFS_FOR_DEBUG) {
+ Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_RECENTS_ONBOARDING, false);
+ Prefs.putInt(mContext, Prefs.Key.NUM_APPS_LAUNCHED, 0);
+ }
+ }
+
+ public void onConnectedToLauncher() {
+ boolean alreadySeenRecentsOnboarding = Prefs.getBoolean(mContext,
+ Prefs.Key.HAS_SEEN_RECENTS_ONBOARDING, false);
+ if (!mTaskListenerRegistered && !alreadySeenRecentsOnboarding) {
+ ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskListener);
+ mTaskListenerRegistered = true;
+ }
+ }
+
+ public void onRecentsAnimationStarted() {
+ boolean alreadySeenRecentsOnboarding = Prefs.getBoolean(mContext,
+ Prefs.Key.HAS_SEEN_RECENTS_ONBOARDING, false);
+ if (!alreadySeenRecentsOnboarding) {
+ Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_RECENTS_ONBOARDING, true);
+ onDisconnectedFromLauncher();
+ }
+ }
+
+ public void onDisconnectedFromLauncher() {
+ if (mTaskListenerRegistered) {
+ ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskListener);
+ mTaskListenerRegistered = false;
+ }
+ hide(false);
+ }
+
+ public void onConfigurationChanged(Configuration newConfiguration) {
+ if (newConfiguration.orientation != Configuration.ORIENTATION_PORTRAIT) {
+ hide(false);
+ }
+ }
+
+ public void show() {
+ CharSequence onboardingText = mOverviewProxyService.getOnboardingText();
+ if (TextUtils.isEmpty(onboardingText)) {
+ Log.w(TAG, "Unable to get onboarding text");
+ return;
+ }
+ mTextView.setText(onboardingText);
+ // Only show in portrait.
+ int orientation = mContext.getResources().getConfiguration().orientation;
+ if (!mLayoutAttachedToWindow && orientation == Configuration.ORIENTATION_PORTRAIT) {
+ mLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+ mWindowManager.addView(mLayout, getWindowLayoutParams());
+ int layoutHeight = mLayout.getHeight();
+ if (layoutHeight == 0) {
+ mLayout.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
+ layoutHeight = mLayout.getMeasuredHeight();
+ }
+ mLayout.setTranslationY(layoutHeight);
+ mLayout.setAlpha(0);
+ mLayout.animate()
+ .translationY(0)
+ .alpha(1f)
+ .withLayer()
+ .setStartDelay(SHOW_DELAY_MS)
+ .setDuration(SHOW_HIDE_DURATION_MS)
+ .setInterpolator(new DecelerateInterpolator())
+ .start();
+ }
+ }
+
+ public void hide(boolean animate) {
+ if (mLayoutAttachedToWindow) {
+ if (animate) {
+ mLayout.animate()
+ .translationY(mLayout.getHeight())
+ .alpha(0f)
+ .withLayer()
+ .setDuration(SHOW_HIDE_DURATION_MS)
+ .setInterpolator(new AccelerateInterpolator())
+ .withEndAction(() -> mWindowManager.removeView(mLayout))
+ .start();
+ } else {
+ mWindowManager.removeView(mLayout);
+ }
+ }
+ }
+
+ public void setContentDarkIntensity(float contentDarkIntensity) {
+ boolean backgroundIsLight = contentDarkIntensity > 0.5f;
+ if (backgroundIsLight != mBackgroundIsLight) {
+ mBackgroundIsLight = backgroundIsLight;
+ mBackgroundDrawable.setColor(mBackgroundIsLight
+ ? mLightBackgroundColor : mDarkBackgroundColor);
+ int contentColor = mBackgroundIsLight ? mDarkContentColor : mLightContentColor;
+ mTextView.setTextColor(contentColor);
+ mTextView.getCompoundDrawables()[3].setColorFilter(contentColor,
+ PorterDuff.Mode.SRC_IN);
+ mDismissView.setColorFilter(contentColor);
+ mDismissView.setBackground(mBackgroundIsLight ? mDarkRipple : mLightRipple);
+ }
+ }
+
+ private WindowManager.LayoutParams getWindowLayoutParams() {
+ int flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+ final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG,
+ flags,
+ PixelFormat.TRANSLUCENT);
+ lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ lp.setTitle("RecentsOnboarding");
+ lp.gravity = Gravity.BOTTOM;
+ return lp;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUser.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUser.java
index 1285626015d2..ff1f7dc5a2a8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUser.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUser.java
@@ -27,6 +27,7 @@ import android.util.SparseArray;
import com.android.systemui.EventLogConstants;
import com.android.systemui.EventLogTags;
import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent;
import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
import com.android.systemui.recents.events.component.SetWaitingForTransitionStartEvent;
@@ -108,6 +109,11 @@ public class RecentsSystemUser extends IRecentsSystemUserCallbacks.Stub {
}
@Override
+ public void sendDockedFirstAnimationFrameEvent() throws RemoteException {
+ EventBus.getDefault().post(new DockedFirstAnimationFrameEvent());
+ }
+
+ @Override
public void setWaitingForTransitionStartEvent(boolean waitingForTransitionStart) {
EventBus.getDefault().post(new SetWaitingForTransitionStartEvent(
waitingForTransitionStart));
diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
index 316ad1661898..57f7818eae58 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
@@ -23,15 +23,12 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.res.Configuration;
import android.graphics.PixelFormat;
-import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.os.Binder;
import android.os.RemoteException;
import android.util.DisplayMetrics;
import android.view.Gravity;
-import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
@@ -43,6 +40,9 @@ import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.systemui.R;
+import com.android.systemui.SysUiServiceProvider;
+import com.android.systemui.statusbar.phone.NavigationBarView;
+import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.util.leak.RotationUtils;
import java.util.ArrayList;
@@ -233,11 +233,30 @@ public class ScreenPinningRequest implements View.OnClickListener {
.setVisibility(View.INVISIBLE);
}
+ StatusBar statusBar = SysUiServiceProvider.getComponent(mContext, StatusBar.class);
+ NavigationBarView navigationBarView = statusBar.getNavigationBarView();
+ final boolean recentsVisible = navigationBarView != null
+ && navigationBarView.isRecentsButtonVisible();
boolean touchExplorationEnabled = mAccessibilityService.isTouchExplorationEnabled();
+ int descriptionStringResId;
+ if (recentsVisible) {
+ mLayout.findViewById(R.id.screen_pinning_recents_group).setVisibility(VISIBLE);
+ mLayout.findViewById(R.id.screen_pinning_home_bg_light).setVisibility(INVISIBLE);
+ mLayout.findViewById(R.id.screen_pinning_home_bg).setVisibility(INVISIBLE);
+ descriptionStringResId = touchExplorationEnabled
+ ? R.string.screen_pinning_description_accessible
+ : R.string.screen_pinning_description;
+ } else {
+ mLayout.findViewById(R.id.screen_pinning_recents_group).setVisibility(INVISIBLE);
+ mLayout.findViewById(R.id.screen_pinning_home_bg_light).setVisibility(VISIBLE);
+ mLayout.findViewById(R.id.screen_pinning_home_bg).setVisibility(VISIBLE);
+ descriptionStringResId = touchExplorationEnabled
+ ? R.string.screen_pinning_description_recents_invisible_accessible
+ : R.string.screen_pinning_description_recents_invisible;
+ }
+
((TextView) mLayout.findViewById(R.id.screen_pinning_description))
- .setText(touchExplorationEnabled
- ? R.string.screen_pinning_description_accessible
- : R.string.screen_pinning_description);
+ .setText(descriptionStringResId);
final int backBgVisibility = touchExplorationEnabled ? View.INVISIBLE : View.VISIBLE;
mLayout.findViewById(R.id.screen_pinning_back_bg).setVisibility(backBgVisibility);
mLayout.findViewById(R.id.screen_pinning_back_bg_light).setVisibility(backBgVisibility);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/CancelEnterRecentsWindowAnimationEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/CancelEnterRecentsWindowAnimationEvent.java
index 7604de1d05d0..fec34e3cd23d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/CancelEnterRecentsWindowAnimationEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/CancelEnterRecentsWindowAnimationEvent.java
@@ -17,7 +17,7 @@
package com.android.systemui.recents.events.activity;
import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task;
/**
* This is sent when we want to cancel the enter-recents window animation for the launch task.
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/DebugFlagsChangedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/DebugFlagsChangedEvent.java
deleted file mode 100644
index fe3bf26334b9..000000000000
--- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/DebugFlagsChangedEvent.java
+++ /dev/null
@@ -1,26 +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.recents.events.activity;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * This is sent when the SystemUI tuner changes a flag.
- */
-public class DebugFlagsChangedEvent extends EventBus.Event {
- // Simple event
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/IterateRecentsEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/IterateRecentsEvent.java
deleted file mode 100644
index f7b2706b9c57..000000000000
--- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/IterateRecentsEvent.java
+++ /dev/null
@@ -1,27 +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.recents.events.activity;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * This is sent when the user taps on the Overview button to iterate to the next item in the
- * Recents list.
- */
-public class IterateRecentsEvent extends EventBus.Event {
- // Simple event
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/LaunchTaskEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/LaunchTaskEvent.java
index 3db106e7200f..2409f39d3760 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/LaunchTaskEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/LaunchTaskEvent.java
@@ -16,10 +16,13 @@
package com.android.systemui.recents.events.activity;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
import android.graphics.Rect;
import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.recents.views.TaskView;
/**
@@ -30,15 +33,23 @@ public class LaunchTaskEvent extends EventBus.Event {
public final TaskView taskView;
public final Task task;
public final Rect targetTaskBounds;
- public final int targetTaskStack;
+ public final int targetWindowingMode;
+ public final int targetActivityType;
public final boolean screenPinningRequested;
- public LaunchTaskEvent(TaskView taskView, Task task, Rect targetTaskBounds, int targetTaskStack,
+ public LaunchTaskEvent(TaskView taskView, Task task, Rect targetTaskBounds,
boolean screenPinningRequested) {
+ this(taskView, task, targetTaskBounds, screenPinningRequested,
+ WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_UNDEFINED);
+ }
+
+ public LaunchTaskEvent(TaskView taskView, Task task, Rect targetTaskBounds,
+ boolean screenPinningRequested, int windowingMode, int activityType) {
this.taskView = taskView;
this.task = task;
this.targetTaskBounds = targetTaskBounds;
- this.targetTaskStack = targetTaskStack;
+ this.targetWindowingMode = windowingMode;
+ this.targetActivityType = activityType;
this.screenPinningRequested = screenPinningRequested;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/MultiWindowStateChangedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/MultiWindowStateChangedEvent.java
index 64eeafa1ae17..e4972b1fd7f4 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/MultiWindowStateChangedEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/MultiWindowStateChangedEvent.java
@@ -17,7 +17,7 @@
package com.android.systemui.recents.events.activity;
import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.shared.recents.model.TaskStack;
/**
* This is sent by the activity whenever the multi-window state has changed.
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/PackagesChangedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/PackagesChangedEvent.java
index 3b68574c2830..47670e03c6a1 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/PackagesChangedEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/PackagesChangedEvent.java
@@ -17,22 +17,20 @@
package com.android.systemui.recents.events.activity;
import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.RecentsPackageMonitor;
import com.android.systemui.recents.views.TaskStackView;
+import com.android.systemui.recents.RecentsActivity;
/**
- * This event is sent by {@link RecentsPackageMonitor} when a package on the the system changes.
+ * This event is sent by {@link RecentsActivity} when a package on the the system changes.
* {@link TaskStackView}s listen for this event, and remove the tasks associated with the removed
* packages.
*/
public class PackagesChangedEvent extends EventBus.Event {
- public final RecentsPackageMonitor monitor;
public final String packageName;
public final int userId;
- public PackagesChangedEvent(RecentsPackageMonitor monitor, String packageName, int userId) {
- this.monitor = monitor;
+ public PackagesChangedEvent(String packageName, int userId) {
this.packageName = packageName;
this.userId = userId;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/TaskStackUpdatedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/TaskStackUpdatedEvent.java
index 0d614e8c675c..51d02b5b0018 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/TaskStackUpdatedEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/TaskStackUpdatedEvent.java
@@ -17,7 +17,7 @@
package com.android.systemui.recents.events.activity;
import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.shared.recents.model.TaskStack;
/**
* This is sent by the activity whenever the task stach has changed.
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/component/ExpandPipEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/component/ExpandPipEvent.java
index 8fe4975f1a2a..37266f6ff39f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/component/ExpandPipEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/component/ExpandPipEvent.java
@@ -22,5 +22,4 @@ import com.android.systemui.recents.events.EventBus;
* This is sent when the PiP should be expanded due to being relaunched.
*/
public class ExpandPipEvent extends EventBus.Event {
- public final boolean clearThumbnailWindows = true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/DeleteTaskDataEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/DeleteTaskDataEvent.java
index 4ed027084def..b52e83b81649 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/DeleteTaskDataEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/DeleteTaskDataEvent.java
@@ -17,7 +17,7 @@
package com.android.systemui.recents.events.ui;
import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task;
/**
* This is sent when the data associated with a given {@link Task} should be deleted from the
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/ShowApplicationInfoEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/ShowApplicationInfoEvent.java
index 40c30b884eae..da19384ae93a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/ShowApplicationInfoEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/ShowApplicationInfoEvent.java
@@ -17,7 +17,7 @@
package com.android.systemui.recents.events.ui;
import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task;
/**
* This is sent when a user wants to show the application info for a {@link Task}.
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/TaskSnapshotChangedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/TaskSnapshotChangedEvent.java
index e0ed7a9e7e35..f08292801b62 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/TaskSnapshotChangedEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/TaskSnapshotChangedEvent.java
@@ -17,7 +17,7 @@
package com.android.systemui.recents.events.ui;
import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.ThumbnailData;
+import com.android.systemui.shared.recents.model.ThumbnailData;
/**
* Sent when a task snapshot has changed.
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/TaskViewDismissedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/TaskViewDismissedEvent.java
index 0628c5015153..881a64af5b0f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/TaskViewDismissedEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/TaskViewDismissedEvent.java
@@ -17,8 +17,8 @@
package com.android.systemui.recents.events.ui;
import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.views.AnimationProps;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.utilities.AnimationProps;
import com.android.systemui.recents.views.TaskView;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/UpdateFreeformTaskViewVisibilityEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/UpdateFreeformTaskViewVisibilityEvent.java
deleted file mode 100644
index b42da9c7a793..000000000000
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/UpdateFreeformTaskViewVisibilityEvent.java
+++ /dev/null
@@ -1,31 +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.recents.events.ui;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * This is sent to update the visibility of all visible freeform task views.
- */
-public class UpdateFreeformTaskViewVisibilityEvent extends EventBus.Event {
-
- public final boolean visible;
-
- public UpdateFreeformTaskViewVisibilityEvent(boolean visible) {
- this.visible = visible;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragDropTargetChangedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragDropTargetChangedEvent.java
index 216be6121f8d..cf61b1ef7637 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragDropTargetChangedEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragDropTargetChangedEvent.java
@@ -17,7 +17,7 @@
package com.android.systemui.recents.events.ui.dragndrop;
import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.recents.views.DropTarget;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndCancelledEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndCancelledEvent.java
index edd799597ea6..297afc53c557 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndCancelledEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndCancelledEvent.java
@@ -17,9 +17,8 @@
package com.android.systemui.recents.events.ui.dragndrop;
import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.TaskStack;
-import com.android.systemui.recents.views.DropTarget;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.TaskStack;
import com.android.systemui.recents.views.TaskView;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java
index 73c282fe8816..73cbde998319 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java
@@ -17,7 +17,7 @@
package com.android.systemui.recents.events.ui.dragndrop;
import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.recents.views.DropTarget;
import com.android.systemui.recents.views.TaskView;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java
index e57fa2d86a66..021be77bcc8b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java
@@ -19,7 +19,7 @@ package com.android.systemui.recents.events.ui.dragndrop;
import android.graphics.Point;
import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.recents.views.TaskView;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartInitializeDropTargetsEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartInitializeDropTargetsEvent.java
index 7030729d2a04..64ba5748bb89 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartInitializeDropTargetsEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartInitializeDropTargetsEvent.java
@@ -17,7 +17,7 @@
package com.android.systemui.recents.events.ui.dragndrop;
import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.recents.views.RecentsViewTouchHandler;
import com.android.systemui.recents.views.TaskView;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java
index a1e4957a2719..171ab5e8bcca 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java
@@ -22,10 +22,5 @@ import com.android.systemui.recents.events.EventBus;
* Focuses the next task view in the stack.
*/
public class FocusNextTaskViewEvent extends EventBus.Event {
-
- public final int timerIndicatorDuration;
-
- public FocusNextTaskViewEvent(int timerIndicatorDuration) {
- this.timerIndicatorDuration = timerIndicatorDuration;
- }
+ // Simple event
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/RectFEvaluator.java b/packages/SystemUI/src/com/android/systemui/recents/misc/RectFEvaluator.java
deleted file mode 100644
index 72511de9ec80..000000000000
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/RectFEvaluator.java
+++ /dev/null
@@ -1,52 +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.recents.misc;
-
-import android.animation.TypeEvaluator;
-import android.graphics.RectF;
-
-/**
- * This evaluator can be used to perform type interpolation between <code>RectF</code> values.
- */
-public class RectFEvaluator implements TypeEvaluator<RectF> {
-
- private RectF mRect = new RectF();
-
- /**
- * This function returns the result of linearly interpolating the start and
- * end Rect values, with <code>fraction</code> representing the proportion
- * between the start and end values. The calculation is a simple parametric
- * calculation on each of the separate components in the Rect objects
- * (left, top, right, and bottom).
- *
- * <p>The object returned will be the <code>reuseRect</code> passed into the constructor.</p>
- *
- * @param fraction The fraction from the starting to the ending values
- * @param startValue The start Rect
- * @param endValue The end Rect
- * @return A linear interpolation between the start and end values, given the
- * <code>fraction</code> parameter.
- */
- @Override
- public RectF evaluate(float fraction, RectF startValue, RectF endValue) {
- float left = startValue.left + ((endValue.left - startValue.left) * fraction);
- float top = startValue.top + ((endValue.top - startValue.top) * fraction);
- float right = startValue.right + ((endValue.right - startValue.right) * fraction);
- float bottom = startValue.bottom + ((endValue.bottom - startValue.bottom) * fraction);
- mRect.set(left, top, right, bottom);
- return mRect;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/NamedCounter.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SysUiTaskStackChangeListener.java
index ec3c39cc4fbc..5d7f1ba5eaf4 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/NamedCounter.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SysUiTaskStackChangeListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -16,24 +16,20 @@
package com.android.systemui.recents.misc;
-/**
- * Used to generate successive incremented names.
- */
-public class NamedCounter {
+import android.content.Context;
- int mCount;
- String mPrefix = "";
- String mSuffix = "";
+import com.android.systemui.shared.system.TaskStackChangeListener;
- public NamedCounter(String prefix, String suffix) {
- mPrefix = prefix;
- mSuffix = suffix;
- }
+/**
+ * An implementation of {@link TaskStackChangeListener}.
+ */
+public abstract class SysUiTaskStackChangeListener extends TaskStackChangeListener {
- /** Returns the next name. */
- public String nextName() {
- String name = mPrefix + mCount + mSuffix;
- mCount++;
- return name;
+ /**
+ * Checks that the current user matches the user's SystemUI process.
+ */
+ protected final boolean checkCurrentUserId(Context context, boolean debug) {
+ int currentUserId = SystemServicesProxy.getInstance(context).getCurrentUser();
+ return checkCurrentUserId(currentUserId, debug);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index c9ef43e3f372..93fd34aa0519 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -16,66 +16,48 @@
package com.android.systemui.recents.misc;
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.HOME_STACK_ID;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-import static android.app.ActivityManager.StackId.RECENTS_STACK_ID;
-import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManager.StackInfo;
-import android.app.ActivityManager.TaskSnapshot;
import android.app.ActivityOptions;
import android.app.AppGlobals;
import android.app.IActivityManager;
-import android.app.KeyguardManager;
+import android.app.WindowConfiguration;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
-import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.IRemoteCallback;
-import android.os.Message;
-import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
-import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
-import android.provider.Settings.Secure;
import android.service.dreams.DreamService;
import android.service.dreams.IDreamManager;
-import android.util.ArraySet;
-import android.util.IconDrawableFactory;
import android.util.Log;
import android.util.MutableBoolean;
import android.view.Display;
-import android.view.IAppTransitionAnimationSpecsFuture;
import android.view.IDockedStackListener;
import android.view.IWindowManager;
import android.view.WindowManager;
@@ -86,23 +68,12 @@ import android.view.accessibility.AccessibilityManager;
import com.android.internal.app.AssistUtils;
import com.android.internal.os.BackgroundThread;
import com.android.systemui.Dependency;
-import com.android.systemui.R;
import com.android.systemui.UiOffloadThread;
-import com.android.systemui.pip.tv.PipMenuActivity;
import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.RecentsDebugFlags;
import com.android.systemui.recents.RecentsImpl;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.ThumbnailData;
import com.android.systemui.statusbar.policy.UserInfoController;
-import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
import java.util.List;
-import java.util.Random;
/**
* Acts as a shim around the real system services that we need to access data from, and provides
@@ -118,42 +89,30 @@ public class SystemServicesProxy {
sBitmapOptions.inPreferredConfig = Bitmap.Config.RGB_565;
}
- final static List<String> sRecentsBlacklist;
- static {
- sRecentsBlacklist = new ArrayList<>();
- sRecentsBlacklist.add(PipMenuActivity.class.getName());
- }
-
private static SystemServicesProxy sSystemServicesProxy;
AccessibilityManager mAccm;
ActivityManager mAm;
IActivityManager mIam;
PackageManager mPm;
- IconDrawableFactory mDrawableFactory;
IPackageManager mIpm;
private final IDreamManager mDreamManager;
private final Context mContext;
AssistUtils mAssistUtils;
WindowManager mWm;
IWindowManager mIwm;
- KeyguardManager mKgm;
UserManager mUm;
Display mDisplay;
String mRecentsPackage;
- ComponentName mAssistComponent;
private int mCurrentUserId;
boolean mIsSafeMode;
- boolean mHasFreeformWorkspaceSupport;
- Bitmap mDummyIcon;
int mDummyThumbnailWidth;
int mDummyThumbnailHeight;
Paint mBgProtectionPaint;
Canvas mBgProtectionCanvas;
- private final Handler mHandler = new H();
private final Runnable mGcRunnable = new Runnable() {
@Override
public void run() {
@@ -164,143 +123,10 @@ public class SystemServicesProxy {
private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
- /**
- * An abstract class to track task stack changes.
- * Classes should implement this instead of {@link android.app.ITaskStackListener}
- * to reduce IPC calls from system services. These callbacks will be called on the main thread.
- */
- public abstract static class TaskStackListener {
- /**
- * NOTE: This call is made of the thread that the binder call comes in on.
- */
- public void onTaskStackChangedBackground() { }
- public void onTaskStackChanged() { }
- public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) { }
- public void onActivityPinned(String packageName, int userId, int taskId) { }
- public void onActivityUnpinned() { }
- public void onPinnedActivityRestartAttempt(boolean clearedTask) { }
- public void onPinnedStackAnimationStarted() { }
- public void onPinnedStackAnimationEnded() { }
- public void onActivityForcedResizable(String packageName, int taskId, int reason) { }
- public void onActivityDismissingDockedStack() { }
- public void onActivityLaunchOnSecondaryDisplayFailed() { }
- public void onTaskProfileLocked(int taskId, int userId) { }
-
- /**
- * Checks that the current user matches the user's SystemUI process. Since
- * {@link android.app.ITaskStackListener} is not multi-user aware, handlers of
- * TaskStackListener should make this call to verify that we don't act on events from other
- * user's processes.
- */
- protected final boolean checkCurrentUserId(Context context, boolean debug) {
- int processUserId = UserHandle.myUserId();
- int currentUserId = SystemServicesProxy.getInstance(context).getCurrentUser();
- if (processUserId != currentUserId) {
- if (debug) {
- Log.d(TAG, "UID mismatch. SystemUI is running uid=" + processUserId
- + " and the current user is uid=" + currentUserId);
- }
- return false;
- }
- return true;
- }
- }
-
- /**
- * Implementation of {@link android.app.ITaskStackListener} to listen task stack changes from
- * ActivityManagerService.
- * This simply passes callbacks to listeners through {@link H}.
- * */
- private android.app.TaskStackListener mTaskStackListener = new android.app.TaskStackListener() {
-
- private final List<SystemServicesProxy.TaskStackListener> mTmpListeners = new ArrayList<>();
-
- @Override
- public void onTaskStackChanged() throws RemoteException {
- // Call the task changed callback for the non-ui thread listeners first
- synchronized (mTaskStackListeners) {
- mTmpListeners.clear();
- mTmpListeners.addAll(mTaskStackListeners);
- }
- for (int i = mTmpListeners.size() - 1; i >= 0; i--) {
- mTmpListeners.get(i).onTaskStackChangedBackground();
- }
-
- mHandler.removeMessages(H.ON_TASK_STACK_CHANGED);
- mHandler.sendEmptyMessage(H.ON_TASK_STACK_CHANGED);
- }
-
- @Override
- public void onActivityPinned(String packageName, int userId, int taskId)
- throws RemoteException {
- mHandler.removeMessages(H.ON_ACTIVITY_PINNED);
- mHandler.obtainMessage(H.ON_ACTIVITY_PINNED, userId, taskId, packageName).sendToTarget();
- }
-
- @Override
- public void onActivityUnpinned() throws RemoteException {
- mHandler.removeMessages(H.ON_ACTIVITY_UNPINNED);
- mHandler.sendEmptyMessage(H.ON_ACTIVITY_UNPINNED);
- }
-
- @Override
- public void onPinnedActivityRestartAttempt(boolean clearedTask)
- throws RemoteException{
- mHandler.removeMessages(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT);
- mHandler.obtainMessage(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT, clearedTask ? 1 : 0, 0)
- .sendToTarget();
- }
-
- @Override
- public void onPinnedStackAnimationStarted() throws RemoteException {
- mHandler.removeMessages(H.ON_PINNED_STACK_ANIMATION_STARTED);
- mHandler.sendEmptyMessage(H.ON_PINNED_STACK_ANIMATION_STARTED);
- }
-
- @Override
- public void onPinnedStackAnimationEnded() throws RemoteException {
- mHandler.removeMessages(H.ON_PINNED_STACK_ANIMATION_ENDED);
- mHandler.sendEmptyMessage(H.ON_PINNED_STACK_ANIMATION_ENDED);
- }
-
- @Override
- public void onActivityForcedResizable(String packageName, int taskId, int reason)
- throws RemoteException {
- mHandler.obtainMessage(H.ON_ACTIVITY_FORCED_RESIZABLE, taskId, reason, packageName)
- .sendToTarget();
- }
-
- @Override
- public void onActivityDismissingDockedStack() throws RemoteException {
- mHandler.sendEmptyMessage(H.ON_ACTIVITY_DISMISSING_DOCKED_STACK);
- }
-
- @Override
- public void onActivityLaunchOnSecondaryDisplayFailed() throws RemoteException {
- mHandler.sendEmptyMessage(H.ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED);
- }
-
- @Override
- public void onTaskProfileLocked(int taskId, int userId) {
- mHandler.obtainMessage(H.ON_TASK_PROFILE_LOCKED, taskId, userId).sendToTarget();
- }
-
- @Override
- public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot)
- throws RemoteException {
- mHandler.obtainMessage(H.ON_TASK_SNAPSHOT_CHANGED, taskId, 0, snapshot).sendToTarget();
- }
- };
-
private final UserInfoController.OnUserInfoChangedListener mOnUserInfoChangedListener =
(String name, Drawable picture, String userAccount) ->
mCurrentUserId = mAm.getCurrentUser();
- /**
- * List of {@link TaskStackListener} registered from {@link #registerTaskStackListener}.
- */
- private List<TaskStackListener> mTaskStackListeners = new ArrayList<>();
-
/** Private constructor */
private SystemServicesProxy(Context context) {
mContext = context.getApplicationContext();
@@ -308,21 +134,15 @@ public class SystemServicesProxy {
mAm = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
mIam = ActivityManager.getService();
mPm = context.getPackageManager();
- mDrawableFactory = IconDrawableFactory.newInstance(context);
mIpm = AppGlobals.getPackageManager();
mAssistUtils = new AssistUtils(context);
mWm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
mIwm = WindowManagerGlobal.getWindowManagerService();
- mKgm = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
mUm = UserManager.get(context);
mDreamManager = IDreamManager.Stub.asInterface(
ServiceManager.checkService(DreamService.DREAM_SERVICE));
mDisplay = mWm.getDefaultDisplay();
mRecentsPackage = context.getPackageName();
- mHasFreeformWorkspaceSupport =
- mPm.hasSystemFeature(PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT) ||
- Settings.Global.getInt(context.getContentResolver(),
- DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, 0) != 0;
mIsSafeMode = mPm.isSafeMode();
mCurrentUserId = mAm.getCurrentUser();
@@ -339,23 +159,11 @@ public class SystemServicesProxy {
mBgProtectionPaint.setColor(0xFFffffff);
mBgProtectionCanvas = new Canvas();
- // Resolve the assist intent
- mAssistComponent = mAssistUtils.getAssistComponentForUser(UserHandle.myUserId());
-
// Since SystemServicesProxy can be accessed from a per-SysUI process component, create a
// per-process listener to keep track of the current user id to reduce the number of binder
// calls to fetch it.
UserInfoController userInfoController = Dependency.get(UserInfoController.class);
userInfoController.addCallback(mOnUserInfoChangedListener);
-
- if (RecentsDebugFlags.Static.EnableMockTasks) {
- // Create a dummy icon
- mDummyIcon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
- mDummyIcon.eraseColor(0xFF999999);
- }
-
- Collections.addAll(sRecentsBlacklist,
- res.getStringArray(R.array.recents_blacklist_array));
}
/**
@@ -377,130 +185,6 @@ public class SystemServicesProxy {
}
/**
- * @return whether the provided {@param className} is blacklisted
- */
- public boolean isBlackListedActivity(String className) {
- return sRecentsBlacklist.contains(className);
- }
-
- /**
- * Returns a list of the recents tasks.
- *
- * @param includeFrontMostExcludedTask if set, will ensure that the front most excluded task
- * will be visible, otherwise no excluded tasks will be
- * visible.
- */
- public List<ActivityManager.RecentTaskInfo> getRecentTasks(int numLatestTasks, int userId,
- boolean includeFrontMostExcludedTask, ArraySet<Integer> quietProfileIds) {
- if (mAm == null) return null;
-
- // If we are mocking, then create some recent tasks
- if (RecentsDebugFlags.Static.EnableMockTasks) {
- ArrayList<ActivityManager.RecentTaskInfo> tasks =
- new ArrayList<ActivityManager.RecentTaskInfo>();
- int count = Math.min(numLatestTasks, RecentsDebugFlags.Static.MockTaskCount);
- for (int i = 0; i < count; i++) {
- // Create a dummy component name
- int packageIndex = i % RecentsDebugFlags.Static.MockTasksPackageCount;
- ComponentName cn = new ComponentName("com.android.test" + packageIndex,
- "com.android.test" + i + ".Activity");
- String description = "" + i + " - " +
- Long.toString(Math.abs(new Random().nextLong()), 36);
- // Create the recent task info
- ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo();
- rti.id = rti.persistentId = rti.affiliatedTaskId = i;
- rti.baseIntent = new Intent();
- rti.baseIntent.setComponent(cn);
- rti.description = description;
- rti.firstActiveTime = rti.lastActiveTime = i;
- if (i % 2 == 0) {
- rti.taskDescription = new ActivityManager.TaskDescription(description,
- Bitmap.createBitmap(mDummyIcon), null,
- 0xFF000000 | (0xFFFFFF & new Random().nextInt()),
- 0xFF000000 | (0xFFFFFF & new Random().nextInt()),
- 0, 0);
- } else {
- rti.taskDescription = new ActivityManager.TaskDescription();
- }
- tasks.add(rti);
- }
- return tasks;
- }
-
- // Remove home/recents/excluded tasks
- int minNumTasksToQuery = 10;
- int numTasksToQuery = Math.max(minNumTasksToQuery, numLatestTasks);
- int flags = ActivityManager.RECENT_IGNORE_HOME_AND_RECENTS_STACK_TASKS |
- ActivityManager.RECENT_INGORE_DOCKED_STACK_TOP_TASK |
- ActivityManager.RECENT_INGORE_PINNED_STACK_TASKS |
- ActivityManager.RECENT_IGNORE_UNAVAILABLE |
- ActivityManager.RECENT_INCLUDE_PROFILES;
- if (includeFrontMostExcludedTask) {
- flags |= ActivityManager.RECENT_WITH_EXCLUDED;
- }
- List<ActivityManager.RecentTaskInfo> tasks = null;
- try {
- tasks = mAm.getRecentTasksForUser(numTasksToQuery, flags, userId);
- } catch (Exception e) {
- Log.e(TAG, "Failed to get recent tasks", e);
- }
-
- // Break early if we can't get a valid set of tasks
- if (tasks == null) {
- return new ArrayList<>();
- }
-
- boolean isFirstValidTask = true;
- Iterator<ActivityManager.RecentTaskInfo> iter = tasks.iterator();
- while (iter.hasNext()) {
- ActivityManager.RecentTaskInfo t = iter.next();
-
- // NOTE: The order of these checks happens in the expected order of the traversal of the
- // tasks
-
- // Remove the task if it or it's package are blacklsited
- if (sRecentsBlacklist.contains(t.realActivity.getClassName()) ||
- sRecentsBlacklist.contains(t.realActivity.getPackageName())) {
- iter.remove();
- continue;
- }
-
- // Remove the task if it is marked as excluded, unless it is the first most task and we
- // are requested to include it
- boolean isExcluded = (t.baseIntent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
- == Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
- isExcluded |= quietProfileIds.contains(t.userId);
- if (isExcluded && (!isFirstValidTask || !includeFrontMostExcludedTask)) {
- iter.remove();
- }
-
- isFirstValidTask = false;
- }
-
- return tasks.subList(0, Math.min(tasks.size(), numLatestTasks));
- }
-
- /**
- * Returns the top running task.
- */
- public ActivityManager.RunningTaskInfo getRunningTask() {
- // Note: The set of running tasks from the system is ordered by recency
- List<ActivityManager.RunningTaskInfo> tasks = mAm.getRunningTasks(10);
- if (tasks != null && !tasks.isEmpty()) {
- // Find the first task in a valid stack, we ignore everything from the Recents and PiP
- // stacks
- for (int i = 0; i < tasks.size(); i++) {
- ActivityManager.RunningTaskInfo task = tasks.get(i);
- int stackId = task.stackId;
- if (stackId != RECENTS_STACK_ID && stackId != PINNED_STACK_ID) {
- return task;
- }
- }
- }
- return null;
- }
-
- /**
* Returns whether the recents activity is currently visible.
*/
public boolean isRecentsActivityVisible() {
@@ -512,6 +196,8 @@ public class SystemServicesProxy {
*
* @param isHomeStackVisible if provided, will return whether the home stack is visible
* regardless of the recents visibility
+ *
+ * TODO(winsonc): Refactor this check to just use the recents activity lifecycle
*/
public boolean isRecentsActivityVisible(MutableBoolean isHomeStackVisible) {
if (mIam == null) return false;
@@ -522,12 +208,17 @@ public class SystemServicesProxy {
ActivityManager.StackInfo fullscreenStackInfo = null;
ActivityManager.StackInfo recentsStackInfo = null;
for (int i = 0; i < stackInfos.size(); i++) {
- StackInfo stackInfo = stackInfos.get(i);
- if (stackInfo.stackId == HOME_STACK_ID) {
+ final StackInfo stackInfo = stackInfos.get(i);
+ final WindowConfiguration winConfig = stackInfo.configuration.windowConfiguration;
+ final int activityType = winConfig.getActivityType();
+ final int windowingMode = winConfig.getWindowingMode();
+ if (homeStackInfo == null && activityType == ACTIVITY_TYPE_HOME) {
homeStackInfo = stackInfo;
- } else if (stackInfo.stackId == FULLSCREEN_WORKSPACE_STACK_ID) {
+ } else if (fullscreenStackInfo == null && activityType == ACTIVITY_TYPE_STANDARD
+ && (windowingMode == WINDOWING_MODE_FULLSCREEN
+ || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY)) {
fullscreenStackInfo = stackInfo;
- } else if (stackInfo.stackId == RECENTS_STACK_ID) {
+ } else if (recentsStackInfo == null && activityType == ACTIVITY_TYPE_RECENTS) {
recentsStackInfo = stackInfo;
}
}
@@ -561,76 +252,34 @@ public class SystemServicesProxy {
}
/**
- * Returns whether this device has freeform workspaces.
- */
- public boolean hasFreeformWorkspaceSupport() {
- return mHasFreeformWorkspaceSupport;
- }
-
- /**
* Returns whether this device is in the safe mode.
*/
public boolean isInSafeMode() {
return mIsSafeMode;
}
- /** Docks a task to the side of the screen and starts it. */
- public boolean startTaskInDockedMode(int taskId, int createMode) {
- if (mIam == null) return false;
-
- try {
- final ActivityOptions options = ActivityOptions.makeBasic();
- options.setDockCreateMode(createMode);
- options.setLaunchStackId(DOCKED_STACK_ID);
- mIam.startActivityFromRecents(taskId, options.toBundle());
- return true;
- } catch (Exception e) {
- Log.e(TAG, "Failed to dock task: " + taskId + " with createMode: " + createMode, e);
- }
- return false;
- }
-
- /** Docks an already resumed task to the side of the screen. */
- public boolean moveTaskToDockedStack(int taskId, int createMode, Rect initialBounds) {
+ /** Moves an already resumed task to the side of the screen to initiate split screen. */
+ public boolean setTaskWindowingModeSplitScreenPrimary(int taskId, int createMode,
+ Rect initialBounds) {
if (mIam == null) {
return false;
}
try {
- return mIam.moveTaskToDockedStack(taskId, createMode, true /* onTop */,
- false /* animate */, initialBounds);
+ return mIam.setTaskWindowingModeSplitScreenPrimary(taskId, createMode, true /* onTop */,
+ false /* animate */, initialBounds, true /* showRecents */);
} catch (RemoteException e) {
e.printStackTrace();
}
return false;
}
- /**
- * Returns whether the given stack id is the home stack id.
- */
- public static boolean isHomeStack(int stackId) {
- return stackId == HOME_STACK_ID;
- }
-
- /**
- * Returns whether the given stack id is the pinned stack id.
- */
- public static boolean isPinnedStack(int stackId){
- return stackId == PINNED_STACK_ID;
- }
-
- /**
- * Returns whether the given stack id is the docked stack id.
- */
- public static boolean isDockedStack(int stackId) {
- return stackId == DOCKED_STACK_ID;
- }
-
- /**
- * Returns whether the given stack id is the freeform workspace stack id.
- */
- public static boolean isFreeformStack(int stackId) {
- return stackId == FREEFORM_WORKSPACE_STACK_ID;
+ public ActivityManager.StackInfo getSplitScreenPrimaryStack() {
+ try {
+ return mIam.getStackInfo(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED);
+ } catch (RemoteException e) {
+ return null;
+ }
}
/**
@@ -639,13 +288,7 @@ public class SystemServicesProxy {
public boolean hasDockedTask() {
if (mIam == null) return false;
- ActivityManager.StackInfo stackInfo = null;
- try {
- stackInfo = mIam.getStackInfo(DOCKED_STACK_ID);
- } catch (RemoteException e) {
- e.printStackTrace();
- }
-
+ ActivityManager.StackInfo stackInfo = getSplitScreenPrimaryStack();
if (stackInfo != null) {
int userId = getCurrentUser();
boolean hasUserTask = false;
@@ -662,7 +305,7 @@ public class SystemServicesProxy {
*/
public boolean hasSoftNavigationBar() {
try {
- return WindowManagerGlobal.getWindowManagerService().hasNavigationBar();
+ return mIwm.hasNavigationBar();
} catch (RemoteException e) {
e.printStackTrace();
}
@@ -679,371 +322,17 @@ public class SystemServicesProxy {
return insets.right > 0;
}
- /**
- * Cancels the current window transtion to/from Recents for the given task id.
- */
- public void cancelWindowTransition(int taskId) {
+ /** Set the task's windowing mode. */
+ public void setTaskWindowingMode(int taskId, int windowingMode) {
if (mIam == null) return;
try {
- mIam.cancelTaskWindowTransition(taskId);
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- }
-
- /**
- * Cancels the current thumbnail transtion to/from Recents for the given task id.
- */
- public void cancelThumbnailTransition(int taskId) {
- if (mIam == null) return;
-
- try {
- mIam.cancelTaskThumbnailTransition(taskId);
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- }
-
- /** Returns the top task thumbnail for the given task id */
- public ThumbnailData getTaskThumbnail(int taskId, boolean reduced) {
- if (mAm == null) return null;
-
- // If we are mocking, then just return a dummy thumbnail
- if (RecentsDebugFlags.Static.EnableMockTasks) {
- ThumbnailData thumbnailData = new ThumbnailData();
- thumbnailData.thumbnail = Bitmap.createBitmap(mDummyThumbnailWidth,
- mDummyThumbnailHeight, Bitmap.Config.ARGB_8888);
- thumbnailData.thumbnail.eraseColor(0xff333333);
- return thumbnailData;
- }
-
- ThumbnailData thumbnailData = getThumbnail(taskId, reduced);
- if (thumbnailData.thumbnail != null && !ActivityManager.ENABLE_TASK_SNAPSHOTS) {
- thumbnailData.thumbnail.setHasAlpha(false);
- // We use a dumb heuristic for now, if the thumbnail is purely transparent in the top
- // left pixel, then assume the whole thumbnail is transparent. Generally, proper
- // screenshots are always composed onto a bitmap that has no alpha.
- if (Color.alpha(thumbnailData.thumbnail.getPixel(0, 0)) == 0) {
- mBgProtectionCanvas.setBitmap(thumbnailData.thumbnail);
- mBgProtectionCanvas.drawRect(0, 0, thumbnailData.thumbnail.getWidth(),
- thumbnailData.thumbnail.getHeight(), mBgProtectionPaint);
- mBgProtectionCanvas.setBitmap(null);
- Log.e(TAG, "Invalid screenshot detected from getTaskThumbnail()");
- }
- }
- return thumbnailData;
- }
-
- /**
- * Returns a task thumbnail from the activity manager
- */
- public @NonNull ThumbnailData getThumbnail(int taskId, boolean reducedResolution) {
- if (mAm == null) {
- return new ThumbnailData();
- }
-
- final ThumbnailData thumbnailData;
- if (ActivityManager.ENABLE_TASK_SNAPSHOTS) {
- ActivityManager.TaskSnapshot snapshot = null;
- try {
- snapshot = ActivityManager.getService().getTaskSnapshot(taskId, reducedResolution);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to retrieve snapshot", e);
- }
- if (snapshot != null) {
- thumbnailData = ThumbnailData.createFromTaskSnapshot(snapshot);
- } else {
- return new ThumbnailData();
- }
- } else {
- ActivityManager.TaskThumbnail taskThumbnail = mAm.getTaskThumbnail(taskId);
- if (taskThumbnail == null) {
- return new ThumbnailData();
- }
-
- Bitmap thumbnail = taskThumbnail.mainThumbnail;
- ParcelFileDescriptor descriptor = taskThumbnail.thumbnailFileDescriptor;
- if (thumbnail == null && descriptor != null) {
- thumbnail = BitmapFactory.decodeFileDescriptor(descriptor.getFileDescriptor(),
- null, sBitmapOptions);
- }
- if (descriptor != null) {
- try {
- descriptor.close();
- } catch (IOException e) {
- }
- }
- thumbnailData = new ThumbnailData();
- thumbnailData.thumbnail = thumbnail;
- thumbnailData.orientation = taskThumbnail.thumbnailInfo.screenOrientation;
- thumbnailData.insets.setEmpty();
- }
- return thumbnailData;
- }
-
- /**
- * Moves a task into another stack.
- */
- public void moveTaskToStack(int taskId, int stackId) {
- if (mIam == null) return;
-
- try {
- mIam.positionTaskInStack(taskId, stackId, 0);
+ mIam.setTaskWindowingMode(taskId, windowingMode, false /* onTop */);
} catch (RemoteException | IllegalArgumentException e) {
e.printStackTrace();
}
}
- /** Removes the task */
- public void removeTask(final int taskId) {
- if (mAm == null) return;
- if (RecentsDebugFlags.Static.EnableMockTasks) return;
-
- // Remove the task.
- mUiOffloadThread.submit(() -> {
- mAm.removeTask(taskId);
- });
- }
-
- /**
- * Sends a message to close other system windows.
- */
- public void sendCloseSystemWindows(String reason) {
- mUiOffloadThread.submit(() -> {
- try {
- mIam.closeSystemDialogs(reason);
- } catch (RemoteException e) {
- }
- });
- }
-
- /**
- * Returns the activity info for a given component name.
- *
- * @param cn The component name of the activity.
- * @param userId The userId of the user that this is for.
- */
- public ActivityInfo getActivityInfo(ComponentName cn, int userId) {
- if (mIpm == null) return null;
- if (RecentsDebugFlags.Static.EnableMockTasks) return new ActivityInfo();
-
- try {
- return mIpm.getActivityInfo(cn, PackageManager.GET_META_DATA, userId);
- } catch (RemoteException e) {
- e.printStackTrace();
- return null;
- }
- }
-
- /**
- * Returns the activity info for a given component name.
- *
- * @param cn The component name of the activity.
- */
- public ActivityInfo getActivityInfo(ComponentName cn) {
- if (mPm == null) return null;
- if (RecentsDebugFlags.Static.EnableMockTasks) return new ActivityInfo();
-
- try {
- return mPm.getActivityInfo(cn, PackageManager.GET_META_DATA);
- } catch (PackageManager.NameNotFoundException e) {
- e.printStackTrace();
- return null;
- }
- }
-
- /**
- * Returns the activity label, badging if necessary.
- */
- public String getBadgedActivityLabel(ActivityInfo info, int userId) {
- if (mPm == null) return null;
-
- // If we are mocking, then return a mock label
- if (RecentsDebugFlags.Static.EnableMockTasks) {
- return "Recent Task: " + userId;
- }
-
- return getBadgedLabel(info.loadLabel(mPm).toString(), userId);
- }
-
- /**
- * Returns the application label, badging if necessary.
- */
- public String getBadgedApplicationLabel(ApplicationInfo appInfo, int userId) {
- if (mPm == null) return null;
-
- // If we are mocking, then return a mock label
- if (RecentsDebugFlags.Static.EnableMockTasks) {
- return "Recent Task App: " + userId;
- }
-
- return getBadgedLabel(appInfo.loadLabel(mPm).toString(), userId);
- }
-
- /**
- * Returns the content description for a given task, badging it if necessary. The content
- * description joins the app and activity labels.
- */
- public String getBadgedContentDescription(ActivityInfo info, int userId,
- ActivityManager.TaskDescription td, Resources res) {
- // If we are mocking, then return a mock label
- if (RecentsDebugFlags.Static.EnableMockTasks) {
- return "Recent Task Content Description: " + userId;
- }
-
- String activityLabel;
- if (td != null && td.getLabel() != null) {
- activityLabel = td.getLabel();
- } else {
- activityLabel = info.loadLabel(mPm).toString();
- }
- String applicationLabel = info.applicationInfo.loadLabel(mPm).toString();
- String badgedApplicationLabel = getBadgedLabel(applicationLabel, userId);
- return applicationLabel.equals(activityLabel) ? badgedApplicationLabel
- : res.getString(R.string.accessibility_recents_task_header,
- badgedApplicationLabel, activityLabel);
- }
-
- /**
- * Returns the activity icon for the ActivityInfo for a user, badging if
- * necessary.
- */
- public Drawable getBadgedActivityIcon(ActivityInfo info, int userId) {
- if (mPm == null) return null;
-
- // If we are mocking, then return a mock label
- if (RecentsDebugFlags.Static.EnableMockTasks) {
- return new ColorDrawable(0xFF666666);
- }
-
- return mDrawableFactory.getBadgedIcon(info, info.applicationInfo, userId);
- }
-
- /**
- * Returns the application icon for the ApplicationInfo for a user, badging if
- * necessary.
- */
- public Drawable getBadgedApplicationIcon(ApplicationInfo appInfo, int userId) {
- if (mPm == null) return null;
-
- // If we are mocking, then return a mock label
- if (RecentsDebugFlags.Static.EnableMockTasks) {
- return new ColorDrawable(0xFF666666);
- }
-
- return mDrawableFactory.getBadgedIcon(appInfo, userId);
- }
-
- /**
- * Returns the task description icon, loading and badging it if it necessary.
- */
- public Drawable getBadgedTaskDescriptionIcon(ActivityManager.TaskDescription taskDescription,
- int userId, Resources res) {
-
- // If we are mocking, then return a mock label
- if (RecentsDebugFlags.Static.EnableMockTasks) {
- return new ColorDrawable(0xFF666666);
- }
-
- Bitmap tdIcon = taskDescription.getInMemoryIcon();
- if (tdIcon == null) {
- tdIcon = ActivityManager.TaskDescription.loadTaskDescriptionIcon(
- taskDescription.getIconFilename(), userId);
- }
- if (tdIcon != null) {
- return getBadgedIcon(new BitmapDrawable(res, tdIcon), userId);
- }
- return null;
- }
-
- public ActivityManager.TaskDescription getTaskDescription(int taskId) {
- try {
- return mIam.getTaskDescription(taskId);
- } catch (RemoteException e) {
- return null;
- }
- }
-
- /**
- * Returns the given icon for a user, badging if necessary.
- */
- private Drawable getBadgedIcon(Drawable icon, int userId) {
- if (userId != UserHandle.myUserId()) {
- icon = mPm.getUserBadgedIcon(icon, new UserHandle(userId));
- }
- return icon;
- }
-
- /**
- * Returns a banner used on TV for the specified Activity.
- */
- public Drawable getActivityBanner(ActivityInfo info) {
- if (mPm == null) return null;
-
- // If we are mocking, then return a mock banner
- if (RecentsDebugFlags.Static.EnableMockTasks) {
- return new ColorDrawable(0xFF666666);
- }
-
- Drawable banner = info.loadBanner(mPm);
- return banner;
- }
-
- /**
- * Returns a logo used on TV for the specified Activity.
- */
- public Drawable getActivityLogo(ActivityInfo info) {
- if (mPm == null) return null;
-
- // If we are mocking, then return a mock logo
- if (RecentsDebugFlags.Static.EnableMockTasks) {
- return new ColorDrawable(0xFF666666);
- }
-
- Drawable logo = info.loadLogo(mPm);
- return logo;
- }
-
-
- /**
- * Returns the given label for a user, badging if necessary.
- */
- private String getBadgedLabel(String label, int userId) {
- if (userId != UserHandle.myUserId()) {
- label = mPm.getUserBadgedLabel(label, new UserHandle(userId)).toString();
- }
- return label;
- }
-
- /**
- * Returns whether the provided {@param userId} is currently locked (and showing Keyguard).
- */
- public boolean isDeviceLocked(int userId) {
- if (mKgm == null) {
- return false;
- }
- return mKgm.isDeviceLocked(userId);
- }
-
- /** Returns the package name of the home activity. */
- public String getHomeActivityPackageName() {
- if (mPm == null) return null;
- if (RecentsDebugFlags.Static.EnableMockTasks) return null;
-
- ArrayList<ResolveInfo> homeActivities = new ArrayList<>();
- ComponentName defaultHomeActivity = mPm.getHomeActivities(homeActivities);
- if (defaultHomeActivity != null) {
- return defaultHomeActivity.getPackageName();
- } else if (homeActivities.size() == 1) {
- ResolveInfo info = homeActivities.get(0);
- if (info.activityInfo != null) {
- return info.activityInfo.packageName;
- }
- }
- return null;
- }
-
/**
* Returns whether the provided {@param userId} represents the system user.
*/
@@ -1083,36 +372,13 @@ public class SystemServicesProxy {
if (mIam == null) return false;
try {
- return mIam.isInLockTaskMode();
+ return mIam.getLockTaskModeState() == ActivityManager.LOCK_TASK_MODE_PINNED;
} catch (RemoteException e) {
return false;
}
}
/**
- * Returns a global setting.
- */
- public int getGlobalSetting(Context context, String setting) {
- ContentResolver cr = context.getContentResolver();
- return Settings.Global.getInt(cr, setting, 0);
- }
-
- /**
- * Returns a system setting.
- */
- public int getSystemSetting(Context context, String setting) {
- ContentResolver cr = context.getContentResolver();
- return Settings.System.getInt(cr, setting, 0);
- }
-
- /**
- * Returns a system property.
- */
- public String getSystemProperty(String key) {
- return SystemProperties.get(key);
- }
-
- /**
* Returns the smallest width/height.
*/
public int getDeviceSmallestWidth() {
@@ -1146,9 +412,10 @@ public class SystemServicesProxy {
try {
// Use the recents stack bounds, fallback to fullscreen stack if it is null
- ActivityManager.StackInfo stackInfo = mIam.getStackInfo(RECENTS_STACK_ID);
+ ActivityManager.StackInfo stackInfo =
+ mIam.getStackInfo(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_RECENTS);
if (stackInfo == null) {
- stackInfo = mIam.getStackInfo(FULLSCREEN_WORKSPACE_STACK_ID);
+ stackInfo = mIam.getStackInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
}
if (stackInfo != null) {
windowRect.set(stackInfo.bounds);
@@ -1165,47 +432,6 @@ public class SystemServicesProxy {
opts != null ? opts.toBundle() : null, UserHandle.CURRENT));
}
- /** Starts an activity from recents. */
- public void startActivityFromRecents(Context context, Task.TaskKey taskKey, String taskName,
- ActivityOptions options, int stackId,
- @Nullable final StartActivityFromRecentsResultListener resultListener) {
- if (mIam == null) {
- return;
- }
- if (taskKey.stackId == DOCKED_STACK_ID) {
- // We show non-visible docked tasks in Recents, but we always want to launch
- // them in the fullscreen stack.
- if (options == null) {
- options = ActivityOptions.makeBasic();
- }
- options.setLaunchStackId(FULLSCREEN_WORKSPACE_STACK_ID);
- } else if (stackId != INVALID_STACK_ID) {
- if (options == null) {
- options = ActivityOptions.makeBasic();
- }
- options.setLaunchStackId(stackId);
- }
- final ActivityOptions finalOptions = options;
-
- // Execute this from another thread such that we can do other things (like caching the
- // bitmap for the thumbnail) while AM is busy starting our activity.
- mUiOffloadThread.submit(() -> {
- try {
- mIam.startActivityFromRecents(
- taskKey.id, finalOptions == null ? null : finalOptions.toBundle());
- if (resultListener != null) {
- mHandler.post(() -> resultListener.onStartActivityResult(true));
- }
- } catch (Exception e) {
- Log.e(TAG, context.getString(
- R.string.recents_launch_error_message, taskName), e);
- if (resultListener != null) {
- mHandler.post(() -> resultListener.onStartActivityResult(false));
- }
- }
- });
- }
-
/** Starts an in-place animation on the front most application windows. */
public void startInPlaceAnimationOnFrontMostApplication(ActivityOptions opts) {
if (mIam == null) return;
@@ -1218,32 +444,12 @@ public class SystemServicesProxy {
}
}
- /**
- * Registers a task stack listener with the system.
- * This should be called on the main thread.
- */
- public void registerTaskStackListener(TaskStackListener listener) {
- if (mIam == null) return;
-
- synchronized (mTaskStackListeners) {
- mTaskStackListeners.add(listener);
- if (mTaskStackListeners.size() == 1) {
- // Register mTaskStackListener to IActivityManager only once if needed.
- try {
- mIam.registerTaskStackListener(mTaskStackListener);
- } catch (Exception e) {
- Log.w(TAG, "Failed to call registerTaskStackListener", e);
- }
- }
- }
- }
-
public void endProlongedAnimations() {
if (mWm == null) {
return;
}
try {
- WindowManagerGlobal.getWindowManagerService().endProlongedAnimations();
+ mIwm.endProlongedAnimations();
} catch (Exception e) {
e.printStackTrace();
}
@@ -1253,7 +459,7 @@ public class SystemServicesProxy {
if (mWm == null) return;
try {
- WindowManagerGlobal.getWindowManagerService().registerDockedStackListener(listener);
+ mIwm.registerDockedStackListener(listener);
} catch (Exception e) {
e.printStackTrace();
}
@@ -1280,45 +486,36 @@ public class SystemServicesProxy {
if (mWm == null) return;
try {
- WindowManagerGlobal.getWindowManagerService().getStableInsets(Display.DEFAULT_DISPLAY,
- outStableInsets);
+ mIwm.getStableInsets(Display.DEFAULT_DISPLAY, outStableInsets);
} catch (Exception e) {
e.printStackTrace();
}
}
- public void overridePendingAppTransitionMultiThumbFuture(
- IAppTransitionAnimationSpecsFuture future, IRemoteCallback animStartedListener,
- boolean scaleUp) {
- try {
- WindowManagerGlobal.getWindowManagerService()
- .overridePendingAppTransitionMultiThumbFuture(future, animStartedListener,
- scaleUp);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to override transition: " + e);
- }
- }
-
/**
* Updates the visibility of recents.
*/
- public void setRecentsVisibility(boolean visible) {
- try {
- mIwm.setRecentsVisibility(visible);
- } catch (RemoteException e) {
- Log.e(TAG, "Unable to reach window manager", e);
- }
+ public void setRecentsVisibility(final boolean visible) {
+ mUiOffloadThread.submit(() -> {
+ try {
+ mIwm.setRecentsVisibility(visible);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to reach window manager", e);
+ }
+ });
}
/**
* Updates the visibility of the picture-in-picture.
*/
- public void setPipVisibility(boolean visible) {
- try {
- mIwm.setPipVisibility(visible);
- } catch (RemoteException e) {
- Log.e(TAG, "Unable to reach window manager", e);
- }
+ public void setPipVisibility(final boolean visible) {
+ mUiOffloadThread.submit(() -> {
+ try {
+ mIwm.setPipVisibility(visible);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to reach window manager", e);
+ }
+ });
}
public boolean isDreaming() {
@@ -1340,111 +537,7 @@ public class SystemServicesProxy {
});
}
- public void updateOverviewLastStackActiveTimeAsync(long newLastStackActiveTime,
- int currentUserId) {
- mUiOffloadThread.submit(() -> {
- Settings.Secure.putLongForUser(mContext.getContentResolver(),
- Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, newLastStackActiveTime, currentUserId);
- });
- }
-
public interface StartActivityFromRecentsResultListener {
void onStartActivityResult(boolean succeeded);
}
-
- private final class H extends Handler {
- private static final int ON_TASK_STACK_CHANGED = 1;
- private static final int ON_TASK_SNAPSHOT_CHANGED = 2;
- private static final int ON_ACTIVITY_PINNED = 3;
- private static final int ON_PINNED_ACTIVITY_RESTART_ATTEMPT = 4;
- private static final int ON_PINNED_STACK_ANIMATION_ENDED = 5;
- private static final int ON_ACTIVITY_FORCED_RESIZABLE = 6;
- private static final int ON_ACTIVITY_DISMISSING_DOCKED_STACK = 7;
- private static final int ON_TASK_PROFILE_LOCKED = 8;
- private static final int ON_PINNED_STACK_ANIMATION_STARTED = 9;
- private static final int ON_ACTIVITY_UNPINNED = 10;
- private static final int ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED = 11;
-
- @Override
- public void handleMessage(Message msg) {
- synchronized (mTaskStackListeners) {
- switch (msg.what) {
- case ON_TASK_STACK_CHANGED: {
- Trace.beginSection("onTaskStackChanged");
- for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
- mTaskStackListeners.get(i).onTaskStackChanged();
- }
- Trace.endSection();
- break;
- }
- case ON_TASK_SNAPSHOT_CHANGED: {
- Trace.beginSection("onTaskSnapshotChanged");
- for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
- mTaskStackListeners.get(i).onTaskSnapshotChanged(msg.arg1,
- (TaskSnapshot) msg.obj);
- }
- Trace.endSection();
- break;
- }
- case ON_ACTIVITY_PINNED: {
- for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
- mTaskStackListeners.get(i).onActivityPinned((String) msg.obj, msg.arg1,
- msg.arg2);
- }
- break;
- }
- case ON_ACTIVITY_UNPINNED: {
- for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
- mTaskStackListeners.get(i).onActivityUnpinned();
- }
- break;
- }
- case ON_PINNED_ACTIVITY_RESTART_ATTEMPT: {
- for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
- mTaskStackListeners.get(i).onPinnedActivityRestartAttempt(
- msg.arg1 != 0);
- }
- break;
- }
- case ON_PINNED_STACK_ANIMATION_STARTED: {
- for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
- mTaskStackListeners.get(i).onPinnedStackAnimationStarted();
- }
- break;
- }
- case ON_PINNED_STACK_ANIMATION_ENDED: {
- for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
- mTaskStackListeners.get(i).onPinnedStackAnimationEnded();
- }
- break;
- }
- case ON_ACTIVITY_FORCED_RESIZABLE: {
- for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
- mTaskStackListeners.get(i).onActivityForcedResizable(
- (String) msg.obj, msg.arg1, msg.arg2);
- }
- break;
- }
- case ON_ACTIVITY_DISMISSING_DOCKED_STACK: {
- for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
- mTaskStackListeners.get(i).onActivityDismissingDockedStack();
- }
- break;
- }
- case ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED: {
- for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
- mTaskStackListeners.get(i).onActivityLaunchOnSecondaryDisplayFailed();
- }
- break;
- }
- case ON_TASK_PROFILE_LOCKED: {
- for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
- mTaskStackListeners.get(i).onTaskProfileLocked(msg.arg1, msg.arg2);
- }
- break;
- }
- }
- }
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
deleted file mode 100644
index 4349e30f60e0..000000000000
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
+++ /dev/null
@@ -1,325 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.misc;
-
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.RectEvaluator;
-import android.annotation.FloatRange;
-import android.app.Activity;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.Drawable;
-import android.os.Trace;
-import android.util.ArraySet;
-import android.util.IntProperty;
-import android.util.Property;
-import android.util.TypedValue;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewParent;
-import android.view.ViewStub;
-
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.views.TaskViewTransform;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/* Common code */
-public class Utilities {
-
- public static final Property<Drawable, Integer> DRAWABLE_ALPHA =
- new IntProperty<Drawable>("drawableAlpha") {
- @Override
- public void setValue(Drawable object, int alpha) {
- object.setAlpha(alpha);
- }
-
- @Override
- public Integer get(Drawable object) {
- return object.getAlpha();
- }
- };
-
- public static final Property<Drawable, Rect> DRAWABLE_RECT =
- new Property<Drawable, Rect>(Rect.class, "drawableBounds") {
- @Override
- public void set(Drawable object, Rect bounds) {
- object.setBounds(bounds);
- }
-
- @Override
- public Rect get(Drawable object) {
- return object.getBounds();
- }
- };
-
- public static final RectFEvaluator RECTF_EVALUATOR = new RectFEvaluator();
- public static final RectEvaluator RECT_EVALUATOR = new RectEvaluator(new Rect());
- public static final Rect EMPTY_RECT = new Rect();
-
- /**
- * @return the first parent walking up the view hierarchy that has the given class type.
- *
- * @param parentClass must be a class derived from {@link View}
- */
- public static <T extends View> T findParent(View v, Class<T> parentClass) {
- ViewParent parent = v.getParent();
- while (parent != null) {
- if (parentClass.isAssignableFrom(parent.getClass())) {
- return (T) parent;
- }
- parent = parent.getParent();
- }
- return null;
- }
-
- /**
- * Initializes the {@param setOut} with the given object.
- */
- public static <T> ArraySet<T> objectToSet(T obj, ArraySet<T> setOut) {
- setOut.clear();
- if (obj != null) {
- setOut.add(obj);
- }
- return setOut;
- }
-
- /**
- * Replaces the contents of {@param setOut} with the contents of the {@param array}.
- */
- public static <T> ArraySet<T> arrayToSet(T[] array, ArraySet<T> setOut) {
- setOut.clear();
- if (array != null) {
- Collections.addAll(setOut, array);
- }
- return setOut;
- }
-
- /**
- * @return the clamped {@param value} between the provided {@param min} and {@param max}.
- */
- public static float clamp(float value, float min, float max) {
- return Math.max(min, Math.min(max, value));
- }
-
- /**
- * @return the clamped {@param value} between the provided {@param min} and {@param max}.
- */
- public static int clamp(int value, int min, int max) {
- return Math.max(min, Math.min(max, value));
- }
-
- /**
- * @return the clamped {@param value} between 0 and 1.
- */
- public static float clamp01(float value) {
- return Math.max(0f, Math.min(1f, value));
- }
-
- /**
- * Scales the {@param value} to be proportionally between the {@param min} and
- * {@param max} values.
- *
- * @param value must be between 0 and 1
- */
- public static float mapRange(@FloatRange(from=0.0,to=1.0) float value, float min, float max) {
- return min + (value * (max - min));
- }
-
- /**
- * Scales the {@param value} proportionally from {@param min} and {@param max} to 0 and 1.
- *
- * @param value must be between {@param min} and {@param max}
- */
- public static float unmapRange(float value, float min, float max) {
- return (value - min) / (max - min);
- }
-
- /** Scales a rect about its centroid */
- public static void scaleRectAboutCenter(RectF r, float scale) {
- if (scale != 1.0f) {
- float cx = r.centerX();
- float cy = r.centerY();
- r.offset(-cx, -cy);
- r.left *= scale;
- r.top *= scale;
- r.right *= scale;
- r.bottom *= scale;
- r.offset(cx, cy);
- }
- }
-
- /** Calculates the constrast between two colors, using the algorithm provided by the WCAG v2. */
- public static float computeContrastBetweenColors(int bg, int fg) {
- float bgR = Color.red(bg) / 255f;
- float bgG = Color.green(bg) / 255f;
- float bgB = Color.blue(bg) / 255f;
- bgR = (bgR < 0.03928f) ? bgR / 12.92f : (float) Math.pow((bgR + 0.055f) / 1.055f, 2.4f);
- bgG = (bgG < 0.03928f) ? bgG / 12.92f : (float) Math.pow((bgG + 0.055f) / 1.055f, 2.4f);
- bgB = (bgB < 0.03928f) ? bgB / 12.92f : (float) Math.pow((bgB + 0.055f) / 1.055f, 2.4f);
- float bgL = 0.2126f * bgR + 0.7152f * bgG + 0.0722f * bgB;
-
- float fgR = Color.red(fg) / 255f;
- float fgG = Color.green(fg) / 255f;
- float fgB = Color.blue(fg) / 255f;
- fgR = (fgR < 0.03928f) ? fgR / 12.92f : (float) Math.pow((fgR + 0.055f) / 1.055f, 2.4f);
- fgG = (fgG < 0.03928f) ? fgG / 12.92f : (float) Math.pow((fgG + 0.055f) / 1.055f, 2.4f);
- fgB = (fgB < 0.03928f) ? fgB / 12.92f : (float) Math.pow((fgB + 0.055f) / 1.055f, 2.4f);
- float fgL = 0.2126f * fgR + 0.7152f * fgG + 0.0722f * fgB;
-
- return Math.abs((fgL + 0.05f) / (bgL + 0.05f));
- }
-
- /** Returns the base color overlaid with another overlay color with a specified alpha. */
- public static int getColorWithOverlay(int baseColor, int overlayColor, float overlayAlpha) {
- return Color.rgb(
- (int) (overlayAlpha * Color.red(baseColor) +
- (1f - overlayAlpha) * Color.red(overlayColor)),
- (int) (overlayAlpha * Color.green(baseColor) +
- (1f - overlayAlpha) * Color.green(overlayColor)),
- (int) (overlayAlpha * Color.blue(baseColor) +
- (1f - overlayAlpha) * Color.blue(overlayColor)));
- }
-
- /**
- * Cancels an animation ensuring that if it has listeners, onCancel and onEnd
- * are not called.
- */
- public static void cancelAnimationWithoutCallbacks(Animator animator) {
- if (animator != null && animator.isStarted()) {
- removeAnimationListenersRecursive(animator);
- animator.cancel();
- }
- }
-
- /**
- * Recursively removes all the listeners of all children of this animator
- */
- public static void removeAnimationListenersRecursive(Animator animator) {
- if (animator instanceof AnimatorSet) {
- ArrayList<Animator> animators = ((AnimatorSet) animator).getChildAnimations();
- for (int i = animators.size() - 1; i >= 0; i--) {
- removeAnimationListenersRecursive(animators.get(i));
- }
- }
- animator.removeAllListeners();
- }
-
- /**
- * Sets the given {@link View}'s frame from its current translation.
- */
- public static void setViewFrameFromTranslation(View v) {
- RectF taskViewRect = new RectF(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
- taskViewRect.offset(v.getTranslationX(), v.getTranslationY());
- v.setTranslationX(0);
- v.setTranslationY(0);
- v.setLeftTopRightBottom((int) taskViewRect.left, (int) taskViewRect.top,
- (int) taskViewRect.right, (int) taskViewRect.bottom);
- }
-
- /**
- * Returns a view stub for the given view id.
- */
- public static ViewStub findViewStubById(View v, int stubId) {
- return (ViewStub) v.findViewById(stubId);
- }
-
- /**
- * Returns a view stub for the given view id.
- */
- public static ViewStub findViewStubById(Activity a, int stubId) {
- return (ViewStub) a.findViewById(stubId);
- }
-
- /**
- * Updates {@param transforms} to be the same size as {@param tasks}.
- */
- public static void matchTaskListSize(List<Task> tasks, List<TaskViewTransform> transforms) {
- // We can reuse the task transforms where possible to reduce object allocation
- int taskTransformCount = transforms.size();
- int taskCount = tasks.size();
- if (taskTransformCount < taskCount) {
- // If there are less transforms than tasks, then add as many transforms as necessary
- for (int i = taskTransformCount; i < taskCount; i++) {
- transforms.add(new TaskViewTransform());
- }
- } else if (taskTransformCount > taskCount) {
- // If there are more transforms than tasks, then just subset the transform list
- transforms.subList(taskCount, taskTransformCount).clear();
- }
- }
-
- /**
- * Used for debugging, converts DP to PX.
- */
- public static float dpToPx(Resources res, float dp) {
- return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, res.getDisplayMetrics());
- }
-
- /**
- * Adds a trace event for debugging.
- */
- public static void addTraceEvent(String event) {
- Trace.traceBegin(Trace.TRACE_TAG_VIEW, event);
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
- }
-
- /**
- * Returns whether this view, or one of its descendants have accessibility focus.
- */
- public static boolean isDescendentAccessibilityFocused(View v) {
- if (v.isAccessibilityFocused()) {
- return true;
- }
-
- if (v instanceof ViewGroup) {
- ViewGroup vg = (ViewGroup) v;
- int childCount = vg.getChildCount();
- for (int i = 0; i < childCount; i++) {
- if (isDescendentAccessibilityFocused(vg.getChildAt(i))) {
- return true;
- }
- }
- }
- return false;
- }
-
- /**
- * Returns the application configuration, which is independent of the activity's current
- * configuration in multiwindow.
- */
- public static Configuration getAppConfiguration(Context context) {
- return context.getApplicationContext().getResources().getConfiguration();
- }
-
- /**
- * Returns a lightweight dump of a rect.
- */
- public static String dumpRect(Rect r) {
- if (r == null) {
- return "N:0,0-0,0";
- }
- return r.left + "," + r.top + "-" + r.right + "," + r.bottom;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/HighResThumbnailLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/HighResThumbnailLoader.java
deleted file mode 100644
index 48fa6c3c053b..000000000000
--- a/packages/SystemUI/src/com/android/systemui/recents/model/HighResThumbnailLoader.java
+++ /dev/null
@@ -1,235 +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.recents.model;
-
-import static android.os.Process.setThreadPriority;
-
-import android.os.Handler;
-import android.os.Looper;
-import android.os.SystemClock;
-import android.util.ArraySet;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.RecentsConfiguration;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.model.Task.TaskCallbacks;
-
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-
-/**
- * Loader class that loads full-resolution thumbnails when appropriate.
- */
-public class HighResThumbnailLoader implements TaskCallbacks {
-
- @GuardedBy("mLoadQueue")
- private final ArrayDeque<Task> mLoadQueue = new ArrayDeque<>();
- @GuardedBy("mLoadQueue")
- private final ArraySet<Task> mLoadingTasks = new ArraySet<>();
- @GuardedBy("mLoadQueue")
- private boolean mLoaderIdling;
-
- private final ArrayList<Task> mVisibleTasks = new ArrayList<>();
- private final Thread mLoadThread;
- private final Handler mMainThreadHandler;
- private final SystemServicesProxy mSystemServicesProxy;
- private final boolean mIsLowRamDevice;
- private boolean mLoading;
- private boolean mVisible;
- private boolean mFlingingFast;
- private boolean mTaskLoadQueueIdle;
-
- public HighResThumbnailLoader(SystemServicesProxy ssp, Looper looper, boolean isLowRamDevice) {
- mMainThreadHandler = new Handler(looper);
- mLoadThread = new Thread(mLoader, "Recents-HighResThumbnailLoader");
- mLoadThread.start();
- mSystemServicesProxy = ssp;
- mIsLowRamDevice = isLowRamDevice;
- }
-
- public void setVisible(boolean visible) {
- if (mIsLowRamDevice) {
- return;
- }
- mVisible = visible;
- updateLoading();
- }
-
- public void setFlingingFast(boolean flingingFast) {
- if (mFlingingFast == flingingFast || mIsLowRamDevice) {
- return;
- }
- mFlingingFast = flingingFast;
- updateLoading();
- }
-
- /**
- * Sets whether the other task load queue is idling. Avoid double-loading bitmaps by not
- * starting this queue until the other queue is idling.
- */
- public void setTaskLoadQueueIdle(boolean idle) {
- if (mIsLowRamDevice) {
- return;
- }
- mTaskLoadQueueIdle = idle;
- updateLoading();
- }
-
- @VisibleForTesting
- boolean isLoading() {
- return mLoading;
- }
-
- private void updateLoading() {
- setLoading(mVisible && !mFlingingFast && mTaskLoadQueueIdle);
- }
-
- private void setLoading(boolean loading) {
- if (loading == mLoading) {
- return;
- }
- synchronized (mLoadQueue) {
- mLoading = loading;
- if (!loading) {
- stopLoading();
- } else {
- startLoading();
- }
- }
- }
-
- @GuardedBy("mLoadQueue")
- private void startLoading() {
- for (int i = mVisibleTasks.size() - 1; i >= 0; i--) {
- Task t = mVisibleTasks.get(i);
- if ((t.thumbnail == null || t.thumbnail.reducedResolution)
- && !mLoadQueue.contains(t) && !mLoadingTasks.contains(t)) {
- mLoadQueue.add(t);
- }
- }
- mLoadQueue.notifyAll();
- }
-
- @GuardedBy("mLoadQueue")
- private void stopLoading() {
- mLoadQueue.clear();
- mLoadQueue.notifyAll();
- }
-
- /**
- * Needs to be called when a task becomes visible. Note that this is different from
- * {@link TaskCallbacks#onTaskDataLoaded} as this method should only be called once when it
- * becomes visible, whereas onTaskDataLoaded can be called multiple times whenever some data
- * has been updated.
- */
- public void onTaskVisible(Task t) {
- t.addCallback(this);
- mVisibleTasks.add(t);
- if ((t.thumbnail == null || t.thumbnail.reducedResolution) && mLoading) {
- synchronized (mLoadQueue) {
- mLoadQueue.add(t);
- mLoadQueue.notifyAll();
- }
- }
- }
-
- /**
- * Needs to be called when a task becomes visible. See {@link #onTaskVisible} why this is
- * different from {@link TaskCallbacks#onTaskDataUnloaded()}
- */
- public void onTaskInvisible(Task t) {
- t.removeCallback(this);
- mVisibleTasks.remove(t);
- synchronized (mLoadQueue) {
- mLoadQueue.remove(t);
- }
- }
-
- @VisibleForTesting
- void waitForLoaderIdle() {
- while (true) {
- synchronized (mLoadQueue) {
- if (mLoadQueue.isEmpty() && mLoaderIdling) {
- return;
- }
- }
- SystemClock.sleep(100);
- }
- }
-
- @Override
- public void onTaskDataLoaded(Task task, ThumbnailData thumbnailData) {
- if (thumbnailData != null && !thumbnailData.reducedResolution) {
- synchronized (mLoadQueue) {
- mLoadQueue.remove(task);
- }
- }
- }
-
- @Override
- public void onTaskDataUnloaded() {
- }
-
- @Override
- public void onTaskStackIdChanged() {
- }
-
- private final Runnable mLoader = new Runnable() {
-
- @Override
- public void run() {
- setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND + 1);
- while (true) {
- Task next = null;
- synchronized (mLoadQueue) {
- if (!mLoading || mLoadQueue.isEmpty()) {
- try {
- mLoaderIdling = true;
- mLoadQueue.wait();
- mLoaderIdling = false;
- } catch (InterruptedException e) {
- // Don't care.
- }
- } else {
- next = mLoadQueue.poll();
- if (next != null) {
- mLoadingTasks.add(next);
- }
- }
- }
- if (next != null) {
- loadTask(next);
- }
- }
- }
-
- private void loadTask(Task t) {
- ThumbnailData thumbnail = mSystemServicesProxy.getTaskThumbnail(t.key.id,
- false /* reducedResolution */);
- mMainThreadHandler.post(() -> {
- synchronized (mLoadQueue) {
- mLoadingTasks.remove(t);
- }
- if (mVisibleTasks.contains(t)) {
- t.notifyTaskDataLoaded(thumbnail, t.icon);
- }
- });
- }
- };
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
deleted file mode 100644
index 308cece1dfdd..000000000000
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.model;
-
-import android.content.Context;
-import android.os.UserHandle;
-
-import com.android.internal.content.PackageMonitor;
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.events.activity.PackagesChangedEvent;
-import com.android.systemui.recents.misc.ForegroundThread;
-
-/**
- * The package monitor listens for changes from PackageManager to update the contents of the
- * Recents list.
- */
-public class RecentsPackageMonitor extends PackageMonitor {
-
- /** Registers the broadcast receivers with the specified callbacks. */
- public void register(Context context) {
- try {
- // We register for events from all users, but will cross-reference them with
- // packages for the current user and any profiles they have. Ensure that events are
- // handled in a background thread.
- register(context, ForegroundThread.get().getLooper(), UserHandle.ALL, true);
- } catch (IllegalStateException e) {
- e.printStackTrace();
- }
- }
-
- /** Unregisters the broadcast receivers. */
- @Override
- public void unregister() {
- try {
- super.unregister();
- } catch (IllegalStateException e) {
- e.printStackTrace();
- }
- }
-
- @Override
- public void onPackageRemoved(String packageName, int uid) {
- // Notify callbacks on the main thread that a package has changed
- final int eventUserId = getChangingUserId();
- EventBus.getDefault().post(new PackagesChangedEvent(this, packageName, eventUserId));
- }
-
- @Override
- public boolean onPackageChanged(String packageName, int uid, String[] components) {
- onPackageModified(packageName);
- return true;
- }
-
- @Override
- public void onPackageModified(String packageName) {
- // Notify callbacks on the main thread that a package has changed
- final int eventUserId = getChangingUserId();
- EventBus.getDefault().post(new PackagesChangedEvent(this, packageName, eventUserId));
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
deleted file mode 100644
index 8d31730d8602..000000000000
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ /dev/null
@@ -1,327 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.model;
-
-import android.app.ActivityManager;
-import android.content.Context;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.UserInfo;
-import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.provider.Settings.Secure;
-import android.util.ArraySet;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-import android.util.SparseIntArray;
-
-import com.android.systemui.Prefs;
-import com.android.systemui.R;
-import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.RecentsDebugFlags;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.views.lowram.TaskStackLowRamLayoutAlgorithm;
-import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-
-/**
- * This class stores the loading state as it goes through multiple stages of loading:
- * 1) preloadRawTasks() will load the raw set of recents tasks from the system
- * 2) preloadPlan() will construct a new task stack with all metadata and only icons and
- * thumbnails that are currently in the cache
- * 3) executePlan() will actually load and fill in the icons and thumbnails according to the load
- * options specified, such that we can transition into the Recents activity seamlessly
- */
-public class RecentsTaskLoadPlan {
-
- private static int MIN_NUM_TASKS = 5;
- private static int SESSION_BEGIN_TIME = 1000 /* ms/s */ * 60 /* s/min */ * 60 /* min/hr */ *
- 6 /* hrs */;
-
- /** The set of conditions to load tasks. */
- public static class Options {
- public int runningTaskId = -1;
- public boolean loadIcons = true;
- public boolean loadThumbnails = false;
- public boolean onlyLoadForCache = false;
- public boolean onlyLoadPausedActivities = false;
- public int numVisibleTasks = 0;
- public int numVisibleTaskThumbnails = 0;
- }
-
- Context mContext;
-
- int mPreloadedUserId;
- List<ActivityManager.RecentTaskInfo> mRawTasks;
- TaskStack mStack;
- ArraySet<Integer> mCurrentQuietProfiles = new ArraySet<Integer>();
-
- /** Package level ctor */
- RecentsTaskLoadPlan(Context context) {
- mContext = context;
- }
-
- private void updateCurrentQuietProfilesCache(int currentUserId) {
- mCurrentQuietProfiles.clear();
-
- UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- List<UserInfo> profiles = userManager.getProfiles(currentUserId);
- if (profiles != null) {
- for (int i = 0; i < profiles.size(); i++) {
- UserInfo user = profiles.get(i);
- if (user.isManagedProfile() && user.isQuietModeEnabled()) {
- mCurrentQuietProfiles.add(user.id);
- }
- }
- }
- }
-
- /**
- * An optimization to preload the raw list of tasks. The raw tasks are saved in least-recent
- * to most-recent order.
- *
- * Note: Do not lock, callers should synchronize on the loader before making this call.
- */
- void preloadRawTasks(boolean includeFrontMostExcludedTask) {
- SystemServicesProxy ssp = Recents.getSystemServices();
- int currentUserId = ssp.getCurrentUser();
- updateCurrentQuietProfilesCache(currentUserId);
- mPreloadedUserId = currentUserId;
- mRawTasks = ssp.getRecentTasks(ActivityManager.getMaxRecentTasksStatic(),
- currentUserId, includeFrontMostExcludedTask, mCurrentQuietProfiles);
-
- // Since the raw tasks are given in most-recent to least-recent order, we need to reverse it
- Collections.reverse(mRawTasks);
- }
-
- /**
- * Preloads the list of recent tasks from the system. After this call, the TaskStack will
- * have a list of all the recent tasks with their metadata, not including icons or
- * thumbnails which were not cached and have to be loaded.
- *
- * The tasks will be ordered by:
- * - least-recent to most-recent stack tasks
- * - least-recent to most-recent freeform tasks
- *
- * Note: Do not lock, since this can be calling back to the loader, which separately also drives
- * this call (callers should synchronize on the loader before making this call).
- */
- void preloadPlan(RecentsTaskLoader loader, int runningTaskId,
- boolean includeFrontMostExcludedTask) {
- Resources res = mContext.getResources();
- ArrayList<Task> allTasks = new ArrayList<>();
- if (mRawTasks == null) {
- preloadRawTasks(includeFrontMostExcludedTask);
- }
-
- SparseArray<Task.TaskKey> affiliatedTasks = new SparseArray<>();
- SparseIntArray affiliatedTaskCounts = new SparseIntArray();
- SparseBooleanArray lockedUsers = new SparseBooleanArray();
- String dismissDescFormat = mContext.getString(
- R.string.accessibility_recents_item_will_be_dismissed);
- String appInfoDescFormat = mContext.getString(
- R.string.accessibility_recents_item_open_app_info);
- int currentUserId = mPreloadedUserId;
- long legacyLastStackActiveTime = migrateLegacyLastStackActiveTime(currentUserId);
- long lastStackActiveTime = Settings.Secure.getLongForUser(mContext.getContentResolver(),
- Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, legacyLastStackActiveTime, currentUserId);
- if (RecentsDebugFlags.Static.EnableMockTasks) {
- lastStackActiveTime = 0;
- }
- long newLastStackActiveTime = -1;
- int taskCount = mRawTasks.size();
- for (int i = 0; i < taskCount; i++) {
- ActivityManager.RecentTaskInfo t = mRawTasks.get(i);
-
- // Compose the task key
- Task.TaskKey taskKey = new Task.TaskKey(t.persistentId, t.stackId, t.baseIntent,
- t.userId, t.firstActiveTime, t.lastActiveTime);
-
- // This task is only shown in the stack if it satisfies the historical time or min
- // number of tasks constraints. Freeform tasks are also always shown.
- boolean isFreeformTask = SystemServicesProxy.isFreeformStack(t.stackId);
- boolean isStackTask;
- if (Recents.getConfiguration().isGridEnabled) {
- // When grid layout is enabled, we only show the first
- // TaskGridLayoutAlgorithm.MAX_LAYOUT_FROM_HOME_TASK_COUNT} tasks.
- isStackTask = t.lastActiveTime >= lastStackActiveTime &&
- i >= taskCount - TaskGridLayoutAlgorithm.MAX_LAYOUT_TASK_COUNT;
- } else if (Recents.getConfiguration().isLowRamDevice) {
- // Show a max of 3 items
- isStackTask = t.lastActiveTime >= lastStackActiveTime &&
- i >= taskCount - TaskStackLowRamLayoutAlgorithm.MAX_LAYOUT_TASK_COUNT;
- } else {
- isStackTask = isFreeformTask || !isHistoricalTask(t) ||
- (t.lastActiveTime >= lastStackActiveTime && i >= (taskCount - MIN_NUM_TASKS));
- }
- boolean isLaunchTarget = taskKey.id == runningTaskId;
-
- // The last stack active time is the baseline for which we show visible tasks. Since
- // the system will store all the tasks, we don't want to show the tasks prior to the
- // last visible ones, otherwise, as you dismiss them, the previous tasks may satisfy
- // the other stack-task constraints.
- if (isStackTask && newLastStackActiveTime < 0) {
- newLastStackActiveTime = t.lastActiveTime;
- }
-
- // Load the title, icon, and color
- ActivityInfo info = loader.getAndUpdateActivityInfo(taskKey);
- String title = loader.getAndUpdateActivityTitle(taskKey, t.taskDescription);
- String titleDescription = loader.getAndUpdateContentDescription(taskKey,
- t.taskDescription, res);
- String dismissDescription = String.format(dismissDescFormat, titleDescription);
- String appInfoDescription = String.format(appInfoDescFormat, titleDescription);
- Drawable icon = isStackTask
- ? loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, res, false)
- : null;
- ThumbnailData thumbnail = loader.getAndUpdateThumbnail(taskKey,
- false /* loadIfNotCached */, false /* storeInCache */);
- int activityColor = loader.getActivityPrimaryColor(t.taskDescription);
- int backgroundColor = loader.getActivityBackgroundColor(t.taskDescription);
- boolean isSystemApp = (info != null) &&
- ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0);
- if (lockedUsers.indexOfKey(t.userId) < 0) {
- lockedUsers.put(t.userId, Recents.getSystemServices().isDeviceLocked(t.userId));
- }
- boolean isLocked = lockedUsers.get(t.userId);
-
- // Add the task to the stack
- Task task = new Task(taskKey, t.affiliatedTaskId, t.affiliatedTaskColor, icon,
- thumbnail, title, titleDescription, dismissDescription, appInfoDescription,
- activityColor, backgroundColor, isLaunchTarget, isStackTask, isSystemApp,
- t.supportsSplitScreenMultiWindow, t.bounds, t.taskDescription, t.resizeMode, t.topActivity,
- isLocked);
-
- allTasks.add(task);
- affiliatedTaskCounts.put(taskKey.id, affiliatedTaskCounts.get(taskKey.id, 0) + 1);
- affiliatedTasks.put(taskKey.id, taskKey);
- }
- if (newLastStackActiveTime != -1) {
- Recents.getSystemServices().updateOverviewLastStackActiveTimeAsync(
- newLastStackActiveTime, currentUserId);
- }
-
- // Initialize the stacks
- mStack = new TaskStack();
- mStack.setTasks(mContext, allTasks, false /* notifyStackChanges */);
- }
-
- /**
- * Called to apply the actual loading based on the specified conditions.
- *
- * Note: Do not lock, since this can be calling back to the loader, which separately also drives
- * this call (callers should synchronize on the loader before making this call).
- */
- void executePlan(Options opts, RecentsTaskLoader loader) {
- Resources res = mContext.getResources();
-
- // Iterate through each of the tasks and load them according to the load conditions.
- ArrayList<Task> tasks = mStack.getStackTasks();
- int taskCount = tasks.size();
- for (int i = 0; i < taskCount; i++) {
- Task task = tasks.get(i);
- Task.TaskKey taskKey = task.key;
-
- boolean isRunningTask = (task.key.id == opts.runningTaskId);
- boolean isVisibleTask = i >= (taskCount - opts.numVisibleTasks);
- boolean isVisibleThumbnail = i >= (taskCount - opts.numVisibleTaskThumbnails);
-
- // If requested, skip the running task
- if (opts.onlyLoadPausedActivities && isRunningTask) {
- continue;
- }
-
- if (opts.loadIcons && (isRunningTask || isVisibleTask)) {
- if (task.icon == null) {
- task.icon = loader.getAndUpdateActivityIcon(taskKey, task.taskDescription, res,
- true);
- }
- }
- if (opts.loadThumbnails && isVisibleThumbnail) {
- task.thumbnail = loader.getAndUpdateThumbnail(taskKey,
- true /* loadIfNotCached */, true /* storeInCache */);
- }
- }
- }
-
- /**
- * Returns the TaskStack from the preloaded list of recent tasks.
- */
- public TaskStack getTaskStack() {
- return mStack;
- }
-
- /**
- * Returns the raw list of recent tasks.
- */
- public List<ActivityManager.RecentTaskInfo> getRawTasks() {
- return mRawTasks;
- }
-
- /** Returns whether there are any tasks in any stacks. */
- public boolean hasTasks() {
- if (mStack != null) {
- return mStack.getTaskCount() > 0;
- }
- return false;
- }
-
- /**
- * Returns whether this task is too old to be shown.
- */
- private boolean isHistoricalTask(ActivityManager.RecentTaskInfo t) {
- return t.lastActiveTime < (System.currentTimeMillis() - SESSION_BEGIN_TIME);
- }
-
-
- /**
- * Migrate the last active time from the prefs to the secure settings.
- *
- * The first time this runs, it will:
- * 1) fetch the last stack active time from the prefs
- * 2) set the prefs to the last stack active time for all users
- * 3) clear the pref
- * 4) return the last stack active time
- *
- * Subsequent calls to this will return zero.
- */
- private long migrateLegacyLastStackActiveTime(int currentUserId) {
- long legacyLastStackActiveTime = Prefs.getLong(mContext,
- Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME, -1);
- if (legacyLastStackActiveTime != -1) {
- Prefs.remove(mContext, Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME);
- UserManager userMgr = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- List<UserInfo> users = userMgr.getUsers();
- for (int i = 0; i < users.size(); i++) {
- int userId = users.get(i).id;
- if (userId != currentUserId) {
- Recents.getSystemServices().updateOverviewLastStackActiveTimeAsync(
- legacyLastStackActiveTime, userId);
- }
- }
- return legacyLastStackActiveTime;
- }
- return 0;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
deleted file mode 100644
index 1b893862cb7e..000000000000
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ /dev/null
@@ -1,663 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.model;
-
-import android.app.ActivityManager;
-import android.content.ComponentCallbacks2;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.ActivityInfo;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Trace;
-import android.util.Log;
-import android.util.LruCache;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.systemui.R;
-import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.RecentsConfiguration;
-import com.android.systemui.recents.RecentsDebugFlags;
-import com.android.systemui.recents.events.activity.PackagesChangedEvent;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-
-import java.io.PrintWriter;
-import java.util.Map;
-import java.util.concurrent.ConcurrentLinkedQueue;
-
-
-/**
- * A Task load queue
- */
-class TaskResourceLoadQueue {
-
- ConcurrentLinkedQueue<Task> mQueue = new ConcurrentLinkedQueue<Task>();
-
- /** Adds a new task to the load queue */
- void addTask(Task t) {
- if (!mQueue.contains(t)) {
- mQueue.add(t);
- }
- synchronized(this) {
- notifyAll();
- }
- }
-
- /**
- * Retrieves the next task from the load queue, as well as whether we want that task to be
- * force reloaded.
- */
- Task nextTask() {
- return mQueue.poll();
- }
-
- /** Removes a task from the load queue */
- void removeTask(Task t) {
- mQueue.remove(t);
- }
-
- /** Clears all the tasks from the load queue */
- void clearTasks() {
- mQueue.clear();
- }
-
- /** Returns whether the load queue is empty */
- boolean isEmpty() {
- return mQueue.isEmpty();
- }
-}
-
-/**
- * Task resource loader
- */
-class BackgroundTaskLoader implements Runnable {
- static String TAG = "TaskResourceLoader";
- static boolean DEBUG = false;
-
- Context mContext;
- HandlerThread mLoadThread;
- Handler mLoadThreadHandler;
- Handler mMainThreadHandler;
-
- TaskResourceLoadQueue mLoadQueue;
- TaskKeyLruCache<Drawable> mIconCache;
- BitmapDrawable mDefaultIcon;
-
- boolean mStarted;
- boolean mCancelled;
- boolean mWaitingOnLoadQueue;
-
- private final OnIdleChangedListener mOnIdleChangedListener;
-
- /** Constructor, creates a new loading thread that loads task resources in the background */
- public BackgroundTaskLoader(TaskResourceLoadQueue loadQueue,
- TaskKeyLruCache<Drawable> iconCache, BitmapDrawable defaultIcon,
- OnIdleChangedListener onIdleChangedListener) {
- mLoadQueue = loadQueue;
- mIconCache = iconCache;
- mDefaultIcon = defaultIcon;
- mMainThreadHandler = new Handler();
- mOnIdleChangedListener = onIdleChangedListener;
- mLoadThread = new HandlerThread("Recents-TaskResourceLoader",
- android.os.Process.THREAD_PRIORITY_BACKGROUND);
- mLoadThread.start();
- mLoadThreadHandler = new Handler(mLoadThread.getLooper());
- }
-
- /** Restarts the loader thread */
- void start(Context context) {
- mContext = context;
- mCancelled = false;
- if (!mStarted) {
- // Start loading on the load thread
- mStarted = true;
- mLoadThreadHandler.post(this);
- } else {
- // Notify the load thread to start loading again
- synchronized (mLoadThread) {
- mLoadThread.notifyAll();
- }
- }
- }
-
- /** Requests the loader thread to stop after the current iteration */
- void stop() {
- // Mark as cancelled for the thread to pick up
- mCancelled = true;
- // If we are waiting for the load queue for more tasks, then we can just reset the
- // Context now, since nothing is using it
- if (mWaitingOnLoadQueue) {
- mContext = null;
- }
- }
-
- @Override
- public void run() {
- while (true) {
- if (mCancelled) {
- // We have to unset the context here, since the background thread may be using it
- // when we call stop()
- mContext = null;
- // If we are cancelled, then wait until we are started again
- synchronized(mLoadThread) {
- try {
- mLoadThread.wait();
- } catch (InterruptedException ie) {
- ie.printStackTrace();
- }
- }
- } else {
- SystemServicesProxy ssp = Recents.getSystemServices();
- // If we've stopped the loader, then fall through to the above logic to wait on
- // the load thread
- if (ssp != null) {
- processLoadQueueItem(ssp);
- }
-
- // If there are no other items in the list, then just wait until something is added
- if (!mCancelled && mLoadQueue.isEmpty()) {
- synchronized(mLoadQueue) {
- try {
- mWaitingOnLoadQueue = true;
- mMainThreadHandler.post(
- () -> mOnIdleChangedListener.onIdleChanged(true));
- mLoadQueue.wait();
- mMainThreadHandler.post(
- () -> mOnIdleChangedListener.onIdleChanged(false));
- mWaitingOnLoadQueue = false;
- } catch (InterruptedException ie) {
- ie.printStackTrace();
- }
- }
- }
- }
- }
- }
-
- /**
- * This needs to be in a separate method to work around an surprising interpreter behavior:
- * The register will keep the local reference to cachedThumbnailData even if it falls out of
- * scope. Putting it into a method fixes this issue.
- */
- private void processLoadQueueItem(SystemServicesProxy ssp) {
- // Load the next item from the queue
- final Task t = mLoadQueue.nextTask();
- if (t != null) {
- Drawable cachedIcon = mIconCache.get(t.key);
-
- // Load the icon if it is stale or we haven't cached one yet
- if (cachedIcon == null) {
- cachedIcon = ssp.getBadgedTaskDescriptionIcon(t.taskDescription,
- t.key.userId, mContext.getResources());
-
- if (cachedIcon == null) {
- ActivityInfo info = ssp.getActivityInfo(
- t.key.getComponent(), t.key.userId);
- if (info != null) {
- if (DEBUG) Log.d(TAG, "Loading icon: " + t.key);
- cachedIcon = ssp.getBadgedActivityIcon(info, t.key.userId);
- }
- }
-
- if (cachedIcon == null) {
- cachedIcon = mDefaultIcon;
- }
-
- // At this point, even if we can't load the icon, we will set the
- // default icon.
- mIconCache.put(t.key, cachedIcon);
- }
-
- if (DEBUG) Log.d(TAG, "Loading thumbnail: " + t.key);
- final ThumbnailData thumbnailData = ssp.getTaskThumbnail(t.key.id,
- true /* reducedResolution */);
-
- if (!mCancelled) {
- // Notify that the task data has changed
- final Drawable finalIcon = cachedIcon;
- mMainThreadHandler.post(
- () -> t.notifyTaskDataLoaded(thumbnailData, finalIcon));
- }
- }
- }
-
- interface OnIdleChangedListener {
- void onIdleChanged(boolean idle);
- }
-}
-
-/**
- * Recents task loader
- */
-public class RecentsTaskLoader {
-
- private static final String TAG = "RecentsTaskLoader";
- private static final boolean DEBUG = false;
-
- // This activity info LruCache is useful because it can be expensive to retrieve ActivityInfos
- // for many tasks, which we use to get the activity labels and icons. Unlike the other caches
- // below, this is per-package so we can't invalidate the items in the cache based on the last
- // active time. Instead, we rely on the RecentsPackageMonitor to keep us informed whenever a
- // package in the cache has been updated, so that we may remove it.
- private final LruCache<ComponentName, ActivityInfo> mActivityInfoCache;
- private final TaskKeyLruCache<Drawable> mIconCache;
- private final TaskKeyLruCache<String> mActivityLabelCache;
- private final TaskKeyLruCache<String> mContentDescriptionCache;
- private final TaskResourceLoadQueue mLoadQueue;
- private final BackgroundTaskLoader mLoader;
- private final HighResThumbnailLoader mHighResThumbnailLoader;
- @GuardedBy("this")
- private final TaskKeyStrongCache<ThumbnailData> mThumbnailCache = new TaskKeyStrongCache<>();
- @GuardedBy("this")
- private final TaskKeyStrongCache<ThumbnailData> mTempCache = new TaskKeyStrongCache<>();
- private final int mMaxThumbnailCacheSize;
- private final int mMaxIconCacheSize;
- private int mNumVisibleTasksLoaded;
-
- int mDefaultTaskBarBackgroundColor;
- int mDefaultTaskViewBackgroundColor;
- BitmapDrawable mDefaultIcon;
-
- private TaskKeyLruCache.EvictionCallback mClearActivityInfoOnEviction =
- new TaskKeyLruCache.EvictionCallback() {
- @Override
- public void onEntryEvicted(Task.TaskKey key) {
- if (key != null) {
- mActivityInfoCache.remove(key.getComponent());
- }
- }
- };
-
- public RecentsTaskLoader(Context context) {
- Resources res = context.getResources();
- mDefaultTaskBarBackgroundColor =
- context.getColor(R.color.recents_task_bar_default_background_color);
- mDefaultTaskViewBackgroundColor =
- context.getColor(R.color.recents_task_view_default_background_color);
- mMaxThumbnailCacheSize = res.getInteger(R.integer.config_recents_max_thumbnail_count);
- mMaxIconCacheSize = res.getInteger(R.integer.config_recents_max_icon_count);
- int iconCacheSize = RecentsDebugFlags.Static.DisableBackgroundCache ? 1 :
- mMaxIconCacheSize;
-
- // Create the default assets
- Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8);
- icon.eraseColor(0);
- mDefaultIcon = new BitmapDrawable(context.getResources(), icon);
-
- // Initialize the proxy, cache and loaders
- int numRecentTasks = ActivityManager.getMaxRecentTasksStatic();
- mHighResThumbnailLoader = new HighResThumbnailLoader(Recents.getSystemServices(),
- Looper.getMainLooper(), Recents.getConfiguration().isLowRamDevice);
- mLoadQueue = new TaskResourceLoadQueue();
- mIconCache = new TaskKeyLruCache<>(iconCacheSize, mClearActivityInfoOnEviction);
- mActivityLabelCache = new TaskKeyLruCache<>(numRecentTasks, mClearActivityInfoOnEviction);
- mContentDescriptionCache = new TaskKeyLruCache<>(numRecentTasks,
- mClearActivityInfoOnEviction);
- mActivityInfoCache = new LruCache(numRecentTasks);
- mLoader = new BackgroundTaskLoader(mLoadQueue, mIconCache, mDefaultIcon,
- mHighResThumbnailLoader::setTaskLoadQueueIdle);
- }
-
- /** Returns the size of the app icon cache. */
- public int getIconCacheSize() {
- return mMaxIconCacheSize;
- }
-
- /** Returns the size of the thumbnail cache. */
- public int getThumbnailCacheSize() {
- return mMaxThumbnailCacheSize;
- }
-
- public HighResThumbnailLoader getHighResThumbnailLoader() {
- return mHighResThumbnailLoader;
- }
-
- /** Creates a new plan for loading the recent tasks. */
- public RecentsTaskLoadPlan createLoadPlan(Context context) {
- RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(context);
- return plan;
- }
-
- /** Preloads raw recents tasks using the specified plan to store the output. */
- public synchronized void preloadRawTasks(RecentsTaskLoadPlan plan,
- boolean includeFrontMostExcludedTask) {
- plan.preloadRawTasks(includeFrontMostExcludedTask);
- }
-
- /** Preloads recents tasks using the specified plan to store the output. */
- public synchronized void preloadTasks(RecentsTaskLoadPlan plan, int runningTaskId,
- boolean includeFrontMostExcludedTask) {
- try {
- Trace.beginSection("preloadPlan");
- plan.preloadPlan(this, runningTaskId, includeFrontMostExcludedTask);
- } finally {
- Trace.endSection();
- }
- }
-
- /** Begins loading the heavy task data according to the specified options. */
- public synchronized void loadTasks(Context context, RecentsTaskLoadPlan plan,
- RecentsTaskLoadPlan.Options opts) {
- if (opts == null) {
- throw new RuntimeException("Requires load options");
- }
- if (opts.onlyLoadForCache && opts.loadThumbnails) {
-
- // If we are loading for the cache, we'd like to have the real cache only include the
- // visible thumbnails. However, we also don't want to reload already cached thumbnails.
- // Thus, we copy over the current entries into a second cache, and clear the real cache,
- // such that the real cache only contains visible thumbnails.
- mTempCache.copyEntries(mThumbnailCache);
- mThumbnailCache.evictAll();
- }
- plan.executePlan(opts, this);
- mTempCache.evictAll();
- if (!opts.onlyLoadForCache) {
- mNumVisibleTasksLoaded = opts.numVisibleTasks;
- }
- }
-
- /**
- * Acquires the task resource data directly from the cache, loading if necessary.
- */
- public void loadTaskData(Task t) {
- Drawable icon = mIconCache.getAndInvalidateIfModified(t.key);
- icon = icon != null ? icon : mDefaultIcon;
- mLoadQueue.addTask(t);
- t.notifyTaskDataLoaded(t.thumbnail, icon);
- }
-
- /** Releases the task resource data back into the pool. */
- public void unloadTaskData(Task t) {
- mLoadQueue.removeTask(t);
- t.notifyTaskDataUnloaded(mDefaultIcon);
- }
-
- /** Completely removes the resource data from the pool. */
- public void deleteTaskData(Task t, boolean notifyTaskDataUnloaded) {
- mLoadQueue.removeTask(t);
- mIconCache.remove(t.key);
- mActivityLabelCache.remove(t.key);
- mContentDescriptionCache.remove(t.key);
- if (notifyTaskDataUnloaded) {
- t.notifyTaskDataUnloaded(mDefaultIcon);
- }
- }
-
- /**
- * Handles signals from the system, trimming memory when requested to prevent us from running
- * out of memory.
- */
- public synchronized void onTrimMemory(int level) {
- switch (level) {
- case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
- // Stop the loader immediately when the UI is no longer visible
- stopLoader();
- mIconCache.trimToSize(Math.max(mNumVisibleTasksLoaded,
- mMaxIconCacheSize / 2));
- break;
- case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
- case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
- // We are leaving recents, so trim the data a bit
- mIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 2));
- mActivityInfoCache.trimToSize(Math.max(1,
- ActivityManager.getMaxRecentTasksStatic() / 2));
- break;
- case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
- case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
- // We are going to be low on memory
- mIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 4));
- mActivityInfoCache.trimToSize(Math.max(1,
- ActivityManager.getMaxRecentTasksStatic() / 4));
- break;
- case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
- case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
- // We are low on memory, so release everything
- mIconCache.evictAll();
- mActivityInfoCache.evictAll();
- // The cache is small, only clear the label cache when we are critical
- mActivityLabelCache.evictAll();
- mContentDescriptionCache.evictAll();
- mThumbnailCache.evictAll();
- break;
- default:
- break;
- }
- }
-
- /**
- * Returns the cached task label if the task key is not expired, updating the cache if it is.
- */
- String getAndUpdateActivityTitle(Task.TaskKey taskKey, ActivityManager.TaskDescription td) {
- SystemServicesProxy ssp = Recents.getSystemServices();
-
- // Return the task description label if it exists
- if (td != null && td.getLabel() != null) {
- return td.getLabel();
- }
- // Return the cached activity label if it exists
- String label = mActivityLabelCache.getAndInvalidateIfModified(taskKey);
- if (label != null) {
- return label;
- }
- // All short paths failed, load the label from the activity info and cache it
- ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey);
- if (activityInfo != null) {
- label = ssp.getBadgedActivityLabel(activityInfo, taskKey.userId);
- mActivityLabelCache.put(taskKey, label);
- return label;
- }
- // If the activity info does not exist or fails to load, return an empty label for now,
- // but do not cache it
- return "";
- }
-
- /**
- * Returns the cached task content description if the task key is not expired, updating the
- * cache if it is.
- */
- String getAndUpdateContentDescription(Task.TaskKey taskKey, ActivityManager.TaskDescription td,
- Resources res) {
- SystemServicesProxy ssp = Recents.getSystemServices();
-
- // Return the cached content description if it exists
- String label = mContentDescriptionCache.getAndInvalidateIfModified(taskKey);
- if (label != null) {
- return label;
- }
-
- // All short paths failed, load the label from the activity info and cache it
- ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey);
- if (activityInfo != null) {
- label = ssp.getBadgedContentDescription(activityInfo, taskKey.userId, td, res);
- if (td == null) {
- // Only add to the cache if the task description is null, otherwise, it is possible
- // for the task description to change between calls without the last active time
- // changing (ie. between preloading and Overview starting) which would lead to stale
- // content descriptions
- // TODO: Investigate improving this
- mContentDescriptionCache.put(taskKey, label);
- }
- return label;
- }
- // If the content description does not exist, return an empty label for now, but do not
- // cache it
- return "";
- }
-
- /**
- * Returns the cached task icon if the task key is not expired, updating the cache if it is.
- */
- Drawable getAndUpdateActivityIcon(Task.TaskKey taskKey, ActivityManager.TaskDescription td,
- Resources res, boolean loadIfNotCached) {
- SystemServicesProxy ssp = Recents.getSystemServices();
-
- // Return the cached activity icon if it exists
- Drawable icon = mIconCache.getAndInvalidateIfModified(taskKey);
- if (icon != null) {
- return icon;
- }
-
- if (loadIfNotCached) {
- // Return and cache the task description icon if it exists
- icon = ssp.getBadgedTaskDescriptionIcon(td, taskKey.userId, res);
- if (icon != null) {
- mIconCache.put(taskKey, icon);
- return icon;
- }
-
- // Load the icon from the activity info and cache it
- ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey);
- if (activityInfo != null) {
- icon = ssp.getBadgedActivityIcon(activityInfo, taskKey.userId);
- if (icon != null) {
- mIconCache.put(taskKey, icon);
- return icon;
- }
- }
- }
- // We couldn't load any icon
- return null;
- }
-
- /**
- * Returns the cached thumbnail if the task key is not expired, updating the cache if it is.
- */
- synchronized ThumbnailData getAndUpdateThumbnail(Task.TaskKey taskKey, boolean loadIfNotCached,
- boolean storeInCache) {
- SystemServicesProxy ssp = Recents.getSystemServices();
-
- ThumbnailData cached = mThumbnailCache.getAndInvalidateIfModified(taskKey);
- if (cached != null) {
- return cached;
- }
-
- cached = mTempCache.getAndInvalidateIfModified(taskKey);
- if (cached != null) {
- mThumbnailCache.put(taskKey, cached);
- return cached;
- }
-
- if (loadIfNotCached) {
- RecentsConfiguration config = Recents.getConfiguration();
- if (config.svelteLevel < RecentsConfiguration.SVELTE_DISABLE_LOADING) {
- // Load the thumbnail from the system
- ThumbnailData thumbnailData = ssp.getTaskThumbnail(taskKey.id,
- true /* reducedResolution */);
- if (thumbnailData.thumbnail != null) {
- if (storeInCache) {
- mThumbnailCache.put(taskKey, thumbnailData);
- }
- return thumbnailData;
- }
- }
- }
-
- // We couldn't load any thumbnail
- return null;
- }
-
- /**
- * Returns the task's primary color if possible, defaulting to the default color if there is
- * no specified primary color.
- */
- int getActivityPrimaryColor(ActivityManager.TaskDescription td) {
- if (td != null && td.getPrimaryColor() != 0) {
- return td.getPrimaryColor();
- }
- return mDefaultTaskBarBackgroundColor;
- }
-
- /**
- * Returns the task's background color if possible.
- */
- int getActivityBackgroundColor(ActivityManager.TaskDescription td) {
- if (td != null && td.getBackgroundColor() != 0) {
- return td.getBackgroundColor();
- }
- return mDefaultTaskViewBackgroundColor;
- }
-
- /**
- * Returns the activity info for the given task key, retrieving one from the system if the
- * task key is expired.
- */
- ActivityInfo getAndUpdateActivityInfo(Task.TaskKey taskKey) {
- SystemServicesProxy ssp = Recents.getSystemServices();
- ComponentName cn = taskKey.getComponent();
- ActivityInfo activityInfo = mActivityInfoCache.get(cn);
- if (activityInfo == null) {
- activityInfo = ssp.getActivityInfo(cn, taskKey.userId);
- if (cn == null || activityInfo == null) {
- Log.e(TAG, "Unexpected null component name or activity info: " + cn + ", " +
- activityInfo);
- return null;
- }
- mActivityInfoCache.put(cn, activityInfo);
- }
- return activityInfo;
- }
-
- /**
- * Starts loading tasks.
- */
- public void startLoader(Context ctx) {
- mLoader.start(ctx);
- }
-
- /**
- * Stops the task loader and clears all queued, pending task loads.
- */
- private void stopLoader() {
- mLoader.stop();
- mLoadQueue.clearTasks();
- }
-
- /**** Event Bus Events ****/
-
- public final void onBusEvent(PackagesChangedEvent event) {
- // Remove all the cached activity infos for this package. The other caches do not need to
- // be pruned at this time, as the TaskKey expiration checks will flush them next time their
- // cached contents are requested
- Map<ComponentName, ActivityInfo> activityInfoCache = mActivityInfoCache.snapshot();
- for (ComponentName cn : activityInfoCache.keySet()) {
- if (cn.getPackageName().equals(event.packageName)) {
- if (DEBUG) {
- Log.d(TAG, "Removing activity info from cache: " + cn);
- }
- mActivityInfoCache.remove(cn);
- }
- }
- }
-
- public synchronized void dump(String prefix, PrintWriter writer) {
- String innerPrefix = prefix + " ";
-
- writer.print(prefix); writer.println(TAG);
- writer.print(prefix); writer.println("Icon Cache");
- mIconCache.dump(innerPrefix, writer);
- writer.print(prefix); writer.println("Thumbnail Cache");
- mThumbnailCache.dump(innerPrefix, writer);
- writer.print(prefix); writer.println("Temp Thumbnail Cache");
- mTempCache.dump(innerPrefix, writer);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
deleted file mode 100644
index 29d0a2372769..000000000000
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ /dev/null
@@ -1,369 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.model;
-
-import android.app.ActivityManager;
-import android.app.ActivityManager.TaskThumbnail;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.view.ViewDebug;
-
-import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.Utilities;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Objects;
-
-
-/**
- * A task represents the top most task in the system's task stack.
- */
-public class Task {
-
- public static final String TAG = "Task";
-
- /* Task callbacks */
- public interface TaskCallbacks {
- /* Notifies when a task has been bound */
- public void onTaskDataLoaded(Task task, ThumbnailData thumbnailData);
- /* Notifies when a task has been unbound */
- public void onTaskDataUnloaded();
- /* Notifies when a task's stack id has changed. */
- public void onTaskStackIdChanged();
- }
-
- /* The Task Key represents the unique primary key for the task */
- public static class TaskKey {
- @ViewDebug.ExportedProperty(category="recents")
- public final int id;
- @ViewDebug.ExportedProperty(category="recents")
- public int stackId;
- @ViewDebug.ExportedProperty(category="recents")
- public final Intent baseIntent;
- @ViewDebug.ExportedProperty(category="recents")
- public final int userId;
- @ViewDebug.ExportedProperty(category="recents")
- public long firstActiveTime;
- @ViewDebug.ExportedProperty(category="recents")
- public long lastActiveTime;
-
- private int mHashCode;
-
- public TaskKey(int id, int stackId, Intent intent, int userId, long firstActiveTime,
- long lastActiveTime) {
- this.id = id;
- this.stackId = stackId;
- this.baseIntent = intent;
- this.userId = userId;
- this.firstActiveTime = firstActiveTime;
- this.lastActiveTime = lastActiveTime;
- updateHashCode();
- }
-
- public void setStackId(int stackId) {
- this.stackId = stackId;
- updateHashCode();
- }
-
- public ComponentName getComponent() {
- return this.baseIntent.getComponent();
- }
-
- @Override
- public boolean equals(Object o) {
- if (!(o instanceof TaskKey)) {
- return false;
- }
- TaskKey otherKey = (TaskKey) o;
- return id == otherKey.id && stackId == otherKey.stackId && userId == otherKey.userId;
- }
-
- @Override
- public int hashCode() {
- return mHashCode;
- }
-
- @Override
- public String toString() {
- return "id=" + id + " stackId=" + stackId + " user=" + userId + " lastActiveTime=" +
- lastActiveTime;
- }
-
- private void updateHashCode() {
- mHashCode = Objects.hash(id, stackId, userId);
- }
- }
-
- @ViewDebug.ExportedProperty(deepExport=true, prefix="key_")
- public TaskKey key;
-
- /**
- * The temporary sort index in the stack, used when ordering the stack.
- */
- public int temporarySortIndexInStack;
-
- /**
- * The group will be computed separately from the initialization of the task
- */
- @ViewDebug.ExportedProperty(deepExport=true, prefix="group_")
- public TaskGrouping group;
- /**
- * The affiliationTaskId is the task id of the parent task or itself if it is not affiliated
- * with any task.
- */
- @ViewDebug.ExportedProperty(category="recents")
- public int affiliationTaskId;
- @ViewDebug.ExportedProperty(category="recents")
- public int affiliationColor;
-
- /**
- * The icon is the task description icon (if provided), which falls back to the activity icon,
- * which can then fall back to the application icon.
- */
- public Drawable icon;
- public ThumbnailData thumbnail;
- @ViewDebug.ExportedProperty(category="recents")
- public String title;
- @ViewDebug.ExportedProperty(category="recents")
- public String titleDescription;
- @ViewDebug.ExportedProperty(category="recents")
- public String dismissDescription;
- @ViewDebug.ExportedProperty(category="recents")
- public String appInfoDescription;
- @ViewDebug.ExportedProperty(category="recents")
- public int colorPrimary;
- @ViewDebug.ExportedProperty(category="recents")
- public int colorBackground;
- @ViewDebug.ExportedProperty(category="recents")
- public boolean useLightOnPrimaryColor;
-
- /**
- * The bounds of the task, used only if it is a freeform task.
- */
- @ViewDebug.ExportedProperty(category="recents")
- public Rect bounds;
-
- /**
- * The task description for this task, only used to reload task icons.
- */
- public ActivityManager.TaskDescription taskDescription;
-
- /**
- * The state isLaunchTarget will be set for the correct task upon launching Recents.
- */
- @ViewDebug.ExportedProperty(category="recents")
- public boolean isLaunchTarget;
- @ViewDebug.ExportedProperty(category="recents")
- public boolean isStackTask;
- @ViewDebug.ExportedProperty(category="recents")
- public boolean isSystemApp;
- @ViewDebug.ExportedProperty(category="recents")
- public boolean isDockable;
-
- /**
- * Resize mode. See {@link ActivityInfo#resizeMode}.
- */
- @ViewDebug.ExportedProperty(category="recents")
- public int resizeMode;
-
- @ViewDebug.ExportedProperty(category="recents")
- public ComponentName topActivity;
-
- @ViewDebug.ExportedProperty(category="recents")
- public boolean isLocked;
-
- private ArrayList<TaskCallbacks> mCallbacks = new ArrayList<>();
-
- public Task() {
- // Do nothing
- }
-
- public Task(TaskKey key, int affiliationTaskId, int affiliationColor, Drawable icon,
- ThumbnailData thumbnail, String title, String titleDescription,
- String dismissDescription, String appInfoDescription, int colorPrimary,
- int colorBackground, boolean isLaunchTarget, boolean isStackTask, boolean isSystemApp,
- boolean isDockable, Rect bounds, ActivityManager.TaskDescription taskDescription,
- int resizeMode, ComponentName topActivity, boolean isLocked) {
- boolean isInAffiliationGroup = (affiliationTaskId != key.id);
- boolean hasAffiliationGroupColor = isInAffiliationGroup && (affiliationColor != 0);
- this.key = key;
- this.affiliationTaskId = affiliationTaskId;
- this.affiliationColor = affiliationColor;
- this.icon = icon;
- this.thumbnail = thumbnail;
- this.title = title;
- this.titleDescription = titleDescription;
- this.dismissDescription = dismissDescription;
- this.appInfoDescription = appInfoDescription;
- this.colorPrimary = hasAffiliationGroupColor ? affiliationColor : colorPrimary;
- this.colorBackground = colorBackground;
- this.useLightOnPrimaryColor = Utilities.computeContrastBetweenColors(this.colorPrimary,
- Color.WHITE) > 3f;
- this.bounds = bounds;
- this.taskDescription = taskDescription;
- this.isLaunchTarget = isLaunchTarget;
- this.isStackTask = isStackTask;
- this.isSystemApp = isSystemApp;
- this.isDockable = isDockable;
- this.resizeMode = resizeMode;
- this.topActivity = topActivity;
- this.isLocked = isLocked;
- }
-
- /**
- * Copies the metadata from another task, but retains the current callbacks.
- */
- public void copyFrom(Task o) {
- this.key = o.key;
- this.group = o.group;
- this.affiliationTaskId = o.affiliationTaskId;
- this.affiliationColor = o.affiliationColor;
- this.icon = o.icon;
- this.thumbnail = o.thumbnail;
- this.title = o.title;
- this.titleDescription = o.titleDescription;
- this.dismissDescription = o.dismissDescription;
- this.appInfoDescription = o.appInfoDescription;
- this.colorPrimary = o.colorPrimary;
- this.colorBackground = o.colorBackground;
- this.useLightOnPrimaryColor = o.useLightOnPrimaryColor;
- this.bounds = o.bounds;
- this.taskDescription = o.taskDescription;
- this.isLaunchTarget = o.isLaunchTarget;
- this.isStackTask = o.isStackTask;
- this.isSystemApp = o.isSystemApp;
- this.isDockable = o.isDockable;
- this.resizeMode = o.resizeMode;
- this.isLocked = o.isLocked;
- this.topActivity = o.topActivity;
- }
-
- /**
- * Add a callback.
- */
- public void addCallback(TaskCallbacks cb) {
- if (!mCallbacks.contains(cb)) {
- mCallbacks.add(cb);
- }
- }
-
- /**
- * Remove a callback.
- */
- public void removeCallback(TaskCallbacks cb) {
- mCallbacks.remove(cb);
- }
-
- /** Set the grouping */
- public void setGroup(TaskGrouping group) {
- this.group = group;
- }
-
- /**
- * Updates the stack id of this task.
- */
- public void setStackId(int stackId) {
- key.setStackId(stackId);
- int callbackCount = mCallbacks.size();
- for (int i = 0; i < callbackCount; i++) {
- mCallbacks.get(i).onTaskStackIdChanged();
- }
- }
-
- /**
- * Returns whether this task is on the freeform task stack.
- */
- public boolean isFreeformTask() {
- SystemServicesProxy ssp = Recents.getSystemServices();
- return ssp.hasFreeformWorkspaceSupport() && ssp.isFreeformStack(key.stackId);
- }
-
- /** Notifies the callback listeners that this task has been loaded */
- public void notifyTaskDataLoaded(ThumbnailData thumbnailData, Drawable applicationIcon) {
- this.icon = applicationIcon;
- this.thumbnail = thumbnailData;
- int callbackCount = mCallbacks.size();
- for (int i = 0; i < callbackCount; i++) {
- mCallbacks.get(i).onTaskDataLoaded(this, thumbnailData);
- }
- }
-
- /** Notifies the callback listeners that this task has been unloaded */
- public void notifyTaskDataUnloaded(Drawable defaultApplicationIcon) {
- icon = defaultApplicationIcon;
- thumbnail = null;
- for (int i = mCallbacks.size() - 1; i >= 0; i--) {
- mCallbacks.get(i).onTaskDataUnloaded();
- }
- }
-
- /**
- * Returns whether this task is affiliated with another task.
- */
- public boolean isAffiliatedTask() {
- return key.id != affiliationTaskId;
- }
-
- /**
- * Returns the top activity component.
- */
- public ComponentName getTopComponent() {
- return topActivity != null
- ? topActivity
- : key.baseIntent.getComponent();
- }
-
- @Override
- public boolean equals(Object o) {
- // Check that the id matches
- Task t = (Task) o;
- return key.equals(t.key);
- }
-
- @Override
- public String toString() {
- return "[" + key.toString() + "] " + title;
- }
-
- public void dump(String prefix, PrintWriter writer) {
- writer.print(prefix); writer.print(key);
- if (isAffiliatedTask()) {
- writer.print(" "); writer.print("affTaskId=" + affiliationTaskId);
- }
- if (!isDockable) {
- writer.print(" dockable=N");
- }
- if (isLaunchTarget) {
- writer.print(" launchTarget=Y");
- }
- if (isFreeformTask()) {
- writer.print(" freeform=Y");
- }
- if (isLocked) {
- writer.print(" locked=Y");
- }
- writer.print(" "); writer.print(title);
- writer.println();
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskGrouping.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskGrouping.java
deleted file mode 100644
index 2109376d4ff3..000000000000
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskGrouping.java
+++ /dev/null
@@ -1,106 +0,0 @@
-package com.android.systemui.recents.model;
-
-import android.util.ArrayMap;
-
-import java.util.ArrayList;
-
-/** Represents a grouping of tasks witihin a stack. */
-public class TaskGrouping {
-
- int affiliation;
- long latestActiveTimeInGroup;
-
- Task.TaskKey mFrontMostTaskKey;
- ArrayList<Task.TaskKey> mTaskKeys = new ArrayList<Task.TaskKey>();
- ArrayMap<Task.TaskKey, Integer> mTaskKeyIndices = new ArrayMap<>();
-
- /** Creates a group with a specified affiliation. */
- public TaskGrouping(int affiliation) {
- this.affiliation = affiliation;
- }
-
- /** Adds a new task to this group. */
- void addTask(Task t) {
- mTaskKeys.add(t.key);
- if (t.key.lastActiveTime > latestActiveTimeInGroup) {
- latestActiveTimeInGroup = t.key.lastActiveTime;
- }
- t.setGroup(this);
- updateTaskIndices();
- }
-
- /** Removes a task from this group. */
- void removeTask(Task t) {
- mTaskKeys.remove(t.key);
- latestActiveTimeInGroup = 0;
- int taskCount = mTaskKeys.size();
- for (int i = 0; i < taskCount; i++) {
- long lastActiveTime = mTaskKeys.get(i).lastActiveTime;
- if (lastActiveTime > latestActiveTimeInGroup) {
- latestActiveTimeInGroup = lastActiveTime;
- }
- }
- t.setGroup(null);
- updateTaskIndices();
- }
-
- /** Returns the key of the next task in the group. */
- public Task.TaskKey getNextTaskInGroup(Task t) {
- int i = indexOf(t);
- if ((i + 1) < getTaskCount()) {
- return mTaskKeys.get(i + 1);
- }
- return null;
- }
-
- /** Returns the key of the previous task in the group. */
- public Task.TaskKey getPrevTaskInGroup(Task t) {
- int i = indexOf(t);
- if ((i - 1) >= 0) {
- return mTaskKeys.get(i - 1);
- }
- return null;
- }
-
- /** Gets the front task */
- public boolean isFrontMostTask(Task t) {
- return (t.key == mFrontMostTaskKey);
- }
-
- /** Finds the index of a given task in a group. */
- public int indexOf(Task t) {
- return mTaskKeyIndices.get(t.key);
- }
-
- /** Returns whether a task is in this grouping. */
- public boolean containsTask(Task t) {
- return mTaskKeyIndices.containsKey(t.key);
- }
-
- /** Returns whether one task is above another in the group. If they are not in the same group,
- * this returns false. */
- public boolean isTaskAboveTask(Task t, Task below) {
- return mTaskKeyIndices.containsKey(t.key) && mTaskKeyIndices.containsKey(below.key) &&
- mTaskKeyIndices.get(t.key) > mTaskKeyIndices.get(below.key);
- }
-
- /** Returns the number of tasks in this group. */
- public int getTaskCount() { return mTaskKeys.size(); }
-
- /** Updates the mapping of tasks to indices. */
- private void updateTaskIndices() {
- if (mTaskKeys.isEmpty()) {
- mFrontMostTaskKey = null;
- mTaskKeyIndices.clear();
- return;
- }
-
- int taskCount = mTaskKeys.size();
- mFrontMostTaskKey = mTaskKeys.get(mTaskKeys.size() - 1);
- mTaskKeyIndices.clear();
- for (int i = 0; i < taskCount; i++) {
- Task.TaskKey k = mTaskKeys.get(i);
- mTaskKeyIndices.put(k, i);
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyCache.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyCache.java
deleted file mode 100644
index be99f93028dc..000000000000
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyCache.java
+++ /dev/null
@@ -1,89 +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.recents.model;
-
-import android.util.Log;
-import android.util.SparseArray;
-
-import com.android.systemui.recents.model.Task.TaskKey;
-
-/**
- * Base class for both strong and LRU task key cache.
- */
-public abstract class TaskKeyCache<V> {
-
- protected static final String TAG = "TaskKeyCache";
-
- protected final SparseArray<TaskKey> mKeys = new SparseArray<>();
-
- /**
- * Gets a specific entry in the cache with the specified key, regardless of whether the cached
- * value is valid or not.
- */
- final V get(Task.TaskKey key) {
- return getCacheEntry(key.id);
- }
-
- /**
- * Returns the value only if the key is valid (has not been updated since the last time it was
- * in the cache)
- */
- final V getAndInvalidateIfModified(Task.TaskKey key) {
- Task.TaskKey lastKey = mKeys.get(key.id);
- if (lastKey != null) {
- if ((lastKey.stackId != key.stackId) ||
- (lastKey.lastActiveTime != key.lastActiveTime)) {
- // The task has updated (been made active since the last time it was put into the
- // LRU cache) or the stack id for the task has changed, invalidate that cache item
- remove(key);
- return null;
- }
- }
- // Either the task does not exist in the cache, or the last active time is the same as
- // the key specified, so return what is in the cache
- return getCacheEntry(key.id);
- }
-
- /** Puts an entry in the cache for a specific key. */
- final void put(Task.TaskKey key, V value) {
- if (key == null || value == null) {
- Log.e(TAG, "Unexpected null key or value: " + key + ", " + value);
- return;
- }
- mKeys.put(key.id, key);
- putCacheEntry(key.id, value);
- }
-
-
- /** Removes a cache entry for a specific key. */
- final void remove(Task.TaskKey key) {
- // Remove the key after the cache value because we need it to make the callback
- removeCacheEntry(key.id);
- mKeys.remove(key.id);
- }
-
- /** Removes all the entries in the cache. */
- final void evictAll() {
- evictAllCache();
- mKeys.clear();
- }
-
- protected abstract V getCacheEntry(int id);
- protected abstract void putCacheEntry(int id, V value);
- protected abstract void removeCacheEntry(int id);
- protected abstract void evictAllCache();
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyLruCache.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyLruCache.java
deleted file mode 100644
index 778df6be399b..000000000000
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyLruCache.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.model;
-
-import android.util.LruCache;
-
-import java.io.PrintWriter;
-
-/**
- * A mapping of {@link Task.TaskKey} to value, with additional LRU functionality where the least
- * recently referenced key/values will be evicted as more values than the given cache size are
- * inserted.
- *
- * In addition, this also allows the caller to invalidate cached values for keys that have since
- * changed.
- */
-public class TaskKeyLruCache<V> extends TaskKeyCache<V> {
-
- public interface EvictionCallback {
- public void onEntryEvicted(Task.TaskKey key);
- }
-
- private final LruCache<Integer, V> mCache;
- private final EvictionCallback mEvictionCallback;
-
- public TaskKeyLruCache(int cacheSize) {
- this(cacheSize, null);
- }
-
- public TaskKeyLruCache(int cacheSize, EvictionCallback evictionCallback) {
- mEvictionCallback = evictionCallback;
- mCache = new LruCache<Integer, V>(cacheSize) {
-
- @Override
- protected void entryRemoved(boolean evicted, Integer taskId, V oldV, V newV) {
- if (mEvictionCallback != null) {
- mEvictionCallback.onEntryEvicted(mKeys.get(taskId));
- }
- mKeys.remove(taskId);
- }
- };
- }
-
- /** Trims the cache to a specific size */
- final void trimToSize(int cacheSize) {
- mCache.trimToSize(cacheSize);
- }
-
- public void dump(String prefix, PrintWriter writer) {
- String innerPrefix = prefix + " ";
-
- writer.print(prefix); writer.print(TAG);
- writer.print(" numEntries="); writer.print(mKeys.size());
- writer.println();
- int keyCount = mKeys.size();
- for (int i = 0; i < keyCount; i++) {
- writer.print(innerPrefix); writer.println(mKeys.get(mKeys.keyAt(i)));
- }
- }
-
- @Override
- protected V getCacheEntry(int id) {
- return mCache.get(id);
- }
-
- @Override
- protected void putCacheEntry(int id, V value) {
- mCache.put(id, value);
- }
-
- @Override
- protected void removeCacheEntry(int id) {
- mCache.remove(id);
- }
-
- @Override
- protected void evictAllCache() {
- mCache.evictAll();
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyStrongCache.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyStrongCache.java
deleted file mode 100644
index c84df8a14288..000000000000
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyStrongCache.java
+++ /dev/null
@@ -1,73 +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.recents.model;
-
-import android.util.ArrayMap;
-import android.util.Log;
-import android.util.SparseArray;
-
-import com.android.systemui.recents.model.Task.TaskKey;
-
-import java.io.PrintWriter;
-
-/**
- * Like {@link TaskKeyLruCache}, but without LRU functionality.
- */
-public class TaskKeyStrongCache<V> extends TaskKeyCache<V> {
-
- private static final String TAG = "TaskKeyCache";
-
- private final ArrayMap<Integer, V> mCache = new ArrayMap<>();
-
- final void copyEntries(TaskKeyStrongCache<V> other) {
- for (int i = other.mKeys.size() - 1; i >= 0; i--) {
- TaskKey key = other.mKeys.valueAt(i);
- put(key, other.mCache.get(key.id));
- }
- }
-
- public void dump(String prefix, PrintWriter writer) {
- String innerPrefix = prefix + " ";
- writer.print(prefix); writer.print(TAG);
- writer.print(" numEntries="); writer.print(mKeys.size());
- writer.println();
- int keyCount = mKeys.size();
- for (int i = 0; i < keyCount; i++) {
- writer.print(innerPrefix); writer.println(mKeys.get(mKeys.keyAt(i)));
- }
- }
-
- @Override
- protected V getCacheEntry(int id) {
- return mCache.get(id);
- }
-
- @Override
- protected void putCacheEntry(int id, V value) {
- mCache.put(id, value);
- }
-
- @Override
- protected void removeCacheEntry(int id) {
- mCache.remove(id);
- }
-
- @Override
- protected void evictAllCache() {
- mCache.clear();
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
deleted file mode 100644
index 6e3be09b2ae9..000000000000
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ /dev/null
@@ -1,1142 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.model;
-
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.view.WindowManager.DOCKED_BOTTOM;
-import static android.view.WindowManager.DOCKED_INVALID;
-import static android.view.WindowManager.DOCKED_LEFT;
-import static android.view.WindowManager.DOCKED_RIGHT;
-import static android.view.WindowManager.DOCKED_TOP;
-
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.PropertyValuesHolder;
-import android.annotation.IntDef;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.ColorDrawable;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.IntProperty;
-import android.util.SparseArray;
-import android.view.animation.Interpolator;
-
-import com.android.internal.policy.DockedDividerUtils;
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.RecentsDebugFlags;
-import com.android.systemui.recents.misc.NamedCounter;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.views.AnimationProps;
-import com.android.systemui.recents.views.DropTarget;
-import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
-
-import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Random;
-
-
-/**
- * An interface for a task filter to query whether a particular task should show in a stack.
- */
-interface TaskFilter {
- /** Returns whether the filter accepts the specified task */
- public boolean acceptTask(SparseArray<Task> taskIdMap, Task t, int index);
-}
-
-/**
- * A list of filtered tasks.
- */
-class FilteredTaskList {
-
- ArrayList<Task> mTasks = new ArrayList<>();
- ArrayList<Task> mFilteredTasks = new ArrayList<>();
- ArrayMap<Task.TaskKey, Integer> mTaskIndices = new ArrayMap<>();
- TaskFilter mFilter;
-
- /** Sets the task filter, saving the current touch state */
- boolean setFilter(TaskFilter filter) {
- ArrayList<Task> prevFilteredTasks = new ArrayList<>(mFilteredTasks);
- mFilter = filter;
- updateFilteredTasks();
- if (!prevFilteredTasks.equals(mFilteredTasks)) {
- return true;
- } else {
- return false;
- }
- }
-
- /** Removes the task filter and returns the previous touch state */
- void removeFilter() {
- mFilter = null;
- updateFilteredTasks();
- }
-
- /** Adds a new task to the task list */
- void add(Task t) {
- mTasks.add(t);
- updateFilteredTasks();
- }
-
- /**
- * Moves the given task.
- */
- public void moveTaskToStack(Task task, int insertIndex, int newStackId) {
- int taskIndex = indexOf(task);
- if (taskIndex != insertIndex) {
- mTasks.remove(taskIndex);
- if (taskIndex < insertIndex) {
- insertIndex--;
- }
- mTasks.add(insertIndex, task);
- }
-
- // Update the stack id now, after we've moved the task, and before we update the
- // filtered tasks
- task.setStackId(newStackId);
- updateFilteredTasks();
- }
-
- /** Sets the list of tasks */
- void set(List<Task> tasks) {
- mTasks.clear();
- mTasks.addAll(tasks);
- updateFilteredTasks();
- }
-
- /** Removes a task from the base list only if it is in the filtered list */
- boolean remove(Task t) {
- if (mFilteredTasks.contains(t)) {
- boolean removed = mTasks.remove(t);
- updateFilteredTasks();
- return removed;
- }
- return false;
- }
-
- /** Returns the index of this task in the list of filtered tasks */
- int indexOf(Task t) {
- if (t != null && mTaskIndices.containsKey(t.key)) {
- return mTaskIndices.get(t.key);
- }
- return -1;
- }
-
- /** Returns the size of the list of filtered tasks */
- int size() {
- return mFilteredTasks.size();
- }
-
- /** Returns whether the filtered list contains this task */
- boolean contains(Task t) {
- return mTaskIndices.containsKey(t.key);
- }
-
- /** Updates the list of filtered tasks whenever the base task list changes */
- private void updateFilteredTasks() {
- mFilteredTasks.clear();
- if (mFilter != null) {
- // Create a sparse array from task id to Task
- SparseArray<Task> taskIdMap = new SparseArray<>();
- int taskCount = mTasks.size();
- for (int i = 0; i < taskCount; i++) {
- Task t = mTasks.get(i);
- taskIdMap.put(t.key.id, t);
- }
-
- for (int i = 0; i < taskCount; i++) {
- Task t = mTasks.get(i);
- if (mFilter.acceptTask(taskIdMap, t, i)) {
- mFilteredTasks.add(t);
- }
- }
- } else {
- mFilteredTasks.addAll(mTasks);
- }
- updateFilteredTaskIndices();
- }
-
- /** Updates the mapping of tasks to indices. */
- private void updateFilteredTaskIndices() {
- int taskCount = mFilteredTasks.size();
- mTaskIndices.clear();
- for (int i = 0; i < taskCount; i++) {
- Task t = mFilteredTasks.get(i);
- mTaskIndices.put(t.key, i);
- }
- }
-
- /** Returns whether this task list is filtered */
- boolean hasFilter() {
- return (mFilter != null);
- }
-
- /** Returns the list of filtered tasks */
- ArrayList<Task> getTasks() {
- return mFilteredTasks;
- }
-}
-
-/**
- * The task stack contains a list of multiple tasks.
- */
-public class TaskStack {
-
- private static final String TAG = "TaskStack";
-
- /** Task stack callbacks */
- public interface TaskStackCallbacks {
- /**
- * Notifies when a new task has been added to the stack.
- */
- void onStackTaskAdded(TaskStack stack, Task newTask);
-
- /**
- * Notifies when a task has been removed from the stack.
- */
- void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask,
- AnimationProps animation, boolean fromDockGesture,
- boolean dismissRecentsIfAllRemoved);
-
- /**
- * Notifies when all tasks have been removed from the stack.
- */
- void onStackTasksRemoved(TaskStack stack);
-
- /**
- * Notifies when tasks in the stack have been updated.
- */
- void onStackTasksUpdated(TaskStack stack);
- }
-
- /**
- * The various possible dock states when dragging and dropping a task.
- */
- public static class DockState implements DropTarget {
-
- public static final int DOCK_AREA_BG_COLOR = 0xFFffffff;
- public static final int DOCK_AREA_GRID_BG_COLOR = 0xFF000000;
-
- // The rotation to apply to the hint text
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({HORIZONTAL, VERTICAL})
- public @interface TextOrientation {}
- private static final int HORIZONTAL = 0;
- private static final int VERTICAL = 1;
-
- private static final int DOCK_AREA_ALPHA = 80;
- public static final DockState NONE = new DockState(DOCKED_INVALID, -1, 80, 255, HORIZONTAL,
- null, null, null);
- public static final DockState LEFT = new DockState(DOCKED_LEFT,
- DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, 0, VERTICAL,
- new RectF(0, 0, 0.125f, 1), new RectF(0, 0, 0.125f, 1),
- new RectF(0, 0, 0.5f, 1));
- public static final DockState TOP = new DockState(DOCKED_TOP,
- DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, 0, HORIZONTAL,
- new RectF(0, 0, 1, 0.125f), new RectF(0, 0, 1, 0.125f),
- new RectF(0, 0, 1, 0.5f));
- public static final DockState RIGHT = new DockState(DOCKED_RIGHT,
- DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, 0, VERTICAL,
- new RectF(0.875f, 0, 1, 1), new RectF(0.875f, 0, 1, 1),
- new RectF(0.5f, 0, 1, 1));
- public static final DockState BOTTOM = new DockState(DOCKED_BOTTOM,
- DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, 0, HORIZONTAL,
- new RectF(0, 0.875f, 1, 1), new RectF(0, 0.875f, 1, 1),
- new RectF(0, 0.5f, 1, 1));
-
- @Override
- public boolean acceptsDrop(int x, int y, int width, int height, Rect insets,
- boolean isCurrentTarget) {
- if (isCurrentTarget) {
- getMappedRect(expandedTouchDockArea, width, height, mTmpRect);
- return mTmpRect.contains(x, y);
- } else {
- getMappedRect(touchArea, width, height, mTmpRect);
- updateBoundsWithSystemInsets(mTmpRect, insets);
- return mTmpRect.contains(x, y);
- }
- }
-
- // Represents the view state of this dock state
- public static class ViewState {
- private static final IntProperty<ViewState> HINT_ALPHA =
- new IntProperty<ViewState>("drawableAlpha") {
- @Override
- public void setValue(ViewState object, int alpha) {
- object.mHintTextAlpha = alpha;
- object.dockAreaOverlay.invalidateSelf();
- }
-
- @Override
- public Integer get(ViewState object) {
- return object.mHintTextAlpha;
- }
- };
-
- public final int dockAreaAlpha;
- public final ColorDrawable dockAreaOverlay;
- public final int hintTextAlpha;
- public final int hintTextOrientation;
-
- private final int mHintTextResId;
- private String mHintText;
- private Paint mHintTextPaint;
- private Point mHintTextBounds = new Point();
- private int mHintTextAlpha = 255;
- private AnimatorSet mDockAreaOverlayAnimator;
- private Rect mTmpRect = new Rect();
-
- private ViewState(int areaAlpha, int hintAlpha, @TextOrientation int hintOrientation,
- int hintTextResId) {
- dockAreaAlpha = areaAlpha;
- dockAreaOverlay = new ColorDrawable(Recents.getConfiguration().isGridEnabled
- ? DOCK_AREA_GRID_BG_COLOR : DOCK_AREA_BG_COLOR);
- dockAreaOverlay.setAlpha(0);
- hintTextAlpha = hintAlpha;
- hintTextOrientation = hintOrientation;
- mHintTextResId = hintTextResId;
- mHintTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- mHintTextPaint.setColor(Color.WHITE);
- }
-
- /**
- * Updates the view state with the given context.
- */
- public void update(Context context) {
- Resources res = context.getResources();
- mHintText = context.getString(mHintTextResId);
- mHintTextPaint.setTextSize(res.getDimensionPixelSize(
- R.dimen.recents_drag_hint_text_size));
- mHintTextPaint.getTextBounds(mHintText, 0, mHintText.length(), mTmpRect);
- mHintTextBounds.set((int) mHintTextPaint.measureText(mHintText), mTmpRect.height());
- }
-
- /**
- * Draws the current view state.
- */
- public void draw(Canvas canvas) {
- // Draw the overlay background
- if (dockAreaOverlay.getAlpha() > 0) {
- dockAreaOverlay.draw(canvas);
- }
-
- // Draw the hint text
- if (mHintTextAlpha > 0) {
- Rect bounds = dockAreaOverlay.getBounds();
- int x = bounds.left + (bounds.width() - mHintTextBounds.x) / 2;
- int y = bounds.top + (bounds.height() + mHintTextBounds.y) / 2;
- mHintTextPaint.setAlpha(mHintTextAlpha);
- if (hintTextOrientation == VERTICAL) {
- canvas.save();
- canvas.rotate(-90f, bounds.centerX(), bounds.centerY());
- }
- canvas.drawText(mHintText, x, y, mHintTextPaint);
- if (hintTextOrientation == VERTICAL) {
- canvas.restore();
- }
- }
- }
-
- /**
- * Creates a new bounds and alpha animation.
- */
- public void startAnimation(Rect bounds, int areaAlpha, int hintAlpha, int duration,
- Interpolator interpolator, boolean animateAlpha, boolean animateBounds) {
- if (mDockAreaOverlayAnimator != null) {
- mDockAreaOverlayAnimator.cancel();
- }
-
- ObjectAnimator anim;
- ArrayList<Animator> animators = new ArrayList<>();
- if (dockAreaOverlay.getAlpha() != areaAlpha) {
- if (animateAlpha) {
- anim = ObjectAnimator.ofInt(dockAreaOverlay,
- Utilities.DRAWABLE_ALPHA, dockAreaOverlay.getAlpha(), areaAlpha);
- anim.setDuration(duration);
- anim.setInterpolator(interpolator);
- animators.add(anim);
- } else {
- dockAreaOverlay.setAlpha(areaAlpha);
- }
- }
- if (mHintTextAlpha != hintAlpha) {
- if (animateAlpha) {
- anim = ObjectAnimator.ofInt(this, HINT_ALPHA, mHintTextAlpha,
- hintAlpha);
- anim.setDuration(150);
- anim.setInterpolator(hintAlpha > mHintTextAlpha
- ? Interpolators.ALPHA_IN
- : Interpolators.ALPHA_OUT);
- animators.add(anim);
- } else {
- mHintTextAlpha = hintAlpha;
- dockAreaOverlay.invalidateSelf();
- }
- }
- if (bounds != null && !dockAreaOverlay.getBounds().equals(bounds)) {
- if (animateBounds) {
- PropertyValuesHolder prop = PropertyValuesHolder.ofObject(
- Utilities.DRAWABLE_RECT, Utilities.RECT_EVALUATOR,
- new Rect(dockAreaOverlay.getBounds()), bounds);
- anim = ObjectAnimator.ofPropertyValuesHolder(dockAreaOverlay, prop);
- anim.setDuration(duration);
- anim.setInterpolator(interpolator);
- animators.add(anim);
- } else {
- dockAreaOverlay.setBounds(bounds);
- }
- }
- if (!animators.isEmpty()) {
- mDockAreaOverlayAnimator = new AnimatorSet();
- mDockAreaOverlayAnimator.playTogether(animators);
- mDockAreaOverlayAnimator.start();
- }
- }
- }
-
- public final int dockSide;
- public final int createMode;
- public final ViewState viewState;
- private final RectF touchArea;
- private final RectF dockArea;
- private final RectF expandedTouchDockArea;
- private static final Rect mTmpRect = new Rect();
-
- /**
- * @param createMode used to pass to ActivityManager to dock the task
- * @param touchArea the area in which touch will initiate this dock state
- * @param dockArea the visible dock area
- * @param expandedTouchDockArea the area in which touch will continue to dock after entering
- * the initial touch area. This is also the new dock area to
- * draw.
- */
- DockState(int dockSide, int createMode, int dockAreaAlpha, int hintTextAlpha,
- @TextOrientation int hintTextOrientation, RectF touchArea, RectF dockArea,
- RectF expandedTouchDockArea) {
- this.dockSide = dockSide;
- this.createMode = createMode;
- this.viewState = new ViewState(dockAreaAlpha, hintTextAlpha, hintTextOrientation,
- R.string.recents_drag_hint_message);
- this.dockArea = dockArea;
- this.touchArea = touchArea;
- this.expandedTouchDockArea = expandedTouchDockArea;
- }
-
- /**
- * Updates the dock state with the given context.
- */
- public void update(Context context) {
- viewState.update(context);
- }
-
- /**
- * Returns the docked task bounds with the given {@param width} and {@param height}.
- */
- public Rect getPreDockedBounds(int width, int height, Rect insets) {
- getMappedRect(dockArea, width, height, mTmpRect);
- return updateBoundsWithSystemInsets(mTmpRect, insets);
- }
-
- /**
- * Returns the expanded docked task bounds with the given {@param width} and
- * {@param height}.
- */
- public Rect getDockedBounds(int width, int height, int dividerSize, Rect insets,
- Resources res) {
- // Calculate the docked task bounds
- boolean isHorizontalDivision =
- res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
- int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision,
- insets, width, height, dividerSize);
- Rect newWindowBounds = new Rect();
- DockedDividerUtils.calculateBoundsForPosition(position, dockSide, newWindowBounds,
- width, height, dividerSize);
- return newWindowBounds;
- }
-
- /**
- * Returns the task stack bounds with the given {@param width} and
- * {@param height}.
- */
- public Rect getDockedTaskStackBounds(Rect displayRect, int width, int height,
- int dividerSize, Rect insets, TaskStackLayoutAlgorithm layoutAlgorithm,
- Resources res, Rect windowRectOut) {
- // Calculate the inverse docked task bounds
- boolean isHorizontalDivision =
- res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
- int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision,
- insets, width, height, dividerSize);
- DockedDividerUtils.calculateBoundsForPosition(position,
- DockedDividerUtils.invertDockSide(dockSide), windowRectOut, width, height,
- dividerSize);
-
- // Calculate the task stack bounds from the new window bounds
- Rect taskStackBounds = new Rect();
- // If the task stack bounds is specifically under the dock area, then ignore the top
- // inset
- int top = dockArea.bottom < 1f
- ? 0
- : insets.top;
- // For now, ignore the left insets since we always dock on the left and show Recents
- // on the right
- layoutAlgorithm.getTaskStackBounds(displayRect, windowRectOut, top, 0, insets.right,
- taskStackBounds);
- return taskStackBounds;
- }
-
- /**
- * Returns the expanded bounds in certain dock sides such that the bounds account for the
- * system insets (namely the vertical nav bar). This call modifies and returns the given
- * {@param bounds}.
- */
- private Rect updateBoundsWithSystemInsets(Rect bounds, Rect insets) {
- if (dockSide == DOCKED_LEFT) {
- bounds.right += insets.left;
- } else if (dockSide == DOCKED_RIGHT) {
- bounds.left -= insets.right;
- }
- return bounds;
- }
-
- /**
- * Returns the mapped rect to the given dimensions.
- */
- private void getMappedRect(RectF bounds, int width, int height, Rect out) {
- out.set((int) (bounds.left * width), (int) (bounds.top * height),
- (int) (bounds.right * width), (int) (bounds.bottom * height));
- }
- }
-
- // A comparator that sorts tasks by their freeform state
- private Comparator<Task> FREEFORM_COMPARATOR = new Comparator<Task>() {
- @Override
- public int compare(Task o1, Task o2) {
- if (o1.isFreeformTask() && !o2.isFreeformTask()) {
- return 1;
- } else if (o2.isFreeformTask() && !o1.isFreeformTask()) {
- return -1;
- }
- return Long.compare(o1.temporarySortIndexInStack, o2.temporarySortIndexInStack);
- }
- };
-
-
- // The task offset to apply to a task id as a group affiliation
- static final int IndividualTaskIdOffset = 1 << 16;
-
- ArrayList<Task> mRawTaskList = new ArrayList<>();
- FilteredTaskList mStackTaskList = new FilteredTaskList();
- TaskStackCallbacks mCb;
-
- ArrayList<TaskGrouping> mGroups = new ArrayList<>();
- ArrayMap<Integer, TaskGrouping> mAffinitiesGroups = new ArrayMap<>();
-
- public TaskStack() {
- // Ensure that we only show non-docked tasks
- mStackTaskList.setFilter(new TaskFilter() {
- @Override
- public boolean acceptTask(SparseArray<Task> taskIdMap, Task t, int index) {
- if (RecentsDebugFlags.Static.EnableAffiliatedTaskGroups) {
- if (t.isAffiliatedTask()) {
- // If this task is affiliated with another parent in the stack, then the
- // historical state of this task depends on the state of the parent task
- Task parentTask = taskIdMap.get(t.affiliationTaskId);
- if (parentTask != null) {
- t = parentTask;
- }
- }
- }
- return t.isStackTask;
- }
- });
- }
-
- /** Sets the callbacks for this task stack. */
- public void setCallbacks(TaskStackCallbacks cb) {
- mCb = cb;
- }
-
- /**
- * Moves the given task to either the front of the freeform workspace or the stack.
- */
- public void moveTaskToStack(Task task, int newStackId) {
- // Find the index to insert into
- ArrayList<Task> taskList = mStackTaskList.getTasks();
- int taskCount = taskList.size();
- if (!task.isFreeformTask() && (newStackId == FREEFORM_WORKSPACE_STACK_ID)) {
- // Insert freeform tasks at the front
- mStackTaskList.moveTaskToStack(task, taskCount, newStackId);
- } else if (task.isFreeformTask() && (newStackId == FULLSCREEN_WORKSPACE_STACK_ID)) {
- // Insert after the first stacked task
- int insertIndex = 0;
- for (int i = taskCount - 1; i >= 0; i--) {
- if (!taskList.get(i).isFreeformTask()) {
- insertIndex = i + 1;
- break;
- }
- }
- mStackTaskList.moveTaskToStack(task, insertIndex, newStackId);
- }
- }
-
- /** Does the actual work associated with removing the task. */
- void removeTaskImpl(FilteredTaskList taskList, Task t) {
- // Remove the task from the list
- taskList.remove(t);
- // Remove it from the group as well, and if it is empty, remove the group
- TaskGrouping group = t.group;
- if (group != null) {
- group.removeTask(t);
- if (group.getTaskCount() == 0) {
- removeGroup(group);
- }
- }
- }
-
- /**
- * Removes a task from the stack, with an additional {@param animation} hint to the callbacks on
- * how they should update themselves.
- */
- public void removeTask(Task t, AnimationProps animation, boolean fromDockGesture) {
- removeTask(t, animation, fromDockGesture, true /* dismissRecentsIfAllRemoved */);
- }
-
- /**
- * Removes a task from the stack, with an additional {@param animation} hint to the callbacks on
- * how they should update themselves.
- */
- public void removeTask(Task t, AnimationProps animation, boolean fromDockGesture,
- boolean dismissRecentsIfAllRemoved) {
- if (mStackTaskList.contains(t)) {
- removeTaskImpl(mStackTaskList, t);
- Task newFrontMostTask = getStackFrontMostTask(false /* includeFreeform */);
- if (mCb != null) {
- // Notify that a task has been removed
- mCb.onStackTaskRemoved(this, t, newFrontMostTask, animation,
- fromDockGesture, dismissRecentsIfAllRemoved);
- }
- }
- mRawTaskList.remove(t);
- }
-
- /**
- * Removes all tasks from the stack.
- */
- public void removeAllTasks(boolean notifyStackChanges) {
- ArrayList<Task> tasks = mStackTaskList.getTasks();
- for (int i = tasks.size() - 1; i >= 0; i--) {
- Task t = tasks.get(i);
- removeTaskImpl(mStackTaskList, t);
- mRawTaskList.remove(t);
- }
- if (mCb != null && notifyStackChanges) {
- // Notify that all tasks have been removed
- mCb.onStackTasksRemoved(this);
- }
- }
-
-
- /**
- * @see #setTasks(Context, List, boolean, boolean)
- */
- public void setTasks(Context context, TaskStack stack, boolean notifyStackChanges) {
- setTasks(context, stack.mRawTaskList, notifyStackChanges);
- }
-
- /**
- * Sets a few tasks in one go, without calling any callbacks.
- *
- * @param tasks the new set of tasks to replace the current set.
- * @param notifyStackChanges whether or not to callback on specific changes to the list of tasks.
- */
- public void setTasks(Context context, List<Task> tasks, boolean notifyStackChanges) {
- // Compute a has set for each of the tasks
- ArrayMap<Task.TaskKey, Task> currentTasksMap = createTaskKeyMapFromList(mRawTaskList);
- ArrayMap<Task.TaskKey, Task> newTasksMap = createTaskKeyMapFromList(tasks);
- ArrayList<Task> addedTasks = new ArrayList<>();
- ArrayList<Task> removedTasks = new ArrayList<>();
- ArrayList<Task> allTasks = new ArrayList<>();
-
- // Disable notifications if there are no callbacks
- if (mCb == null) {
- notifyStackChanges = false;
- }
-
- // Remove any tasks that no longer exist
- int taskCount = mRawTaskList.size();
- for (int i = taskCount - 1; i >= 0; i--) {
- Task task = mRawTaskList.get(i);
- if (!newTasksMap.containsKey(task.key)) {
- if (notifyStackChanges) {
- removedTasks.add(task);
- }
- }
- task.setGroup(null);
- }
-
- // Add any new tasks
- taskCount = tasks.size();
- for (int i = 0; i < taskCount; i++) {
- Task newTask = tasks.get(i);
- Task currentTask = currentTasksMap.get(newTask.key);
- if (currentTask == null && notifyStackChanges) {
- addedTasks.add(newTask);
- } else if (currentTask != null) {
- // The current task has bound callbacks, so just copy the data from the new task
- // state and add it back into the list
- currentTask.copyFrom(newTask);
- newTask = currentTask;
- }
- allTasks.add(newTask);
- }
-
- // Sort all the tasks to ensure they are ordered correctly
- for (int i = allTasks.size() - 1; i >= 0; i--) {
- allTasks.get(i).temporarySortIndexInStack = i;
- }
- Collections.sort(allTasks, FREEFORM_COMPARATOR);
-
- mStackTaskList.set(allTasks);
- mRawTaskList = allTasks;
-
- // Update the affiliated groupings
- createAffiliatedGroupings(context);
-
- // Only callback for the removed tasks after the stack has updated
- int removedTaskCount = removedTasks.size();
- Task newFrontMostTask = getStackFrontMostTask(false);
- for (int i = 0; i < removedTaskCount; i++) {
- mCb.onStackTaskRemoved(this, removedTasks.get(i), newFrontMostTask,
- AnimationProps.IMMEDIATE, false /* fromDockGesture */,
- true /* dismissRecentsIfAllRemoved */);
- }
-
- // Only callback for the newly added tasks after this stack has been updated
- int addedTaskCount = addedTasks.size();
- for (int i = 0; i < addedTaskCount; i++) {
- mCb.onStackTaskAdded(this, addedTasks.get(i));
- }
-
- // Notify that the task stack has been updated
- if (notifyStackChanges) {
- mCb.onStackTasksUpdated(this);
- }
- }
-
- /**
- * Gets the front-most task in the stack.
- */
- public Task getStackFrontMostTask(boolean includeFreeformTasks) {
- ArrayList<Task> stackTasks = mStackTaskList.getTasks();
- if (stackTasks.isEmpty()) {
- return null;
- }
- for (int i = stackTasks.size() - 1; i >= 0; i--) {
- Task task = stackTasks.get(i);
- if (!task.isFreeformTask() || includeFreeformTasks) {
- return task;
- }
- }
- return null;
- }
-
- /** Gets the task keys */
- public ArrayList<Task.TaskKey> getTaskKeys() {
- ArrayList<Task.TaskKey> taskKeys = new ArrayList<>();
- ArrayList<Task> tasks = computeAllTasksList();
- int taskCount = tasks.size();
- for (int i = 0; i < taskCount; i++) {
- Task task = tasks.get(i);
- taskKeys.add(task.key);
- }
- return taskKeys;
- }
-
- /**
- * Returns the set of "active" (non-historical) tasks in the stack that have been used recently.
- */
- public ArrayList<Task> getStackTasks() {
- return mStackTaskList.getTasks();
- }
-
- /**
- * Returns the set of "freeform" tasks in the stack.
- */
- public ArrayList<Task> getFreeformTasks() {
- ArrayList<Task> freeformTasks = new ArrayList<>();
- ArrayList<Task> tasks = mStackTaskList.getTasks();
- int taskCount = tasks.size();
- for (int i = 0; i < taskCount; i++) {
- Task task = tasks.get(i);
- if (task.isFreeformTask()) {
- freeformTasks.add(task);
- }
- }
- return freeformTasks;
- }
-
- /**
- * Computes a set of all the active and historical tasks.
- */
- public ArrayList<Task> computeAllTasksList() {
- ArrayList<Task> tasks = new ArrayList<>();
- tasks.addAll(mStackTaskList.getTasks());
- return tasks;
- }
-
- /**
- * Returns the number of stack and freeform tasks.
- */
- public int getTaskCount() {
- return mStackTaskList.size();
- }
-
- /**
- * Returns the number of stack tasks.
- */
- public int getStackTaskCount() {
- ArrayList<Task> tasks = mStackTaskList.getTasks();
- int stackCount = 0;
- int taskCount = tasks.size();
- for (int i = 0; i < taskCount; i++) {
- Task task = tasks.get(i);
- if (!task.isFreeformTask()) {
- stackCount++;
- }
- }
- return stackCount;
- }
-
- /**
- * Returns the number of freeform tasks.
- */
- public int getFreeformTaskCount() {
- ArrayList<Task> tasks = mStackTaskList.getTasks();
- int freeformCount = 0;
- int taskCount = tasks.size();
- for (int i = 0; i < taskCount; i++) {
- Task task = tasks.get(i);
- if (task.isFreeformTask()) {
- freeformCount++;
- }
- }
- return freeformCount;
- }
-
- /**
- * Returns the task in stack tasks which is the launch target.
- */
- public Task getLaunchTarget() {
- ArrayList<Task> tasks = mStackTaskList.getTasks();
- int taskCount = tasks.size();
- for (int i = 0; i < taskCount; i++) {
- Task task = tasks.get(i);
- if (task.isLaunchTarget) {
- return task;
- }
- }
- return null;
- }
-
- /**
- * Returns whether the next launch target should actually be the PiP task.
- */
- public boolean isNextLaunchTargetPip(long lastPipTime) {
- Task launchTarget = getLaunchTarget();
- Task nextLaunchTarget = getNextLaunchTargetRaw();
- if (nextLaunchTarget != null && lastPipTime > 0) {
- // If the PiP time is more recent than the next launch target, then launch the PiP task
- return lastPipTime > nextLaunchTarget.key.lastActiveTime;
- } else if (launchTarget != null && lastPipTime > 0 && getTaskCount() == 1) {
- // Otherwise, if there is no next launch target, but there is a PiP, then launch
- // the PiP task
- return true;
- }
- return false;
- }
-
- /**
- * Returns the task in stack tasks which should be launched next if Recents are toggled
- * again, or null if there is no task to be launched. Callers should check
- * {@link #isNextLaunchTargetPip(long)} before fetching the next raw launch target from the
- * stack.
- */
- public Task getNextLaunchTarget() {
- Task nextLaunchTarget = getNextLaunchTargetRaw();
- if (nextLaunchTarget != null) {
- return nextLaunchTarget;
- }
- return getStackTasks().get(getTaskCount() - 1);
- }
-
- private Task getNextLaunchTargetRaw() {
- int taskCount = getTaskCount();
- if (taskCount == 0) {
- return null;
- }
- int launchTaskIndex = indexOfStackTask(getLaunchTarget());
- if (launchTaskIndex != -1 && launchTaskIndex > 0) {
- return getStackTasks().get(launchTaskIndex - 1);
- }
- return null;
- }
-
- /** Returns the index of this task in this current task stack */
- public int indexOfStackTask(Task t) {
- return mStackTaskList.indexOf(t);
- }
-
- /** Finds the task with the specified task id. */
- public Task findTaskWithId(int taskId) {
- ArrayList<Task> tasks = computeAllTasksList();
- int taskCount = tasks.size();
- for (int i = 0; i < taskCount; i++) {
- Task task = tasks.get(i);
- if (task.key.id == taskId) {
- return task;
- }
- }
- return null;
- }
-
- /******** Grouping ********/
-
- /** Adds a group to the set */
- public void addGroup(TaskGrouping group) {
- mGroups.add(group);
- mAffinitiesGroups.put(group.affiliation, group);
- }
-
- public void removeGroup(TaskGrouping group) {
- mGroups.remove(group);
- mAffinitiesGroups.remove(group.affiliation);
- }
-
- /** Returns the group with the specified affiliation. */
- public TaskGrouping getGroupWithAffiliation(int affiliation) {
- return mAffinitiesGroups.get(affiliation);
- }
-
- /**
- * Temporary: This method will simulate affiliation groups
- */
- void createAffiliatedGroupings(Context context) {
- mGroups.clear();
- mAffinitiesGroups.clear();
-
- if (RecentsDebugFlags.Static.EnableMockTaskGroups) {
- ArrayMap<Task.TaskKey, Task> taskMap = new ArrayMap<>();
- // Sort all tasks by increasing firstActiveTime of the task
- ArrayList<Task> tasks = mStackTaskList.getTasks();
- Collections.sort(tasks, new Comparator<Task>() {
- @Override
- public int compare(Task task, Task task2) {
- return Long.compare(task.key.firstActiveTime, task2.key.firstActiveTime);
- }
- });
- // Create groups when sequential packages are the same
- NamedCounter counter = new NamedCounter("task-group", "");
- int taskCount = tasks.size();
- String prevPackage = "";
- int prevAffiliation = -1;
- Random r = new Random();
- int groupCountDown = RecentsDebugFlags.Static.MockTaskGroupsTaskCount;
- for (int i = 0; i < taskCount; i++) {
- Task t = tasks.get(i);
- String packageName = t.key.getComponent().getPackageName();
- packageName = "pkg";
- TaskGrouping group;
- if (packageName.equals(prevPackage) && groupCountDown > 0) {
- group = getGroupWithAffiliation(prevAffiliation);
- groupCountDown--;
- } else {
- int affiliation = IndividualTaskIdOffset + t.key.id;
- group = new TaskGrouping(affiliation);
- addGroup(group);
- prevAffiliation = affiliation;
- prevPackage = packageName;
- groupCountDown = RecentsDebugFlags.Static.MockTaskGroupsTaskCount;
- }
- group.addTask(t);
- taskMap.put(t.key, t);
- }
- // Sort groups by increasing latestActiveTime of the group
- Collections.sort(mGroups, new Comparator<TaskGrouping>() {
- @Override
- public int compare(TaskGrouping taskGrouping, TaskGrouping taskGrouping2) {
- return Long.compare(taskGrouping.latestActiveTimeInGroup,
- taskGrouping2.latestActiveTimeInGroup);
- }
- });
- // Sort group tasks by increasing firstActiveTime of the task, and also build a new list
- // of tasks
- int taskIndex = 0;
- int groupCount = mGroups.size();
- for (int i = 0; i < groupCount; i++) {
- TaskGrouping group = mGroups.get(i);
- Collections.sort(group.mTaskKeys, new Comparator<Task.TaskKey>() {
- @Override
- public int compare(Task.TaskKey taskKey, Task.TaskKey taskKey2) {
- return Long.compare(taskKey.firstActiveTime, taskKey2.firstActiveTime);
- }
- });
- ArrayList<Task.TaskKey> groupTasks = group.mTaskKeys;
- int groupTaskCount = groupTasks.size();
- for (int j = 0; j < groupTaskCount; j++) {
- tasks.set(taskIndex, taskMap.get(groupTasks.get(j)));
- taskIndex++;
- }
- }
- mStackTaskList.set(tasks);
- } else {
- // Create the task groups
- ArrayMap<Task.TaskKey, Task> tasksMap = new ArrayMap<>();
- ArrayList<Task> tasks = mStackTaskList.getTasks();
- int taskCount = tasks.size();
- for (int i = 0; i < taskCount; i++) {
- Task t = tasks.get(i);
- TaskGrouping group;
- if (RecentsDebugFlags.Static.EnableAffiliatedTaskGroups) {
- int affiliation = t.affiliationTaskId > 0 ? t.affiliationTaskId :
- IndividualTaskIdOffset + t.key.id;
- if (mAffinitiesGroups.containsKey(affiliation)) {
- group = getGroupWithAffiliation(affiliation);
- } else {
- group = new TaskGrouping(affiliation);
- addGroup(group);
- }
- } else {
- group = new TaskGrouping(t.key.id);
- addGroup(group);
- }
- group.addTask(t);
- tasksMap.put(t.key, t);
- }
- // Update the task colors for each of the groups
- float minAlpha = context.getResources().getFloat(
- R.dimen.recents_task_affiliation_color_min_alpha_percentage);
- int taskGroupCount = mGroups.size();
- for (int i = 0; i < taskGroupCount; i++) {
- TaskGrouping group = mGroups.get(i);
- taskCount = group.getTaskCount();
- // Ignore the groups that only have one task
- if (taskCount <= 1) continue;
- // Calculate the group color distribution
- int affiliationColor = tasksMap.get(group.mTaskKeys.get(0)).affiliationColor;
- float alphaStep = (1f - minAlpha) / taskCount;
- float alpha = 1f;
- for (int j = 0; j < taskCount; j++) {
- Task t = tasksMap.get(group.mTaskKeys.get(j));
- t.colorPrimary = Utilities.getColorWithOverlay(affiliationColor, Color.WHITE,
- alpha);
- alpha -= alphaStep;
- }
- }
- }
- }
-
- /**
- * Computes the components of tasks in this stack that have been removed as a result of a change
- * in the specified package.
- */
- public ArraySet<ComponentName> computeComponentsRemoved(String packageName, int userId) {
- // Identify all the tasks that should be removed as a result of the package being removed.
- // Using a set to ensure that we callback once per unique component.
- SystemServicesProxy ssp = Recents.getSystemServices();
- ArraySet<ComponentName> existingComponents = new ArraySet<>();
- ArraySet<ComponentName> removedComponents = new ArraySet<>();
- ArrayList<Task.TaskKey> taskKeys = getTaskKeys();
- int taskKeyCount = taskKeys.size();
- for (int i = 0; i < taskKeyCount; i++) {
- Task.TaskKey t = taskKeys.get(i);
-
- // Skip if this doesn't apply to the current user
- if (t.userId != userId) continue;
-
- ComponentName cn = t.getComponent();
- if (cn.getPackageName().equals(packageName)) {
- if (existingComponents.contains(cn)) {
- // If we know that the component still exists in the package, then skip
- continue;
- }
- if (ssp.getActivityInfo(cn, userId) != null) {
- existingComponents.add(cn);
- } else {
- removedComponents.add(cn);
- }
- }
- }
- return removedComponents;
- }
-
- @Override
- public String toString() {
- String str = "Stack Tasks (" + mStackTaskList.size() + "):\n";
- ArrayList<Task> tasks = mStackTaskList.getTasks();
- int taskCount = tasks.size();
- for (int i = 0; i < taskCount; i++) {
- str += " " + tasks.get(i).toString() + "\n";
- }
- return str;
- }
-
- /**
- * Given a list of tasks, returns a map of each task's key to the task.
- */
- private ArrayMap<Task.TaskKey, Task> createTaskKeyMapFromList(List<Task> tasks) {
- ArrayMap<Task.TaskKey, Task> map = new ArrayMap<>(tasks.size());
- int taskCount = tasks.size();
- for (int i = 0; i < taskCount; i++) {
- Task task = tasks.get(i);
- map.put(task.key, task);
- }
- return map;
- }
-
- public void dump(String prefix, PrintWriter writer) {
- String innerPrefix = prefix + " ";
-
- writer.print(prefix); writer.print(TAG);
- writer.print(" numStackTasks="); writer.print(mStackTaskList.size());
- writer.println();
- ArrayList<Task> tasks = mStackTaskList.getTasks();
- int taskCount = tasks.size();
- for (int i = 0; i < taskCount; i++) {
- tasks.get(i).dump(innerPrefix, writer);
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/ThumbnailData.java b/packages/SystemUI/src/com/android/systemui/recents/model/ThumbnailData.java
deleted file mode 100644
index 33ff1b634d64..000000000000
--- a/packages/SystemUI/src/com/android/systemui/recents/model/ThumbnailData.java
+++ /dev/null
@@ -1,44 +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.recents.model;
-
-import android.app.ActivityManager.TaskSnapshot;
-import android.graphics.Bitmap;
-import android.graphics.Rect;
-
-/**
- * Data for a single thumbnail.
- */
-public class ThumbnailData {
-
- // TODO: Make these final once the non-snapshot path is removed.
- public Bitmap thumbnail;
- public int orientation;
- public final Rect insets = new Rect();
- public boolean reducedResolution;
- public float scale;
-
- public static ThumbnailData createFromTaskSnapshot(TaskSnapshot snapshot) {
- ThumbnailData out = new ThumbnailData();
- out.thumbnail = Bitmap.createHardwareBitmap(snapshot.getSnapshot());
- out.insets.set(snapshot.getContentInsets());
- out.orientation = snapshot.getOrientation();
- out.reducedResolution = snapshot.isReducedResolution();
- out.scale = snapshot.getScale();
- return out;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java b/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java
deleted file mode 100644
index dba085e37427..000000000000
--- a/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.views;
-
-import android.graphics.Outline;
-import android.graphics.Rect;
-import android.view.View;
-import android.view.ViewDebug;
-import android.view.ViewOutlineProvider;
-
-import com.android.systemui.recents.misc.Utilities;
-
-/* An outline provider that has a clip and outline that can be animated. */
-public class AnimateableViewBounds extends ViewOutlineProvider {
-
- private static final float MIN_ALPHA = 0.1f;
- private static final float MAX_ALPHA = 0.8f;
-
- protected View mSourceView;
- @ViewDebug.ExportedProperty(category="recents")
- protected Rect mClipRect = new Rect();
- @ViewDebug.ExportedProperty(category="recents")
- protected Rect mClipBounds = new Rect();
- @ViewDebug.ExportedProperty(category="recents")
- protected Rect mLastClipBounds = new Rect();
- @ViewDebug.ExportedProperty(category="recents")
- protected int mCornerRadius;
- @ViewDebug.ExportedProperty(category="recents")
- protected float mAlpha = 1f;
-
- public AnimateableViewBounds(View source, int cornerRadius) {
- mSourceView = source;
- mCornerRadius = cornerRadius;
- }
-
- /**
- * Resets the right and bottom clip for this view.
- */
- public void reset() {
- mClipRect.set(-1, -1, -1, -1);
- updateClipBounds();
- }
-
- @Override
- public void getOutline(View view, Outline outline) {
- outline.setAlpha(Utilities.mapRange(mAlpha, MIN_ALPHA, MAX_ALPHA));
- if (mCornerRadius > 0) {
- outline.setRoundRect(mClipRect.left, mClipRect.top,
- mSourceView.getWidth() - mClipRect.right,
- mSourceView.getHeight() - mClipRect.bottom,
- mCornerRadius);
- } else {
- outline.setRect(mClipRect.left, mClipRect.top,
- mSourceView.getWidth() - mClipRect.right,
- mSourceView.getHeight() - mClipRect.bottom);
- }
- }
-
- /**
- * Sets the view outline alpha.
- */
- void setAlpha(float alpha) {
- if (Float.compare(alpha, mAlpha) != 0) {
- mAlpha = alpha;
- // TODO, If both clip and alpha change in the same frame, only invalidate once
- mSourceView.invalidateOutline();
- }
- }
-
- /**
- * @return the outline alpha.
- */
- public float getAlpha() {
- return mAlpha;
- }
-
- /** Sets the top clip. */
- public void setClipTop(int top) {
- mClipRect.top = top;
- updateClipBounds();
- }
-
- /** Returns the top clip. */
- public int getClipTop() {
- return mClipRect.top;
- }
-
- /** Sets the bottom clip. */
- public void setClipBottom(int bottom) {
- mClipRect.bottom = bottom;
- updateClipBounds();
- }
-
- /** Returns the bottom clip. */
- public int getClipBottom() {
- return mClipRect.bottom;
- }
-
- protected void updateClipBounds() {
- mClipBounds.set(Math.max(0, mClipRect.left), Math.max(0, mClipRect.top),
- mSourceView.getWidth() - Math.max(0, mClipRect.right),
- mSourceView.getHeight() - Math.max(0, mClipRect.bottom));
- if (!mLastClipBounds.equals(mClipBounds)) {
- mSourceView.setClipBounds(mClipBounds);
- // TODO, If both clip and alpha change in the same frame, only invalidate once
- mSourceView.invalidateOutline();
- mLastClipBounds.set(mClipBounds);
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/AnimationProps.java b/packages/SystemUI/src/com/android/systemui/recents/views/AnimationProps.java
deleted file mode 100644
index 716d1bcf78c2..000000000000
--- a/packages/SystemUI/src/com/android/systemui/recents/views/AnimationProps.java
+++ /dev/null
@@ -1,230 +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.recents.views;
-
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.ValueAnimator;
-import android.annotation.IntDef;
-import android.util.SparseArray;
-import android.util.SparseLongArray;
-import android.view.View;
-import android.view.animation.Interpolator;
-
-import com.android.systemui.Interpolators;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.List;
-
-/**
- * The generic set of animation properties to animate a {@link View}. The animation can have
- * different interpolators, start delays and durations for each of the different properties.
- */
-public class AnimationProps {
-
- public static final AnimationProps IMMEDIATE = new AnimationProps(0, Interpolators.LINEAR);
-
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({ALL, TRANSLATION_X, TRANSLATION_Y, TRANSLATION_Z, ALPHA, SCALE, BOUNDS})
- public @interface PropType {}
-
- public static final int ALL = 0;
- public static final int TRANSLATION_X = 1;
- public static final int TRANSLATION_Y = 2;
- public static final int TRANSLATION_Z = 3;
- public static final int ALPHA = 4;
- public static final int SCALE = 5;
- public static final int BOUNDS = 6;
- public static final int DIM_ALPHA = 7;
- public static final int FOCUS_STATE = 8;
-
- private SparseLongArray mPropStartDelay;
- private SparseLongArray mPropDuration;
- private SparseArray<Interpolator> mPropInterpolators;
- private Animator.AnimatorListener mListener;
-
- /**
- * The builder constructor.
- */
- public AnimationProps() {}
-
- /**
- * Creates an animation with a default {@param duration} and {@param interpolator} for all
- * properties in this animation.
- */
- public AnimationProps(int duration, Interpolator interpolator) {
- this(0, duration, interpolator, null);
- }
-
- /**
- * Creates an animation with a default {@param duration} and {@param interpolator} for all
- * properties in this animation.
- */
- public AnimationProps(int duration, Interpolator interpolator,
- Animator.AnimatorListener listener) {
- this(0, duration, interpolator, listener);
- }
-
- /**
- * Creates an animation with a default {@param startDelay}, {@param duration} and
- * {@param interpolator} for all properties in this animation.
- */
- public AnimationProps(int startDelay, int duration, Interpolator interpolator) {
- this(startDelay, duration, interpolator, null);
- }
-
- /**
- * Creates an animation with a default {@param startDelay}, {@param duration} and
- * {@param interpolator} for all properties in this animation.
- */
- public AnimationProps(int startDelay, int duration, Interpolator interpolator,
- Animator.AnimatorListener listener) {
- setStartDelay(ALL, startDelay);
- setDuration(ALL, duration);
- setInterpolator(ALL, interpolator);
- setListener(listener);
- }
-
- /**
- * Creates a new {@link AnimatorSet} that will animate the given animators. Callers need to
- * manually apply the individual animation properties for each of the animators respectively.
- */
- public AnimatorSet createAnimator(List<Animator> animators) {
- AnimatorSet anim = new AnimatorSet();
- if (mListener != null) {
- anim.addListener(mListener);
- }
- anim.playTogether(animators);
- return anim;
- }
-
- /**
- * Applies the specific start delay, duration and interpolator to the given {@param animator}
- * for the specified {@param propertyType}.
- */
- public <T extends ValueAnimator> T apply(@PropType int propertyType, T animator) {
- animator.setStartDelay(getStartDelay(propertyType));
- animator.setDuration(getDuration(propertyType));
- animator.setInterpolator(getInterpolator(propertyType));
- return animator;
- }
-
- /**
- * Sets a start delay for a specific property.
- */
- public AnimationProps setStartDelay(@PropType int propertyType, int startDelay) {
- if (mPropStartDelay == null) {
- mPropStartDelay = new SparseLongArray();
- }
- mPropStartDelay.append(propertyType, startDelay);
- return this;
- }
-
- /**
- * Returns the start delay for a specific property.
- */
- public long getStartDelay(@PropType int propertyType) {
- if (mPropStartDelay != null) {
- long startDelay = mPropStartDelay.get(propertyType, -1);
- if (startDelay != -1) {
- return startDelay;
- }
- return mPropStartDelay.get(ALL, 0);
- }
- return 0;
- }
-
- /**
- * Sets a duration for a specific property.
- */
- public AnimationProps setDuration(@PropType int propertyType, int duration) {
- if (mPropDuration == null) {
- mPropDuration = new SparseLongArray();
- }
- mPropDuration.append(propertyType, duration);
- return this;
- }
-
- /**
- * Returns the duration for a specific property.
- */
- public long getDuration(@PropType int propertyType) {
- if (mPropDuration != null) {
- long duration = mPropDuration.get(propertyType, -1);
- if (duration != -1) {
- return duration;
- }
- return mPropDuration.get(ALL, 0);
- }
- return 0;
- }
-
- /**
- * Sets an interpolator for a specific property.
- */
- public AnimationProps setInterpolator(@PropType int propertyType, Interpolator interpolator) {
- if (mPropInterpolators == null) {
- mPropInterpolators = new SparseArray<>();
- }
- mPropInterpolators.append(propertyType, interpolator);
- return this;
- }
-
- /**
- * Returns the interpolator for a specific property, falling back to the general interpolator
- * if there is no specific property interpolator.
- */
- public Interpolator getInterpolator(@PropType int propertyType) {
- if (mPropInterpolators != null) {
- Interpolator interp = mPropInterpolators.get(propertyType);
- if (interp != null) {
- return interp;
- }
- return mPropInterpolators.get(ALL, Interpolators.LINEAR);
- }
- return Interpolators.LINEAR;
- }
-
- /**
- * Sets an animator listener for this animation.
- */
- public AnimationProps setListener(Animator.AnimatorListener listener) {
- mListener = listener;
- return this;
- }
-
- /**
- * Returns the animator listener for this animation.
- */
- public Animator.AnimatorListener getListener() {
- return mListener;
- }
-
- /**
- * Returns whether this animation has any duration.
- */
- public boolean isImmediate() {
- int count = mPropDuration.size();
- for (int i = 0; i < count; i++) {
- if (mPropDuration.valueAt(i) > 0) {
- return false;
- }
- }
- return true;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/DockState.java b/packages/SystemUI/src/com/android/systemui/recents/views/DockState.java
new file mode 100644
index 000000000000..65b96fbb52f9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/DockState.java
@@ -0,0 +1,351 @@
+/*
+ * 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.recents.views;
+
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
+import static android.view.WindowManager.DOCKED_BOTTOM;
+import static android.view.WindowManager.DOCKED_INVALID;
+import static android.view.WindowManager.DOCKED_LEFT;
+import static android.view.WindowManager.DOCKED_RIGHT;
+import static android.view.WindowManager.DOCKED_TOP;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.annotation.IntDef;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.ColorDrawable;
+import android.util.IntProperty;
+import android.view.animation.Interpolator;
+
+import com.android.internal.policy.DockedDividerUtils;
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.shared.recents.utilities.Utilities;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+
+/**
+ * The various possible dock states when dragging and dropping a task.
+ */
+public class DockState implements DropTarget {
+
+ public static final int DOCK_AREA_BG_COLOR = 0xFFffffff;
+ public static final int DOCK_AREA_GRID_BG_COLOR = 0xFF000000;
+
+ // The rotation to apply to the hint text
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({HORIZONTAL, VERTICAL})
+ public @interface TextOrientation {}
+ private static final int HORIZONTAL = 0;
+ private static final int VERTICAL = 1;
+
+ private static final int DOCK_AREA_ALPHA = 80;
+ public static final DockState NONE = new DockState(DOCKED_INVALID, -1, 80, 255, HORIZONTAL,
+ null, null, null);
+ public static final DockState LEFT = new DockState(DOCKED_LEFT,
+ SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, 0, VERTICAL,
+ new RectF(0, 0, 0.125f, 1), new RectF(0, 0, 0.125f, 1),
+ new RectF(0, 0, 0.5f, 1));
+ public static final DockState TOP = new DockState(DOCKED_TOP,
+ SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, 0, HORIZONTAL,
+ new RectF(0, 0, 1, 0.125f), new RectF(0, 0, 1, 0.125f),
+ new RectF(0, 0, 1, 0.5f));
+ public static final DockState RIGHT = new DockState(DOCKED_RIGHT,
+ SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, 0, VERTICAL,
+ new RectF(0.875f, 0, 1, 1), new RectF(0.875f, 0, 1, 1),
+ new RectF(0.5f, 0, 1, 1));
+ public static final DockState BOTTOM = new DockState(DOCKED_BOTTOM,
+ SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, 0, HORIZONTAL,
+ new RectF(0, 0.875f, 1, 1), new RectF(0, 0.875f, 1, 1),
+ new RectF(0, 0.5f, 1, 1));
+
+ @Override
+ public boolean acceptsDrop(int x, int y, int width, int height, Rect insets,
+ boolean isCurrentTarget) {
+ if (isCurrentTarget) {
+ getMappedRect(expandedTouchDockArea, width, height, mTmpRect);
+ return mTmpRect.contains(x, y);
+ } else {
+ getMappedRect(touchArea, width, height, mTmpRect);
+ updateBoundsWithSystemInsets(mTmpRect, insets);
+ return mTmpRect.contains(x, y);
+ }
+ }
+
+ // Represents the view state of this dock state
+ public static class ViewState {
+ private static final IntProperty<ViewState> HINT_ALPHA =
+ new IntProperty<ViewState>("drawableAlpha") {
+ @Override
+ public void setValue(ViewState object, int alpha) {
+ object.mHintTextAlpha = alpha;
+ object.dockAreaOverlay.invalidateSelf();
+ }
+
+ @Override
+ public Integer get(ViewState object) {
+ return object.mHintTextAlpha;
+ }
+ };
+
+ public final int dockAreaAlpha;
+ public final ColorDrawable dockAreaOverlay;
+ public final int hintTextAlpha;
+ public final int hintTextOrientation;
+
+ private final int mHintTextResId;
+ private String mHintText;
+ private Paint mHintTextPaint;
+ private Point mHintTextBounds = new Point();
+ private int mHintTextAlpha = 255;
+ private AnimatorSet mDockAreaOverlayAnimator;
+ private Rect mTmpRect = new Rect();
+
+ private ViewState(int areaAlpha, int hintAlpha, @TextOrientation int hintOrientation,
+ int hintTextResId) {
+ dockAreaAlpha = areaAlpha;
+ dockAreaOverlay = new ColorDrawable(Recents.getConfiguration().isGridEnabled
+ ? DOCK_AREA_GRID_BG_COLOR : DOCK_AREA_BG_COLOR);
+ dockAreaOverlay.setAlpha(0);
+ hintTextAlpha = hintAlpha;
+ hintTextOrientation = hintOrientation;
+ mHintTextResId = hintTextResId;
+ mHintTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mHintTextPaint.setColor(Color.WHITE);
+ }
+
+ /**
+ * Updates the view state with the given context.
+ */
+ public void update(Context context) {
+ Resources res = context.getResources();
+ mHintText = context.getString(mHintTextResId);
+ mHintTextPaint.setTextSize(res.getDimensionPixelSize(
+ R.dimen.recents_drag_hint_text_size));
+ mHintTextPaint.getTextBounds(mHintText, 0, mHintText.length(), mTmpRect);
+ mHintTextBounds.set((int) mHintTextPaint.measureText(mHintText), mTmpRect.height());
+ }
+
+ /**
+ * Draws the current view state.
+ */
+ public void draw(Canvas canvas) {
+ // Draw the overlay background
+ if (dockAreaOverlay.getAlpha() > 0) {
+ dockAreaOverlay.draw(canvas);
+ }
+
+ // Draw the hint text
+ if (mHintTextAlpha > 0) {
+ Rect bounds = dockAreaOverlay.getBounds();
+ int x = bounds.left + (bounds.width() - mHintTextBounds.x) / 2;
+ int y = bounds.top + (bounds.height() + mHintTextBounds.y) / 2;
+ mHintTextPaint.setAlpha(mHintTextAlpha);
+ if (hintTextOrientation == VERTICAL) {
+ canvas.save();
+ canvas.rotate(-90f, bounds.centerX(), bounds.centerY());
+ }
+ canvas.drawText(mHintText, x, y, mHintTextPaint);
+ if (hintTextOrientation == VERTICAL) {
+ canvas.restore();
+ }
+ }
+ }
+
+ /**
+ * Creates a new bounds and alpha animation.
+ */
+ public void startAnimation(Rect bounds, int areaAlpha, int hintAlpha, int duration,
+ Interpolator interpolator, boolean animateAlpha, boolean animateBounds) {
+ if (mDockAreaOverlayAnimator != null) {
+ mDockAreaOverlayAnimator.cancel();
+ }
+
+ ObjectAnimator anim;
+ ArrayList<Animator> animators = new ArrayList<>();
+ if (dockAreaOverlay.getAlpha() != areaAlpha) {
+ if (animateAlpha) {
+ anim = ObjectAnimator.ofInt(dockAreaOverlay,
+ Utilities.DRAWABLE_ALPHA, dockAreaOverlay.getAlpha(), areaAlpha);
+ anim.setDuration(duration);
+ anim.setInterpolator(interpolator);
+ animators.add(anim);
+ } else {
+ dockAreaOverlay.setAlpha(areaAlpha);
+ }
+ }
+ if (mHintTextAlpha != hintAlpha) {
+ if (animateAlpha) {
+ anim = ObjectAnimator.ofInt(this, HINT_ALPHA, mHintTextAlpha,
+ hintAlpha);
+ anim.setDuration(150);
+ anim.setInterpolator(hintAlpha > mHintTextAlpha
+ ? Interpolators.ALPHA_IN
+ : Interpolators.ALPHA_OUT);
+ animators.add(anim);
+ } else {
+ mHintTextAlpha = hintAlpha;
+ dockAreaOverlay.invalidateSelf();
+ }
+ }
+ if (bounds != null && !dockAreaOverlay.getBounds().equals(bounds)) {
+ if (animateBounds) {
+ PropertyValuesHolder prop = PropertyValuesHolder.ofObject(
+ Utilities.DRAWABLE_RECT, Utilities.RECT_EVALUATOR,
+ new Rect(dockAreaOverlay.getBounds()), bounds);
+ anim = ObjectAnimator.ofPropertyValuesHolder(dockAreaOverlay, prop);
+ anim.setDuration(duration);
+ anim.setInterpolator(interpolator);
+ animators.add(anim);
+ } else {
+ dockAreaOverlay.setBounds(bounds);
+ }
+ }
+ if (!animators.isEmpty()) {
+ mDockAreaOverlayAnimator = new AnimatorSet();
+ mDockAreaOverlayAnimator.playTogether(animators);
+ mDockAreaOverlayAnimator.start();
+ }
+ }
+ }
+
+ public final int dockSide;
+ public final int createMode;
+ public final ViewState viewState;
+ private final RectF touchArea;
+ private final RectF dockArea;
+ private final RectF expandedTouchDockArea;
+ private static final Rect mTmpRect = new Rect();
+
+ /**
+ * @param createMode used to pass to ActivityManager to dock the task
+ * @param touchArea the area in which touch will initiate this dock state
+ * @param dockArea the visible dock area
+ * @param expandedTouchDockArea the area in which touch will continue to dock after entering
+ * the initial touch area. This is also the new dock area to
+ * draw.
+ */
+ DockState(int dockSide, int createMode, int dockAreaAlpha, int hintTextAlpha,
+ @TextOrientation int hintTextOrientation, RectF touchArea, RectF dockArea,
+ RectF expandedTouchDockArea) {
+ this.dockSide = dockSide;
+ this.createMode = createMode;
+ this.viewState = new ViewState(dockAreaAlpha, hintTextAlpha, hintTextOrientation,
+ R.string.recents_drag_hint_message);
+ this.dockArea = dockArea;
+ this.touchArea = touchArea;
+ this.expandedTouchDockArea = expandedTouchDockArea;
+ }
+
+ /**
+ * Updates the dock state with the given context.
+ */
+ public void update(Context context) {
+ viewState.update(context);
+ }
+
+ /**
+ * Returns the docked task bounds with the given {@param width} and {@param height}.
+ */
+ public Rect getPreDockedBounds(int width, int height, Rect insets) {
+ getMappedRect(dockArea, width, height, mTmpRect);
+ return updateBoundsWithSystemInsets(mTmpRect, insets);
+ }
+
+ /**
+ * Returns the expanded docked task bounds with the given {@param width} and
+ * {@param height}.
+ */
+ public Rect getDockedBounds(int width, int height, int dividerSize, Rect insets,
+ Resources res) {
+ // Calculate the docked task bounds
+ boolean isHorizontalDivision =
+ res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
+ int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision,
+ insets, width, height, dividerSize);
+ Rect newWindowBounds = new Rect();
+ DockedDividerUtils.calculateBoundsForPosition(position, dockSide, newWindowBounds,
+ width, height, dividerSize);
+ return newWindowBounds;
+ }
+
+ /**
+ * Returns the task stack bounds with the given {@param width} and
+ * {@param height}.
+ */
+ public Rect getDockedTaskStackBounds(Rect displayRect, int width, int height,
+ int dividerSize, Rect insets, TaskStackLayoutAlgorithm layoutAlgorithm,
+ Resources res, Rect windowRectOut) {
+ // Calculate the inverse docked task bounds
+ boolean isHorizontalDivision =
+ res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
+ int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision,
+ insets, width, height, dividerSize);
+ DockedDividerUtils.calculateBoundsForPosition(position,
+ DockedDividerUtils.invertDockSide(dockSide), windowRectOut, width, height,
+ dividerSize);
+
+ // Calculate the task stack bounds from the new window bounds
+ Rect taskStackBounds = new Rect();
+ // If the task stack bounds is specifically under the dock area, then ignore the top
+ // inset
+ int top = dockArea.bottom < 1f
+ ? 0
+ : insets.top;
+ // For now, ignore the left insets since we always dock on the left and show Recents
+ // on the right
+ layoutAlgorithm.getTaskStackBounds(displayRect, windowRectOut, top, 0, insets.right,
+ taskStackBounds);
+ return taskStackBounds;
+ }
+
+ /**
+ * Returns the expanded bounds in certain dock sides such that the bounds account for the
+ * system insets (namely the vertical nav bar). This call modifies and returns the given
+ * {@param bounds}.
+ */
+ private Rect updateBoundsWithSystemInsets(Rect bounds, Rect insets) {
+ if (dockSide == DOCKED_LEFT) {
+ bounds.right += insets.left;
+ } else if (dockSide == DOCKED_RIGHT) {
+ bounds.left -= insets.right;
+ }
+ return bounds;
+ }
+
+ /**
+ * Returns the mapped rect to the given dimensions.
+ */
+ private void getMappedRect(RectF bounds, int width, int height, Rect out) {
+ out.set((int) (bounds.left * width), (int) (bounds.top * height),
+ (int) (bounds.right * width), (int) (bounds.bottom * height));
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
deleted file mode 100644
index 035c058c7e8d..000000000000
--- a/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.views;
-
-import android.content.Context;
-import android.graphics.RectF;
-import android.util.ArrayMap;
-
-import com.android.systemui.R;
-import com.android.systemui.recents.model.Task;
-
-import java.util.Collections;
-import java.util.List;
-
-/**
- * The layout logic for the contents of the freeform workspace.
- */
-public class FreeformWorkspaceLayoutAlgorithm {
-
- // Optimization, allows for quick lookup of task -> rect
- private ArrayMap<Task.TaskKey, RectF> mTaskRectMap = new ArrayMap<>();
-
- private int mTaskPadding;
-
- public FreeformWorkspaceLayoutAlgorithm(Context context) {
- reloadOnConfigurationChange(context);
- }
-
- /**
- * Reloads the layout for the current configuration.
- */
- public void reloadOnConfigurationChange(Context context) {
- // This is applied to the edges of each task
- mTaskPadding = context.getResources().getDimensionPixelSize(
- R.dimen.recents_freeform_layout_task_padding) / 2;
- }
-
- /**
- * Updates the layout for each of the freeform workspace tasks. This is called after the stack
- * layout is updated.
- */
- public void update(List<Task> freeformTasks, TaskStackLayoutAlgorithm stackLayout) {
- Collections.reverse(freeformTasks);
- mTaskRectMap.clear();
-
- int numFreeformTasks = stackLayout.mNumFreeformTasks;
- if (!freeformTasks.isEmpty()) {
-
- // Normalize the widths so that we can calculate the best layout below
- int workspaceWidth = stackLayout.mFreeformRect.width();
- int workspaceHeight = stackLayout.mFreeformRect.height();
- float normalizedWorkspaceWidth = (float) workspaceWidth / workspaceHeight;
- float normalizedWorkspaceHeight = 1f;
- float[] normalizedTaskWidths = new float[numFreeformTasks];
- for (int i = 0; i < numFreeformTasks; i++) {
- Task task = freeformTasks.get(i);
- float rowTaskWidth;
- if (task.bounds != null) {
- rowTaskWidth = (float) task.bounds.width() / task.bounds.height();
- } else {
- // If this is a stack task that was dragged into the freeform workspace, then
- // the task will not yet have an associated bounds, so assume the full workspace
- // width for the time being
- rowTaskWidth = normalizedWorkspaceWidth;
- }
- // Bound the task width to the workspace width so that at the worst case, it will
- // fit its own row
- normalizedTaskWidths[i] = Math.min(rowTaskWidth, normalizedWorkspaceWidth);
- }
-
- // Determine the scale to best fit each of the tasks in the workspace
- float rowScale = 0.85f;
- float rowWidth = 0f;
- float maxRowWidth = 0f;
- int rowCount = 1;
- for (int i = 0; i < numFreeformTasks;) {
- float width = normalizedTaskWidths[i] * rowScale;
- if (rowWidth + width > normalizedWorkspaceWidth) {
- // That is too long for this row, create new row
- if ((rowCount + 1) * rowScale > normalizedWorkspaceHeight) {
- // The new row is too high, so we need to try fitting again. Update the
- // scale to be the smaller of the scale needed to fit the task in the
- // previous row, or the scale needed to fit the new row
- rowScale = Math.min(normalizedWorkspaceWidth / (rowWidth + width),
- normalizedWorkspaceHeight / (rowCount + 1));
- rowCount = 1;
- rowWidth = 0;
- i = 0;
- } else {
- // The new row fits, so continue
- rowWidth = width;
- rowCount++;
- i++;
- }
- } else {
- // Task is OK in this row
- rowWidth += width;
- i++;
- }
- maxRowWidth = Math.max(rowWidth, maxRowWidth);
- }
-
- // Normalize each of the actual rects to that scale
- float defaultRowLeft = ((1f - (maxRowWidth / normalizedWorkspaceWidth)) *
- workspaceWidth) / 2f;
- float rowLeft = defaultRowLeft;
- float rowTop = ((1f - (rowScale * rowCount)) * workspaceHeight) / 2f;
- float rowHeight = rowScale * workspaceHeight;
- for (int i = 0; i < numFreeformTasks; i++) {
- Task task = freeformTasks.get(i);
- float width = rowHeight * normalizedTaskWidths[i];
- if (rowLeft + width > workspaceWidth) {
- // This goes on the next line
- rowTop += rowHeight;
- rowLeft = defaultRowLeft;
- }
- RectF rect = new RectF(rowLeft, rowTop, rowLeft + width, rowTop + rowHeight);
- rect.inset(mTaskPadding, mTaskPadding);
- rowLeft += width;
- mTaskRectMap.put(task.key, rect);
- }
- }
- }
-
- /**
- * Returns whether the transform is available for the given task.
- */
- public boolean isTransformAvailable(Task task, TaskStackLayoutAlgorithm stackLayout) {
- if (stackLayout.mNumFreeformTasks == 0 || task == null) {
- return false;
- }
- return mTaskRectMap.containsKey(task.key);
- }
-
- /**
- * Returns the transform for the given task. Any rect returned will be offset by the actual
- * transform for the freeform workspace.
- */
- public TaskViewTransform getTransform(Task task, TaskViewTransform transformOut,
- TaskStackLayoutAlgorithm stackLayout) {
- if (mTaskRectMap.containsKey(task.key)) {
- final RectF ffRect = mTaskRectMap.get(task.key);
-
- transformOut.scale = 1f;
- transformOut.alpha = 1f;
- transformOut.translationZ = stackLayout.mMaxTranslationZ;
- transformOut.dimAlpha = 0f;
- transformOut.viewOutlineAlpha = TaskStackLayoutAlgorithm.OUTLINE_ALPHA_MAX_VALUE;
- transformOut.rect.set(ffRect);
- transformOut.rect.offset(stackLayout.mFreeformRect.left, stackLayout.mFreeformRect.top);
- transformOut.visible = true;
- return transformOut;
- }
- return null;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionComposer.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionComposer.java
new file mode 100644
index 000000000000..1c47430106e8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionComposer.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.views;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.GraphicBuffer;
+import android.graphics.Rect;
+import android.util.Log;
+
+import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.RecentsDebugFlags;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat;
+import com.android.systemui.shared.recents.view.RecentsTransition;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A helper class to create the transition app animation specs to/from Recents
+ */
+public class RecentsTransitionComposer {
+
+ private static final String TAG = "RecentsTransitionComposer";
+
+ private Context mContext;
+ private TaskViewTransform mTmpTransform = new TaskViewTransform();
+
+ public RecentsTransitionComposer(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Composes a single animation spec for the given {@link TaskView}
+ */
+ private static AppTransitionAnimationSpecCompat composeAnimationSpec(TaskStackView stackView,
+ TaskView taskView, TaskViewTransform transform, boolean addHeaderBitmap) {
+ Bitmap b = null;
+ if (addHeaderBitmap) {
+ b = composeHeaderBitmap(taskView, transform);
+ if (b == null) {
+ return null;
+ }
+ }
+
+ Rect taskRect = new Rect();
+ transform.rect.round(taskRect);
+ // Disable in for low ram devices because each task does in Recents does not have fullscreen
+ // height (stackView height) and when transitioning to fullscreen app, the code below would
+ // force the task thumbnail to full stackView height immediately causing the transition
+ // jarring.
+ if (!Recents.getConfiguration().isLowRamDevice && taskView.getTask() !=
+ stackView.getStack().getFrontMostTask()) {
+ taskRect.bottom = taskRect.top + stackView.getMeasuredHeight();
+ }
+ return new AppTransitionAnimationSpecCompat(taskView.getTask().key.id, b, taskRect);
+ }
+
+ /**
+ * Composes the transition spec when docking a task, which includes a full task bitmap.
+ */
+ public List<AppTransitionAnimationSpecCompat> composeDockAnimationSpec(TaskView taskView,
+ Rect bounds) {
+ mTmpTransform.fillIn(taskView);
+ Task task = taskView.getTask();
+ Bitmap buffer = RecentsTransitionComposer.composeTaskBitmap(taskView, mTmpTransform);
+ return Collections.singletonList(new AppTransitionAnimationSpecCompat(task.key.id, buffer,
+ bounds));
+ }
+
+ /**
+ * Composes the animation specs for all the tasks in the target stack.
+ */
+ public List<AppTransitionAnimationSpecCompat> composeAnimationSpecs(final Task task,
+ final TaskStackView stackView, int windowingMode, int activityType, Rect windowRect) {
+ // Calculate the offscreen task rect (for tasks that are not backed by views)
+ TaskView taskView = stackView.getChildViewForTask(task);
+ TaskStackLayoutAlgorithm stackLayout = stackView.getStackAlgorithm();
+ Rect offscreenTaskRect = new Rect();
+ stackLayout.getFrontOfStackTransform().rect.round(offscreenTaskRect);
+
+ // If this is a full screen stack, the transition will be towards the single, full screen
+ // task. We only need the transition spec for this task.
+
+ // TODO: Sometimes targetStackId is not initialized after reboot, so we also have to
+ // check for INVALID_STACK_ID (now WINDOWING_MODE_UNDEFINED)
+ if (windowingMode == WINDOWING_MODE_FULLSCREEN
+ || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+ || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
+ || activityType == ACTIVITY_TYPE_ASSISTANT
+ || windowingMode == WINDOWING_MODE_UNDEFINED) {
+ List<AppTransitionAnimationSpecCompat> specs = new ArrayList<>();
+ if (taskView == null) {
+ specs.add(composeOffscreenAnimationSpec(task, offscreenTaskRect));
+ } else {
+ mTmpTransform.fillIn(taskView);
+ stackLayout.transformToScreenCoordinates(mTmpTransform, windowRect);
+ AppTransitionAnimationSpecCompat spec = composeAnimationSpec(stackView, taskView,
+ mTmpTransform, true /* addHeaderBitmap */);
+ if (spec != null) {
+ specs.add(spec);
+ }
+ }
+ return specs;
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * Composes a single animation spec for the given {@link Task}
+ */
+ private static AppTransitionAnimationSpecCompat composeOffscreenAnimationSpec(Task task,
+ Rect taskRect) {
+ return new AppTransitionAnimationSpecCompat(task.key.id, null, taskRect);
+ }
+
+ public static Bitmap composeTaskBitmap(TaskView taskView, TaskViewTransform transform) {
+ float scale = transform.scale;
+ int fromWidth = (int) (transform.rect.width() * scale);
+ int fromHeight = (int) (transform.rect.height() * scale);
+ if (fromWidth == 0 || fromHeight == 0) {
+ Log.e(TAG, "Could not compose thumbnail for task: " + taskView.getTask() +
+ " at transform: " + transform);
+
+ return RecentsTransition.drawViewIntoHardwareBitmap(1, 1, null, 1f, 0x00ffffff);
+ } else {
+ if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
+ return RecentsTransition.drawViewIntoHardwareBitmap(fromWidth, fromHeight, null, 1f,
+ 0xFFff0000);
+ } else {
+ return RecentsTransition.drawViewIntoHardwareBitmap(fromWidth, fromHeight, taskView,
+ scale, 0);
+ }
+ }
+ }
+
+ private static Bitmap composeHeaderBitmap(TaskView taskView,
+ TaskViewTransform transform) {
+ float scale = transform.scale;
+ int headerWidth = (int) (transform.rect.width());
+ int headerHeight = (int) (taskView.mHeaderView.getMeasuredHeight() * scale);
+ if (headerWidth == 0 || headerHeight == 0) {
+ return null;
+ }
+
+ if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
+ return RecentsTransition.drawViewIntoHardwareBitmap(headerWidth, headerHeight, null, 1f,
+ 0xFFff0000);
+ } else {
+ return RecentsTransition.drawViewIntoHardwareBitmap(headerWidth, headerHeight,
+ taskView.mHeaderView, scale, 0);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
deleted file mode 100644
index b2675d7ac858..000000000000
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
+++ /dev/null
@@ -1,498 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.views;
-
-import static android.app.ActivityManager.StackId.ASSISTANT_STACK_ID;
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-
-import android.annotation.Nullable;
-import android.app.ActivityManager.StackId;
-import android.app.ActivityOptions;
-import android.app.ActivityOptions.OnAnimationStartedListener;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.GraphicBuffer;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IRemoteCallback;
-import android.os.RemoteException;
-import android.util.Log;
-import android.view.AppTransitionAnimationSpec;
-import android.view.DisplayListCanvas;
-import android.view.IAppTransitionAnimationSpecsFuture;
-import android.view.RenderNode;
-import android.view.ThreadedRenderer;
-import android.view.View;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.RecentsDebugFlags;
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
-import com.android.systemui.recents.events.activity.ExitRecentsWindowFirstAnimationFrameEvent;
-import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
-import com.android.systemui.recents.events.activity.LaunchTaskStartedEvent;
-import com.android.systemui.recents.events.activity.LaunchTaskSucceededEvent;
-import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
-import com.android.systemui.recents.events.component.SetWaitingForTransitionStartEvent;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.TaskStack;
-import com.android.systemui.statusbar.phone.StatusBar;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * A helper class to create transitions to/from Recents
- */
-public class RecentsTransitionHelper {
-
- private static final String TAG = "RecentsTransitionHelper";
- private static final boolean DEBUG = false;
-
- /**
- * Special value for {@link #mAppTransitionAnimationSpecs}: Indicate that we are currently
- * waiting for the specs to be retrieved.
- */
- private static final List<AppTransitionAnimationSpec> SPECS_WAITING = new ArrayList<>();
-
- @GuardedBy("this")
- private List<AppTransitionAnimationSpec> mAppTransitionAnimationSpecs = SPECS_WAITING;
-
- private Context mContext;
- private Handler mHandler;
- private TaskViewTransform mTmpTransform = new TaskViewTransform();
-
- private class StartScreenPinningRunnableRunnable implements Runnable {
-
- private int taskId = -1;
-
- @Override
- public void run() {
- EventBus.getDefault().send(new ScreenPinningRequestEvent(mContext, taskId));
- }
- }
- private StartScreenPinningRunnableRunnable mStartScreenPinningRunnable
- = new StartScreenPinningRunnableRunnable();
-
- public RecentsTransitionHelper(Context context) {
- mContext = context;
- mHandler = new Handler();
- }
-
- /**
- * Launches the specified {@link Task}.
- */
- public void launchTaskFromRecents(final TaskStack stack, @Nullable final Task task,
- final TaskStackView stackView, final TaskView taskView,
- final boolean screenPinningRequested, final int destinationStack) {
-
- final ActivityOptions.OnAnimationStartedListener animStartedListener;
- final AppTransitionAnimationSpecsFuture transitionFuture;
- if (taskView != null) {
-
- // Fetch window rect here already in order not to be blocked on lock contention in WM
- // when the future calls it.
- final Rect windowRect = Recents.getSystemServices().getWindowRect();
- transitionFuture = getAppTransitionFuture(
- () -> composeAnimationSpecs(task, stackView, destinationStack, windowRect));
- animStartedListener = new OnAnimationStartedListener() {
- private boolean mHandled;
-
- @Override
- public void onAnimationStarted() {
- if (mHandled) {
- return;
- }
- mHandled = true;
-
- // If we are launching into another task, cancel the previous task's
- // window transition
- EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
- EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
- stackView.cancelAllTaskViewAnimations();
-
- if (screenPinningRequested) {
- // Request screen pinning after the animation runs
- mStartScreenPinningRunnable.taskId = task.key.id;
- mHandler.postDelayed(mStartScreenPinningRunnable, 350);
- }
-
- if (!Recents.getConfiguration().isLowRamDevice) {
- // Reset the state where we are waiting for the transition to start
- EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(false));
- }
- }
- };
- } else {
- // This is only the case if the task is not on screen (scrolled offscreen for example)
- transitionFuture = null;
- animStartedListener = new OnAnimationStartedListener() {
- private boolean mHandled;
-
- @Override
- public void onAnimationStarted() {
- if (mHandled) {
- return;
- }
- mHandled = true;
-
- // If we are launching into another task, cancel the previous task's
- // window transition
- EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
- EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
- stackView.cancelAllTaskViewAnimations();
-
- if (!Recents.getConfiguration().isLowRamDevice) {
- // Reset the state where we are waiting for the transition to start
- EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(false));
- }
- }
- };
- }
-
- EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(true));
- final ActivityOptions opts = ActivityOptions.makeMultiThumbFutureAspectScaleAnimation(mContext,
- mHandler, transitionFuture != null ? transitionFuture.future : null,
- animStartedListener, true /* scaleUp */);
- if (taskView == null) {
- // If there is no task view, then we do not need to worry about animating out occluding
- // task views, and we can launch immediately
- startTaskActivity(stack, task, taskView, opts, transitionFuture, destinationStack);
- } else {
- LaunchTaskStartedEvent launchStartedEvent = new LaunchTaskStartedEvent(taskView,
- screenPinningRequested);
- if (task.group != null && !task.group.isFrontMostTask(task)) {
- launchStartedEvent.addPostAnimationCallback(new Runnable() {
- @Override
- public void run() {
- startTaskActivity(stack, task, taskView, opts, transitionFuture,
- destinationStack);
- }
- });
- EventBus.getDefault().send(launchStartedEvent);
- } else {
- EventBus.getDefault().send(launchStartedEvent);
- startTaskActivity(stack, task, taskView, opts, transitionFuture, destinationStack);
- }
- }
- Recents.getSystemServices().sendCloseSystemWindows(
- StatusBar.SYSTEM_DIALOG_REASON_HOME_KEY);
- }
-
- public IRemoteCallback wrapStartedListener(final OnAnimationStartedListener listener) {
- if (listener == null) {
- return null;
- }
- return new IRemoteCallback.Stub() {
- @Override
- public void sendResult(Bundle data) throws RemoteException {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- listener.onAnimationStarted();
- }
- });
- }
- };
- }
-
- /**
- * Starts the activity for the launch task.
- *
- * @param taskView this is the {@link TaskView} that we are launching from. This can be null if
- * we are toggling recents and the launch-to task is now offscreen.
- * @param destinationStack id of the stack to put the task into.
- */
- private void startTaskActivity(TaskStack stack, Task task, @Nullable TaskView taskView,
- ActivityOptions opts, AppTransitionAnimationSpecsFuture transitionFuture,
- int destinationStack) {
- SystemServicesProxy ssp = Recents.getSystemServices();
- ssp.startActivityFromRecents(mContext, task.key, task.title, opts, destinationStack,
- succeeded -> {
- if (succeeded) {
- // Keep track of the index of the task launch
- int taskIndexFromFront = 0;
- int taskIndex = stack.indexOfStackTask(task);
- if (taskIndex > -1) {
- taskIndexFromFront = stack.getTaskCount() - taskIndex - 1;
- }
- EventBus.getDefault().send(new LaunchTaskSucceededEvent(taskIndexFromFront));
- } else {
- // Dismiss the task if we fail to launch it
- if (taskView != null) {
- taskView.dismissTask();
- }
-
- // Keep track of failed launches
- EventBus.getDefault().send(new LaunchTaskFailedEvent());
- }
- });
- if (transitionFuture != null) {
- mHandler.post(transitionFuture::precacheSpecs);
- }
- }
-
- /**
- * Creates a future which will later be queried for animation specs for this current transition.
- *
- * @param composer The implementation that composes the specs on the UI thread.
- */
- public AppTransitionAnimationSpecsFuture getAppTransitionFuture(
- final AnimationSpecComposer composer) {
- synchronized (this) {
- mAppTransitionAnimationSpecs = SPECS_WAITING;
- }
- IAppTransitionAnimationSpecsFuture future = new IAppTransitionAnimationSpecsFuture.Stub() {
- @Override
- public AppTransitionAnimationSpec[] get() throws RemoteException {
- mHandler.post(() -> {
- synchronized (RecentsTransitionHelper.this) {
- mAppTransitionAnimationSpecs = composer.composeSpecs();
- RecentsTransitionHelper.this.notifyAll();
- }
- });
- synchronized (RecentsTransitionHelper.this) {
- while (mAppTransitionAnimationSpecs == SPECS_WAITING) {
- try {
- RecentsTransitionHelper.this.wait();
- } catch (InterruptedException e) {}
- }
- if (mAppTransitionAnimationSpecs == null) {
- return null;
- }
- AppTransitionAnimationSpec[] specs
- = new AppTransitionAnimationSpec[mAppTransitionAnimationSpecs.size()];
- mAppTransitionAnimationSpecs.toArray(specs);
- mAppTransitionAnimationSpecs = SPECS_WAITING;
- return specs;
- }
- }
- };
- return new AppTransitionAnimationSpecsFuture(composer, future);
- }
-
- /**
- * Composes the transition spec when docking a task, which includes a full task bitmap.
- */
- public List<AppTransitionAnimationSpec> composeDockAnimationSpec(TaskView taskView,
- Rect bounds) {
- mTmpTransform.fillIn(taskView);
- Task task = taskView.getTask();
- GraphicBuffer buffer = RecentsTransitionHelper.composeTaskBitmap(taskView, mTmpTransform);
- return Collections.singletonList(new AppTransitionAnimationSpec(task.key.id, buffer,
- bounds));
- }
-
- /**
- * Composes the animation specs for all the tasks in the target stack.
- */
- private List<AppTransitionAnimationSpec> composeAnimationSpecs(final Task task,
- final TaskStackView stackView, final int destinationStack, Rect windowRect) {
- // Ensure we have a valid target stack id
- final int targetStackId = destinationStack != INVALID_STACK_ID ?
- destinationStack : task.key.stackId;
- if (!StackId.useAnimationSpecForAppTransition(targetStackId)) {
- return null;
- }
-
- // Calculate the offscreen task rect (for tasks that are not backed by views)
- TaskView taskView = stackView.getChildViewForTask(task);
- TaskStackLayoutAlgorithm stackLayout = stackView.getStackAlgorithm();
- Rect offscreenTaskRect = new Rect();
- stackLayout.getFrontOfStackTransform().rect.round(offscreenTaskRect);
-
- // If this is a full screen stack, the transition will be towards the single, full screen
- // task. We only need the transition spec for this task.
- List<AppTransitionAnimationSpec> specs = new ArrayList<>();
-
- // TODO: Sometimes targetStackId is not initialized after reboot, so we also have to
- // check for INVALID_STACK_ID
- if (targetStackId == FULLSCREEN_WORKSPACE_STACK_ID || targetStackId == DOCKED_STACK_ID
- || targetStackId == ASSISTANT_STACK_ID || targetStackId == INVALID_STACK_ID) {
- if (taskView == null) {
- specs.add(composeOffscreenAnimationSpec(task, offscreenTaskRect));
- } else {
- mTmpTransform.fillIn(taskView);
- stackLayout.transformToScreenCoordinates(mTmpTransform, windowRect);
- AppTransitionAnimationSpec spec = composeAnimationSpec(stackView, taskView,
- mTmpTransform, true /* addHeaderBitmap */);
- if (spec != null) {
- specs.add(spec);
- }
- }
- return specs;
- }
-
- // Otherwise, for freeform tasks, create a new animation spec for each task we have to
- // launch
- TaskStack stack = stackView.getStack();
- ArrayList<Task> tasks = stack.getStackTasks();
- int taskCount = tasks.size();
- for (int i = taskCount - 1; i >= 0; i--) {
- Task t = tasks.get(i);
- if (t.isFreeformTask() || targetStackId == FREEFORM_WORKSPACE_STACK_ID) {
- TaskView tv = stackView.getChildViewForTask(t);
- if (tv == null) {
- // TODO: Create a different animation task rect for this case (though it should
- // never happen)
- specs.add(composeOffscreenAnimationSpec(t, offscreenTaskRect));
- } else {
- mTmpTransform.fillIn(taskView);
- stackLayout.transformToScreenCoordinates(mTmpTransform,
- null /* windowOverrideRect */);
- AppTransitionAnimationSpec spec = composeAnimationSpec(stackView, tv,
- mTmpTransform, true /* addHeaderBitmap */);
- if (spec != null) {
- specs.add(spec);
- }
- }
- }
- }
-
- return specs;
- }
-
- /**
- * Composes a single animation spec for the given {@link Task}
- */
- private static AppTransitionAnimationSpec composeOffscreenAnimationSpec(Task task,
- Rect taskRect) {
- return new AppTransitionAnimationSpec(task.key.id, null, taskRect);
- }
-
- public static GraphicBuffer composeTaskBitmap(TaskView taskView, TaskViewTransform transform) {
- float scale = transform.scale;
- int fromWidth = (int) (transform.rect.width() * scale);
- int fromHeight = (int) (transform.rect.height() * scale);
- if (fromWidth == 0 || fromHeight == 0) {
- Log.e(TAG, "Could not compose thumbnail for task: " + taskView.getTask() +
- " at transform: " + transform);
-
- return drawViewIntoGraphicBuffer(1, 1, null, 1f, 0x00ffffff);
- } else {
- if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
- return drawViewIntoGraphicBuffer(fromWidth, fromHeight, null, 1f, 0xFFff0000);
- } else {
- return drawViewIntoGraphicBuffer(fromWidth, fromHeight, taskView, scale, 0);
- }
- }
- }
-
- private static GraphicBuffer composeHeaderBitmap(TaskView taskView,
- TaskViewTransform transform) {
- float scale = transform.scale;
- int headerWidth = (int) (transform.rect.width());
- int headerHeight = (int) (taskView.mHeaderView.getMeasuredHeight() * scale);
- if (headerWidth == 0 || headerHeight == 0) {
- return null;
- }
-
- if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
- return drawViewIntoGraphicBuffer(headerWidth, headerHeight, null, 1f, 0xFFff0000);
- } else {
- return drawViewIntoGraphicBuffer(headerWidth, headerHeight, taskView.mHeaderView,
- scale, 0);
- }
- }
-
- public static GraphicBuffer drawViewIntoGraphicBuffer(int bufferWidth, int bufferHeight,
- View view, float scale, int eraseColor) {
- RenderNode node = RenderNode.create("RecentsTransition", null);
- node.setLeftTopRightBottom(0, 0, bufferWidth, bufferHeight);
- node.setClipToBounds(false);
- DisplayListCanvas c = node.start(bufferWidth, bufferHeight);
- c.scale(scale, scale);
- if (eraseColor != 0) {
- c.drawColor(eraseColor);
- }
- if (view != null) {
- view.draw(c);
- }
- node.end(c);
- Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, bufferWidth, bufferHeight);
- return hwBitmap.createGraphicBufferHandle();
- }
-
- /**
- * Composes a single animation spec for the given {@link TaskView}
- */
- private static AppTransitionAnimationSpec composeAnimationSpec(TaskStackView stackView,
- TaskView taskView, TaskViewTransform transform, boolean addHeaderBitmap) {
- GraphicBuffer b = null;
- if (addHeaderBitmap) {
- b = composeHeaderBitmap(taskView, transform);
- if (b == null) {
- return null;
- }
- }
-
- Rect taskRect = new Rect();
- transform.rect.round(taskRect);
- // Disable in for low ram devices because each task does in Recents does not have fullscreen
- // height (stackView height) and when transitioning to fullscreen app, the code below would
- // force the task thumbnail to full stackView height immediately causing the transition
- // jarring.
- if (!Recents.getConfiguration().isLowRamDevice && taskView.getTask() !=
- stackView.getStack().getStackFrontMostTask(false /* includeFreeformTasks */)) {
- taskRect.bottom = taskRect.top + stackView.getMeasuredHeight();
- }
- return new AppTransitionAnimationSpec(taskView.getTask().key.id, b, taskRect);
- }
-
- public interface AnimationSpecComposer {
- List<AppTransitionAnimationSpec> composeSpecs();
- }
-
- /**
- * Class to be returned from {@link #composeAnimationSpec} that gives access to both the future
- * and the anonymous class used for composing.
- */
- public class AppTransitionAnimationSpecsFuture {
-
- private final AnimationSpecComposer composer;
- private final IAppTransitionAnimationSpecsFuture future;
-
- private AppTransitionAnimationSpecsFuture(AnimationSpecComposer composer,
- IAppTransitionAnimationSpecsFuture future) {
- this.composer = composer;
- this.future = future;
- }
-
- public IAppTransitionAnimationSpecsFuture getFuture() {
- return future;
- }
-
- /**
- * Manually generates and caches the spec such that they are already available when the
- * future needs.
- */
- public void precacheSpecs() {
- synchronized (RecentsTransitionHelper.this) {
- mAppTransitionAnimationSpecs = composer.composeSpecs();
- }
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index f38420e686ab..e3ed1aaa5506 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -16,13 +16,14 @@
package com.android.systemui.recents.views;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
+
+import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS;
-import android.animation.Animator;
-import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.app.ActivityOptions.OnAnimationStartedListener;
+import android.annotation.Nullable;
+import android.app.ActivityOptions;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Canvas;
@@ -32,10 +33,12 @@ import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.IRemoteCallback;
import android.util.ArraySet;
import android.util.AttributeSet;
+import android.util.Log;
import android.util.MathUtils;
-import android.view.AppTransitionAnimationSpec;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
@@ -57,17 +60,23 @@ import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsActivity;
import com.android.systemui.recents.RecentsActivityLaunchState;
import com.android.systemui.recents.RecentsConfiguration;
-import com.android.systemui.recents.RecentsDebugFlags;
import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent;
import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
+import com.android.systemui.recents.events.activity.ExitRecentsWindowFirstAnimationFrameEvent;
import com.android.systemui.recents.events.activity.HideStackActionButtonEvent;
import com.android.systemui.recents.events.activity.LaunchTaskEvent;
+import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
+import com.android.systemui.recents.events.activity.LaunchTaskStartedEvent;
+import com.android.systemui.recents.events.activity.LaunchTaskSucceededEvent;
import com.android.systemui.recents.events.activity.MultiWindowStateChangedEvent;
import com.android.systemui.recents.events.activity.ShowEmptyViewEvent;
import com.android.systemui.recents.events.activity.ShowStackActionButtonEvent;
import com.android.systemui.recents.events.component.ExpandPipEvent;
+import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
+import com.android.systemui.recents.events.component.SetWaitingForTransitionStartEvent;
import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent;
import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent;
@@ -78,11 +87,15 @@ import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
import com.android.systemui.recents.misc.ReferenceCountedTrigger;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.TaskStack;
-import com.android.systemui.recents.views.RecentsTransitionHelper.AnimationSpecComposer;
-import com.android.systemui.recents.views.RecentsTransitionHelper.AppTransitionAnimationSpecsFuture;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.TaskStack;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat;
+import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
+import com.android.systemui.shared.recents.view.RecentsTransition;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.ActivityOptionsCompat;
+import com.android.systemui.shared.system.WindowManagerWrapper;
import com.android.systemui.stackdivider.WindowManagerProxy;
import com.android.systemui.statusbar.FlingAnimationUtils;
import com.android.systemui.statusbar.phone.ScrimController;
@@ -106,6 +119,7 @@ public class RecentsView extends FrameLayout {
private static final int BUSY_RECENTS_TASK_COUNT = 3;
+ private Handler mHandler;
private TaskStackView mTaskStackView;
private TextView mStackActionButton;
private TextView mEmptyView;
@@ -114,7 +128,6 @@ public class RecentsView extends FrameLayout {
private final int mStackButtonShadowColor;
private boolean mAwaitingFirstLayout = true;
- private boolean mLastTaskLaunchedWasFreeform;
@ViewDebug.ExportedProperty(category="recents")
Rect mSystemInsets = new Rect();
@@ -132,7 +145,7 @@ public class RecentsView extends FrameLayout {
mMultiWindowBackgroundScrim.setAlpha(alpha);
};
- private RecentsTransitionHelper mTransitionHelper;
+ private RecentsTransitionComposer mTransitionHelper;
@ViewDebug.ExportedProperty(deepExport=true, prefix="touch_")
private RecentsViewTouchHandler mTouchHandler;
private final FlingAnimationUtils mFlingAnimationUtils;
@@ -154,7 +167,8 @@ public class RecentsView extends FrameLayout {
setWillNotDraw(false);
SystemServicesProxy ssp = Recents.getSystemServices();
- mTransitionHelper = new RecentsTransitionHelper(getContext());
+ mHandler = new Handler();
+ mTransitionHelper = new RecentsTransitionComposer(getContext());
mDividerSize = ssp.getDockedDividerSize(context);
mTouchHandler = new RecentsViewTouchHandler(this);
mFlingAnimationUtils = new FlingAnimationUtils(context, 0.3f);
@@ -165,22 +179,20 @@ public class RecentsView extends FrameLayout {
mEmptyView = (TextView) inflater.inflate(R.layout.recents_empty, this, false);
addView(mEmptyView);
- if (RecentsDebugFlags.Static.EnableStackActionButton) {
- if (mStackActionButton != null) {
- removeView(mStackActionButton);
- }
- mStackActionButton = (TextView) inflater.inflate(Recents.getConfiguration()
- .isLowRamDevice
- ? R.layout.recents_low_ram_stack_action_button
- : R.layout.recents_stack_action_button,
- this, false);
-
- mStackButtonShadowRadius = mStackActionButton.getShadowRadius();
- mStackButtonShadowDistance = new PointF(mStackActionButton.getShadowDx(),
- mStackActionButton.getShadowDy());
- mStackButtonShadowColor = mStackActionButton.getShadowColor();
- addView(mStackActionButton);
+ if (mStackActionButton != null) {
+ removeView(mStackActionButton);
}
+ mStackActionButton = (TextView) inflater.inflate(Recents.getConfiguration()
+ .isLowRamDevice
+ ? R.layout.recents_low_ram_stack_action_button
+ : R.layout.recents_stack_action_button,
+ this, false);
+
+ mStackButtonShadowRadius = mStackActionButton.getShadowRadius();
+ mStackButtonShadowDistance = new PointF(mStackActionButton.getShadowDx(),
+ mStackActionButton.getShadowDy());
+ mStackButtonShadowColor = mStackActionButton.getShadowColor();
+ addView(mStackActionButton);
reevaluateStyles();
}
@@ -232,7 +244,6 @@ public class RecentsView extends FrameLayout {
// Reset the state
mAwaitingFirstLayout = !isResumingFromVisible;
- mLastTaskLaunchedWasFreeform = false;
// Update the stack
mTaskStackView.onReload(isResumingFromVisible);
@@ -288,7 +299,7 @@ public class RecentsView extends FrameLayout {
* @return True if it changed.
*/
private boolean updateBusyness() {
- final int taskCount = mTaskStackView.getStack().getStackTaskCount();
+ final int taskCount = mTaskStackView.getStack().getTaskCount();
final float busyness = Math.min(taskCount, BUSY_RECENTS_TASK_COUNT)
/ (float) BUSY_RECENTS_TASK_COUNT;
if (mBusynessFactor == busyness) {
@@ -319,21 +330,13 @@ public class RecentsView extends FrameLayout {
}
}
- /**
- * Returns whether the last task launched was in the freeform stack or not.
- */
- public boolean isLastTaskLaunchedFreeform() {
- return mLastTaskLaunchedWasFreeform;
- }
-
/** Launches the focused task from the first stack if possible */
public boolean launchFocusedTask(int logEvent) {
if (mTaskStackView != null) {
Task task = mTaskStackView.getFocusedTask();
if (task != null) {
TaskView taskView = mTaskStackView.getChildViewForTask(task);
- EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null,
- INVALID_STACK_ID, false));
+ EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null, false));
if (logEvent != 0) {
MetricsLogger.action(getContext(), logEvent,
@@ -357,32 +360,13 @@ public class RecentsView extends FrameLayout {
Task task = getStack().getLaunchTarget();
if (task != null) {
TaskView taskView = mTaskStackView.getChildViewForTask(task);
- EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null,
- INVALID_STACK_ID, false));
+ EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null, false));
return true;
}
}
return false;
}
- /** Launches a given task. */
- public boolean launchTask(Task task, Rect taskBounds, int destinationStack) {
- if (mTaskStackView != null) {
- // Iterate the stack views and try and find the given task.
- List<TaskView> taskViews = mTaskStackView.getTaskViews();
- int taskViewCount = taskViews.size();
- for (int j = 0; j < taskViewCount; j++) {
- TaskView tv = taskViews.get(j);
- if (tv.getTask() == task) {
- EventBus.getDefault().send(new LaunchTaskEvent(tv, task, taskBounds,
- destinationStack, false));
- return true;
- }
- }
- }
- return false;
- }
-
/**
* Hides the task stack and shows the empty view.
*/
@@ -391,9 +375,7 @@ public class RecentsView extends FrameLayout {
mEmptyView.setText(msgResId);
mEmptyView.setVisibility(View.VISIBLE);
mEmptyView.bringToFront();
- if (RecentsDebugFlags.Static.EnableStackActionButton) {
- mStackActionButton.bringToFront();
- }
+ mStackActionButton.bringToFront();
}
/**
@@ -403,9 +385,7 @@ public class RecentsView extends FrameLayout {
mEmptyView.setVisibility(View.INVISIBLE);
mTaskStackView.setVisibility(View.VISIBLE);
mTaskStackView.bringToFront();
- if (RecentsDebugFlags.Static.EnableStackActionButton) {
- mStackActionButton.bringToFront();
- }
+ mStackActionButton.bringToFront();
}
/**
@@ -453,13 +433,11 @@ public class RecentsView extends FrameLayout {
MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
}
- if (RecentsDebugFlags.Static.EnableStackActionButton) {
- // Measure the stack action button within the constraints of the space above the stack
- Rect buttonBounds = mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect();
- measureChild(mStackActionButton,
- MeasureSpec.makeMeasureSpec(buttonBounds.width(), MeasureSpec.AT_MOST),
- MeasureSpec.makeMeasureSpec(buttonBounds.height(), MeasureSpec.AT_MOST));
- }
+ // Measure the stack action button within the constraints of the space above the stack
+ Rect buttonBounds = mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect();
+ measureChild(mStackActionButton,
+ MeasureSpec.makeMeasureSpec(buttonBounds.width(), MeasureSpec.AT_MOST),
+ MeasureSpec.makeMeasureSpec(buttonBounds.height(), MeasureSpec.AT_MOST));
setMeasuredDimension(width, height);
}
@@ -493,13 +471,11 @@ public class RecentsView extends FrameLayout {
mBackgroundScrim.setBounds(left, top, right, bottom);
mMultiWindowBackgroundScrim.setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y);
- if (RecentsDebugFlags.Static.EnableStackActionButton) {
- // Layout the stack action button such that its drawable is start-aligned with the
- // stack, vertically centered in the available space above the stack
- Rect buttonBounds = getStackActionButtonBoundsFromStackLayout();
- mStackActionButton.layout(buttonBounds.left, buttonBounds.top, buttonBounds.right,
- buttonBounds.bottom);
- }
+ // Layout the stack action button such that its drawable is start-aligned with the
+ // stack, vertically centered in the available space above the stack
+ Rect buttonBounds = getStackActionButtonBoundsFromStackLayout();
+ mStackActionButton.layout(buttonBounds.left, buttonBounds.top, buttonBounds.right,
+ buttonBounds.bottom);
if (mAwaitingFirstLayout) {
mAwaitingFirstLayout = false;
@@ -541,7 +517,7 @@ public class RecentsView extends FrameLayout {
public void onDrawForeground(Canvas canvas) {
super.onDrawForeground(canvas);
- ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates();
+ ArrayList<DockState> visDockStates = mTouchHandler.getVisibleDockStates();
for (int i = visDockStates.size() - 1; i >= 0; i--) {
visDockStates.get(i).viewState.draw(canvas);
}
@@ -549,7 +525,7 @@ public class RecentsView extends FrameLayout {
@Override
protected boolean verifyDrawable(Drawable who) {
- ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates();
+ ArrayList<DockState> visDockStates = mTouchHandler.getVisibleDockStates();
for (int i = visDockStates.size() - 1; i >= 0; i--) {
Drawable d = visDockStates.get(i).viewState.dockAreaOverlay;
if (d == who) {
@@ -562,9 +538,8 @@ public class RecentsView extends FrameLayout {
/**** EventBus Events ****/
public final void onBusEvent(LaunchTaskEvent event) {
- mLastTaskLaunchedWasFreeform = event.task.isFreeformTask();
- mTransitionHelper.launchTaskFromRecents(getStack(), event.task, mTaskStackView,
- event.taskView, event.screenPinningRequested, event.targetTaskStack);
+ launchTaskFromRecents(getStack(), event.task, mTaskStackView, event.taskView,
+ event.screenPinningRequested, event.targetWindowingMode, event.targetActivityType);
if (Recents.getConfiguration().isLowRamDevice) {
EventBus.getDefault().send(new HideStackActionButtonEvent(false /* translate */));
}
@@ -572,10 +547,8 @@ public class RecentsView extends FrameLayout {
public final void onBusEvent(DismissRecentsToHomeAnimationStarted event) {
int taskViewExitToHomeDuration = TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION;
- if (RecentsDebugFlags.Static.EnableStackActionButton) {
- // Hide the stack action button
- EventBus.getDefault().send(new HideStackActionButtonEvent());
- }
+ // Hide the stack action button
+ EventBus.getDefault().send(new HideStackActionButtonEvent());
animateBackgroundScrim(0f, taskViewExitToHomeDuration);
if (Recents.getConfiguration().isLowRamDevice) {
@@ -585,8 +558,8 @@ public class RecentsView extends FrameLayout {
public final void onBusEvent(DragStartEvent event) {
updateVisibleDockRegions(Recents.getConfiguration().getDockStatesForCurrentOrientation(),
- true /* isDefaultDockState */, TaskStack.DockState.NONE.viewState.dockAreaAlpha,
- TaskStack.DockState.NONE.viewState.hintTextAlpha,
+ true /* isDefaultDockState */, DockState.NONE.viewState.dockAreaAlpha,
+ DockState.NONE.viewState.hintTextAlpha,
true /* animateAlpha */, false /* animateBounds */);
// Temporarily hide the stack action button without changing visibility
@@ -600,15 +573,15 @@ public class RecentsView extends FrameLayout {
}
public final void onBusEvent(DragDropTargetChangedEvent event) {
- if (event.dropTarget == null || !(event.dropTarget instanceof TaskStack.DockState)) {
+ if (event.dropTarget == null || !(event.dropTarget instanceof DockState)) {
updateVisibleDockRegions(
Recents.getConfiguration().getDockStatesForCurrentOrientation(),
- true /* isDefaultDockState */, TaskStack.DockState.NONE.viewState.dockAreaAlpha,
- TaskStack.DockState.NONE.viewState.hintTextAlpha,
+ true /* isDefaultDockState */, DockState.NONE.viewState.dockAreaAlpha,
+ DockState.NONE.viewState.hintTextAlpha,
true /* animateAlpha */, true /* animateBounds */);
} else {
- final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
- updateVisibleDockRegions(new TaskStack.DockState[] {dockState},
+ final DockState dockState = (DockState) event.dropTarget;
+ updateVisibleDockRegions(new DockState[] {dockState},
false /* isDefaultDockState */, -1, -1, true /* animateAlpha */,
true /* animateBounds */);
}
@@ -627,8 +600,8 @@ public class RecentsView extends FrameLayout {
public final void onBusEvent(final DragEndEvent event) {
// Handle the case where we drop onto a dock region
- if (event.dropTarget instanceof TaskStack.DockState) {
- final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
+ if (event.dropTarget instanceof DockState) {
+ final DockState dockState = (DockState) event.dropTarget;
// Hide the dock region
updateVisibleDockRegions(null, false /* isDefaultDockState */, -1, -1,
@@ -638,34 +611,27 @@ public class RecentsView extends FrameLayout {
// rect to its final layout-space rect
Utilities.setViewFrameFromTranslation(event.taskView);
- // Dock the task and launch it
- SystemServicesProxy ssp = Recents.getSystemServices();
- if (ssp.startTaskInDockedMode(event.task.key.id, dockState.createMode)) {
- final OnAnimationStartedListener startedListener =
- new OnAnimationStartedListener() {
+ final ActivityOptions options = ActivityOptionsCompat.makeSplitScreenOptions(
+ dockState.createMode == SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT);
+ if (ActivityManagerWrapper.getInstance().startActivityFromRecents(event.task.key.id,
+ options)) {
+ final Runnable animStartedListener = () -> {
+ EventBus.getDefault().send(new DockedFirstAnimationFrameEvent());
+ // Remove the task and don't bother relaying out, as all the tasks
+ // will be relaid out when the stack changes on the multiwindow
+ // change event
+ getStack().removeTask(event.task, null, true /* fromDockGesture */);
+ };
+ final Rect taskRect = getTaskRect(event.taskView);
+ AppTransitionAnimationSpecsFuture future = new AppTransitionAnimationSpecsFuture(
+ getHandler()) {
@Override
- public void onAnimationStarted() {
- EventBus.getDefault().send(new DockedFirstAnimationFrameEvent());
- // Remove the task and don't bother relaying out, as all the tasks will be
- // relaid out when the stack changes on the multiwindow change event
- getStack().removeTask(event.task, null, true /* fromDockGesture */);
+ public List<AppTransitionAnimationSpecCompat> composeSpecs() {
+ return mTransitionHelper.composeDockAnimationSpec(event.taskView, taskRect);
}
};
-
- final Rect taskRect = getTaskRect(event.taskView);
- AppTransitionAnimationSpecsFuture future =
- mTransitionHelper.getAppTransitionFuture(
- new AnimationSpecComposer() {
- @Override
- public List<AppTransitionAnimationSpec> composeSpecs() {
- return mTransitionHelper.composeDockAnimationSpec(
- event.taskView, taskRect);
- }
- });
- ssp.overridePendingAppTransitionMultiThumbFuture(future.getFuture(),
- mTransitionHelper.wrapStartedListener(startedListener),
- true /* scaleUp */);
-
+ WindowManagerWrapper.getInstance().overridePendingAppTransitionMultiThumbFuture(
+ future, animStartedListener, getHandler(), true /* scaleUp */);
MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_DRAG_DROP,
event.task.getTopComponent().flattenToShortString());
} else {
@@ -750,18 +716,10 @@ public class RecentsView extends FrameLayout {
}
public final void onBusEvent(ShowStackActionButtonEvent event) {
- if (!RecentsDebugFlags.Static.EnableStackActionButton) {
- return;
- }
-
showStackActionButton(SHOW_STACK_ACTION_BUTTON_DURATION, event.translate);
}
public final void onBusEvent(HideStackActionButtonEvent event) {
- if (!RecentsDebugFlags.Static.EnableStackActionButton) {
- return;
- }
-
hideStackActionButton(HIDE_STACK_ACTION_BUTTON_DURATION, true /* translate */);
}
@@ -777,10 +735,6 @@ public class RecentsView extends FrameLayout {
* Shows the stack action button.
*/
private void showStackActionButton(final int duration, final boolean translate) {
- if (!RecentsDebugFlags.Static.EnableStackActionButton) {
- return;
- }
-
final ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger();
if (mStackActionButton.getVisibility() == View.INVISIBLE) {
mStackActionButton.setVisibility(View.VISIBLE);
@@ -813,10 +767,6 @@ public class RecentsView extends FrameLayout {
* Hides the stack action button.
*/
private void hideStackActionButton(int duration, boolean translate) {
- if (!RecentsDebugFlags.Static.EnableStackActionButton) {
- return;
- }
-
final ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger();
hideStackActionButton(duration, translate, postAnimationTrigger);
postAnimationTrigger.flushLastDecrementRunnables();
@@ -827,10 +777,6 @@ public class RecentsView extends FrameLayout {
*/
private void hideStackActionButton(int duration, boolean translate,
final ReferenceCountedTrigger postAnimationTrigger) {
- if (!RecentsDebugFlags.Static.EnableStackActionButton) {
- return;
- }
-
if (mStackActionButton.getVisibility() == View.VISIBLE) {
if (translate) {
mStackActionButton.animate().translationY(mStackActionButton.getMeasuredHeight()
@@ -877,15 +823,15 @@ public class RecentsView extends FrameLayout {
/**
* Updates the dock region to match the specified dock state.
*/
- private void updateVisibleDockRegions(TaskStack.DockState[] newDockStates,
+ private void updateVisibleDockRegions(DockState[] newDockStates,
boolean isDefaultDockState, int overrideAreaAlpha, int overrideHintAlpha,
boolean animateAlpha, boolean animateBounds) {
- ArraySet<TaskStack.DockState> newDockStatesSet = Utilities.arrayToSet(newDockStates,
- new ArraySet<TaskStack.DockState>());
- ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates();
+ ArraySet<DockState> newDockStatesSet = Utilities.arrayToSet(newDockStates,
+ new ArraySet<DockState>());
+ ArrayList<DockState> visDockStates = mTouchHandler.getVisibleDockStates();
for (int i = visDockStates.size() - 1; i >= 0; i--) {
- TaskStack.DockState dockState = visDockStates.get(i);
- TaskStack.DockState.ViewState viewState = dockState.viewState;
+ DockState dockState = visDockStates.get(i);
+ DockState.ViewState viewState = dockState.viewState;
if (newDockStates == null || !newDockStatesSet.contains(dockState)) {
// This is no longer visible, so hide it
viewState.startAnimation(null, 0, 0, TaskStackView.SLOW_SYNC_STACK_DURATION,
@@ -947,7 +893,8 @@ public class RecentsView extends FrameLayout {
* @return the bounds of the stack action button.
*/
Rect getStackActionButtonBoundsFromStackLayout() {
- Rect actionButtonRect = new Rect(mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect());
+ Rect actionButtonRect = new Rect(
+ mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect());
int left, top;
if (Recents.getConfiguration().isLowRamDevice) {
Rect windowRect = Recents.getSystemServices().getWindowRect();
@@ -972,6 +919,139 @@ public class RecentsView extends FrameLayout {
return mStackActionButton;
}
+ /**
+ * Launches the specified {@link Task}.
+ */
+ public void launchTaskFromRecents(final TaskStack stack, @Nullable final Task task,
+ final TaskStackView stackView, final TaskView taskView,
+ final boolean screenPinningRequested, final int windowingMode, final int activityType) {
+
+ final Runnable animStartedListener;
+ final AppTransitionAnimationSpecsFuture transitionFuture;
+ if (taskView != null) {
+
+ // Fetch window rect here already in order not to be blocked on lock contention in WM
+ // when the future calls it.
+ final Rect windowRect = Recents.getSystemServices().getWindowRect();
+ transitionFuture = new AppTransitionAnimationSpecsFuture(stackView.getHandler()) {
+ @Override
+ public List<AppTransitionAnimationSpecCompat> composeSpecs() {
+ return mTransitionHelper.composeAnimationSpecs(task, stackView, windowingMode,
+ activityType, windowRect);
+ }
+ };
+ animStartedListener = new Runnable() {
+ private boolean mHandled;
+
+ @Override
+ public void run() {
+ if (mHandled) {
+ return;
+ }
+ mHandled = true;
+
+ // If we are launching into another task, cancel the previous task's
+ // window transition
+ EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
+ EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
+ stackView.cancelAllTaskViewAnimations();
+
+ if (screenPinningRequested) {
+ // Request screen pinning after the animation runs
+ mHandler.postDelayed(() -> {
+ EventBus.getDefault().send(new ScreenPinningRequestEvent(mContext,
+ task.key.id));
+ }, 350);
+ }
+
+ if (!Recents.getConfiguration().isLowRamDevice) {
+ // Reset the state where we are waiting for the transition to start
+ EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(false));
+ }
+ }
+ };
+ } else {
+ // This is only the case if the task is not on screen (scrolled offscreen for example)
+ transitionFuture = null;
+ animStartedListener = new Runnable() {
+ private boolean mHandled;
+
+ @Override
+ public void run() {
+ if (mHandled) {
+ return;
+ }
+ mHandled = true;
+
+ // If we are launching into another task, cancel the previous task's
+ // window transition
+ EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
+ EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
+ stackView.cancelAllTaskViewAnimations();
+
+ if (!Recents.getConfiguration().isLowRamDevice) {
+ // Reset the state where we are waiting for the transition to start
+ EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(false));
+ }
+ }
+ };
+ }
+
+ EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(true));
+ final ActivityOptions opts = RecentsTransition.createAspectScaleAnimation(mContext,
+ mHandler, true /* scaleUp */, transitionFuture != null ? transitionFuture : null,
+ animStartedListener);
+ if (taskView == null) {
+ // If there is no task view, then we do not need to worry about animating out occluding
+ // task views, and we can launch immediately
+ startTaskActivity(stack, task, taskView, opts, transitionFuture,
+ windowingMode, activityType);
+ } else {
+ LaunchTaskStartedEvent launchStartedEvent = new LaunchTaskStartedEvent(taskView,
+ screenPinningRequested);
+ EventBus.getDefault().send(launchStartedEvent);
+ startTaskActivity(stack, task, taskView, opts, transitionFuture, windowingMode,
+ activityType);
+ }
+ ActivityManagerWrapper.getInstance().closeSystemWindows(SYSTEM_DIALOG_REASON_RECENT_APPS);
+ }
+
+ /**
+ * Starts the activity for the launch task.
+ *
+ * @param taskView this is the {@link TaskView} that we are launching from. This can be null if
+ * we are toggling recents and the launch-to task is now offscreen.
+ */
+ private void startTaskActivity(TaskStack stack, Task task, @Nullable TaskView taskView,
+ ActivityOptions opts, AppTransitionAnimationSpecsFuture transitionFuture,
+ int windowingMode, int activityType) {
+ ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(task.key, opts,
+ windowingMode, activityType, succeeded -> {
+ if (succeeded) {
+ // Keep track of the index of the task launch
+ int taskIndexFromFront = 0;
+ int taskIndex = stack.indexOfTask(task);
+ if (taskIndex > -1) {
+ taskIndexFromFront = stack.getTaskCount() - taskIndex - 1;
+ }
+ EventBus.getDefault().send(new LaunchTaskSucceededEvent(taskIndexFromFront));
+ } else {
+ Log.e(TAG, mContext.getString(R.string.recents_launch_error_message, task.title));
+
+ // Dismiss the task if we fail to launch it
+ if (taskView != null) {
+ taskView.dismissTask();
+ }
+
+ // Keep track of failed launches
+ EventBus.getDefault().send(new LaunchTaskFailedEvent());
+ }
+ }, getHandler());
+ if (transitionFuture != null) {
+ mHandler.post(transitionFuture::composeSpecsSynchronous);
+ }
+ }
+
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
super.requestDisallowInterceptTouchEvent(disallowIntercept);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
index b6b24bcd8800..5c69ae3915b3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
@@ -17,7 +17,6 @@
package com.android.systemui.recents.views;
import android.app.ActivityManager;
-import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
import android.view.InputDevice;
@@ -32,7 +31,6 @@ import com.android.systemui.recents.Recents;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.ConfigurationChangedEvent;
import com.android.systemui.recents.events.activity.HideRecentsEvent;
-import com.android.systemui.recents.events.activity.HideStackActionButtonEvent;
import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent;
import com.android.systemui.recents.events.ui.HideIncompatibleAppOverlayEvent;
import com.android.systemui.recents.events.ui.ShowIncompatibleAppOverlayEvent;
@@ -41,8 +39,7 @@ import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragStartInitializeDropTargetsEvent;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.shared.recents.model.Task;
import java.util.ArrayList;
@@ -72,7 +69,7 @@ public class RecentsViewTouchHandler {
private DropTarget mLastDropTarget;
private DividerSnapAlgorithm mDividerSnapAlgorithm;
private ArrayList<DropTarget> mDropTargets = new ArrayList<>();
- private ArrayList<TaskStack.DockState> mVisibleDockStates = new ArrayList<>();
+ private ArrayList<DockState> mVisibleDockStates = new ArrayList<>();
public RecentsViewTouchHandler(RecentsView rv) {
mRv = rv;
@@ -96,7 +93,7 @@ public class RecentsViewTouchHandler {
/**
* Returns the set of visible dock states for this current drag.
*/
- public ArrayList<TaskStack.DockState> getVisibleDockStates() {
+ public ArrayList<DockState> getVisibleDockStates() {
return mVisibleDockStates;
}
@@ -108,7 +105,7 @@ public class RecentsViewTouchHandler {
/** Handles touch events once we have intercepted them */
public boolean onTouchEvent(MotionEvent ev) {
handleTouchEvent(ev);
- if (ev.getAction() == MotionEvent.ACTION_UP && mRv.getStack().getStackTaskCount() == 0) {
+ if (ev.getAction() == MotionEvent.ACTION_UP && mRv.getStack().getTaskCount() == 0) {
EventBus.getDefault().send(new HideRecentsEvent(false, true));
}
return true;
@@ -148,9 +145,9 @@ public class RecentsViewTouchHandler {
EventBus.getDefault().send(new ShowIncompatibleAppOverlayEvent());
} else {
// Add the dock state drop targets (these take priority)
- TaskStack.DockState[] dockStates = Recents.getConfiguration()
+ DockState[] dockStates = Recents.getConfiguration()
.getDockStatesForCurrentOrientation();
- for (TaskStack.DockState dockState : dockStates) {
+ for (DockState dockState : dockStates) {
registerDropTargetForCurrentDrag(dockState);
dockState.update(mRv.getContext());
mVisibleDockStates.add(dockState);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java b/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java
index 8f784b832e4c..170e39dfc360 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java
@@ -30,7 +30,7 @@ import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent;
import com.android.systemui.recents.events.activity.MultiWindowStateChangedEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragEndCancelledEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.shared.recents.utilities.AnimationProps;
/** Manages the scrims for the various system bars. */
public class SystemBarScrimViews {
@@ -154,19 +154,19 @@ public class SystemBarScrimViews {
public final void onBusEvent(MultiWindowStateChangedEvent event) {
mHasDockedTasks = event.inMultiWindow;
- animateScrimToCurrentNavBarState(event.stack.getStackTaskCount() > 0);
+ animateScrimToCurrentNavBarState(event.stack.getTaskCount() > 0);
}
public final void onBusEvent(final DragEndEvent event) {
// Hide the nav bar scrims once we drop to a dock region
- if (event.dropTarget instanceof TaskStack.DockState) {
+ if (event.dropTarget instanceof DockState) {
animateScrimToCurrentNavBarState(false /* hasStackTasks */);
}
}
public final void onBusEvent(final DragEndCancelledEvent event) {
// Restore the scrims to the normal state
- animateScrimToCurrentNavBarState(event.stack.getStackTaskCount() > 0);
+ animateScrimToCurrentNavBarState(event.stack.getTaskCount() > 0);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
index 81bf6affc94a..67d09787e9f3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
@@ -18,13 +18,11 @@ package com.android.systemui.recents.views;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.util.Log;
-import android.view.View;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
@@ -37,9 +35,10 @@ import com.android.systemui.recents.RecentsDebugFlags;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.component.SetWaitingForTransitionStartEvent;
import com.android.systemui.recents.misc.ReferenceCountedTrigger;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.TaskStack;
import com.android.systemui.recents.views.lowram.TaskStackLowRamLayoutAlgorithm;
+import com.android.systemui.shared.recents.utilities.AnimationProps;
import java.util.ArrayList;
import java.util.List;
@@ -161,20 +160,12 @@ public class TaskStackAnimationHelper {
for (int i = taskViews.size() - 1; i >= 0; i--) {
TaskView tv = taskViews.get(i);
Task task = tv.getTask();
- boolean currentTaskOccludesLaunchTarget = launchTargetTask != null &&
- launchTargetTask.group != null &&
- launchTargetTask.group.isTaskAboveTask(task, launchTargetTask);
- boolean hideTask = launchTargetTask != null &&
- launchTargetTask.isFreeformTask() &&
- task.isFreeformTask();
// Get the current transform for the task, which will be used to position it offscreen
stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform,
null);
- if (hideTask) {
- tv.setVisibility(View.INVISIBLE);
- } else if (launchState.launchedFromApp && !launchState.launchedViaDockGesture) {
+ if (launchState.launchedFromApp && !launchState.launchedViaDockGesture) {
if (task.isLaunchTarget) {
tv.onPrepareLaunchTargetForEnterAnimation();
} else if (isLowRamDevice && i >= taskViews.size() -
@@ -195,13 +186,6 @@ public class TaskStackAnimationHelper {
// com.android.server.wm.AppTransition#DEFAULT_APP_TRANSITION_DURATION}
mStackView.updateTaskViewToTransform(tv, mTmpTransform,
new AnimationProps(336, Interpolators.FAST_OUT_SLOW_IN));
- } else if (currentTaskOccludesLaunchTarget) {
- // Move the task view slightly lower so we can animate it in
- mTmpTransform.rect.offset(0, taskViewAffiliateGroupEnterOffset);
- mTmpTransform.alpha = 0f;
- mStackView.updateTaskViewToTransform(tv, mTmpTransform,
- AnimationProps.IMMEDIATE);
- tv.setClipViewInStack(false);
}
} else if (launchState.launchedFromHome) {
if (isLowRamDevice) {
@@ -266,9 +250,6 @@ public class TaskStackAnimationHelper {
int taskIndexFromBack = i;
final TaskView tv = taskViews.get(i);
Task task = tv.getTask();
- boolean currentTaskOccludesLaunchTarget = launchTargetTask != null &&
- launchTargetTask.group != null &&
- launchTargetTask.group.isTaskAboveTask(task, launchTargetTask);
// Get the current transform for the task, which will be updated to the final transform
// to animate to depending on how recents was invoked
@@ -280,21 +261,6 @@ public class TaskStackAnimationHelper {
tv.onStartLaunchTargetEnterAnimation(mTmpTransform,
taskViewEnterFromAppDuration, mStackView.mScreenPinningEnabled,
postAnimationTrigger);
- } else {
- // Animate the task up if it was occluding the launch target
- if (currentTaskOccludesLaunchTarget) {
- AnimationProps taskAnimation = new AnimationProps(
- taskViewEnterFromAffiliatedAppDuration, Interpolators.ALPHA_IN,
- new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- postAnimationTrigger.decrement();
- tv.setClipViewInStack(true);
- }
- });
- postAnimationTrigger.increment();
- mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation);
- }
}
} else if (launchState.launchedFromHome) {
@@ -423,9 +389,6 @@ public class TaskStackAnimationHelper {
for (int i = 0; i < taskViewCount; i++) {
TaskView tv = taskViews.get(i);
Task task = tv.getTask();
- boolean currentTaskOccludesLaunchTarget = launchingTask != null &&
- launchingTask.group != null &&
- launchingTask.group.isTaskAboveTask(task, launchingTask);
if (tv == launchingTaskView) {
tv.setClipViewInStack(false);
@@ -437,17 +400,6 @@ public class TaskStackAnimationHelper {
});
tv.onStartLaunchTargetLaunchAnimation(taskViewExitToAppDuration,
screenPinningRequested, postAnimationTrigger);
- } else if (currentTaskOccludesLaunchTarget) {
- // Animate this task out of view
- AnimationProps taskAnimation = new AnimationProps(
- taskViewExitToAppDuration, Interpolators.ALPHA_OUT,
- postAnimationTrigger.decrementOnAnimationEnd());
- postAnimationTrigger.increment();
-
- mTmpTransform.fillIn(tv);
- mTmpTransform.alpha = 0f;
- mTmpTransform.rect.offset(0, taskViewAffiliateGroupEnterOffset);
- mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation);
}
}
}
@@ -497,7 +449,7 @@ public class TaskStackAnimationHelper {
// Get the current set of task transforms
int taskViewCount = mStackView.getTaskViews().size();
- ArrayList<Task> stackTasks = stack.getStackTasks();
+ ArrayList<Task> stackTasks = stack.getTasks();
mStackView.getCurrentTaskTransforms(stackTasks, mTmpCurrentTaskTransforms);
// Pick up the newly visible views after the scroll
@@ -589,7 +541,7 @@ public class TaskStackAnimationHelper {
TaskStackViewScroller stackScroller = mStackView.getScroller();
// Get the current set of task transforms
- ArrayList<Task> stackTasks = newStack.getStackTasks();
+ ArrayList<Task> stackTasks = newStack.getTasks();
mStackView.getCurrentTaskTransforms(stackTasks, mTmpCurrentTaskTransforms);
// Update the stack
@@ -611,7 +563,7 @@ public class TaskStackAnimationHelper {
false /* ignoreTaskOverrides */, mTmpFinalTaskTransforms);
// Hide the front most task view until the scroll is complete
- Task frontMostTask = newStack.getStackFrontMostTask(false /* includeFreeform */);
+ Task frontMostTask = newStack.getFrontMostTask();
final TaskView frontMostTaskView = mStackView.getChildViewForTask(frontMostTask);
final TaskViewTransform frontMostTransform = mTmpFinalTaskTransforms.get(
stackTasks.indexOf(frontMostTask));
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
index eaa32eefe795..d9f79bb6d34e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -24,7 +24,6 @@ import android.graphics.Path;
import android.graphics.Rect;
import android.util.ArraySet;
import android.util.Log;
-import android.util.MutableFloat;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.ViewDebug;
@@ -36,9 +35,9 @@ import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.RecentsDebugFlags;
import com.android.systemui.recents.misc.FreePathInterpolator;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.TaskStack;
import com.android.systemui.recents.views.lowram.TaskStackLowRamLayoutAlgorithm;
import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm;
@@ -147,75 +146,6 @@ public class TaskStackLayoutAlgorithm {
}
/**
- * The various stack/freeform states.
- */
- public static class StackState {
-
- public static final StackState FREEFORM_ONLY = new StackState(1f, 255);
- public static final StackState STACK_ONLY = new StackState(0f, 0);
- public static final StackState SPLIT = new StackState(0.5f, 255);
-
- public final float freeformHeightPct;
- public final int freeformBackgroundAlpha;
-
- /**
- * @param freeformHeightPct the percentage of the stack height (not including paddings) to
- * allocate to the freeform workspace
- * @param freeformBackgroundAlpha the background alpha for the freeform workspace
- */
- private StackState(float freeformHeightPct, int freeformBackgroundAlpha) {
- this.freeformHeightPct = freeformHeightPct;
- this.freeformBackgroundAlpha = freeformBackgroundAlpha;
- }
-
- /**
- * Resolves the stack state for the layout given a task stack.
- */
- public static StackState getStackStateForStack(TaskStack stack) {
- SystemServicesProxy ssp = Recents.getSystemServices();
- boolean hasFreeformWorkspaces = ssp.hasFreeformWorkspaceSupport();
- int freeformCount = stack.getFreeformTaskCount();
- int stackCount = stack.getStackTaskCount();
- if (hasFreeformWorkspaces && stackCount > 0 && freeformCount > 0) {
- return SPLIT;
- } else if (hasFreeformWorkspaces && freeformCount > 0) {
- return FREEFORM_ONLY;
- } else {
- return STACK_ONLY;
- }
- }
-
- /**
- * Computes the freeform and stack rect for this state.
- *
- * @param freeformRectOut the freeform rect to be written out
- * @param stackRectOut the stack rect, we only write out the top of the stack
- * @param taskStackBounds the full rect that the freeform rect can take up
- */
- public void computeRects(Rect freeformRectOut, Rect stackRectOut,
- Rect taskStackBounds, int topMargin, int freeformGap, int stackBottomOffset) {
- // The freeform height is the visible height (not including system insets) - padding
- // above freeform and below stack - gap between the freeform and stack
- int availableHeight = taskStackBounds.height() - topMargin - stackBottomOffset;
- int ffPaddedHeight = (int) (availableHeight * freeformHeightPct);
- int ffHeight = Math.max(0, ffPaddedHeight - freeformGap);
- freeformRectOut.set(taskStackBounds.left,
- taskStackBounds.top + topMargin,
- taskStackBounds.right,
- taskStackBounds.top + topMargin + ffHeight);
- stackRectOut.set(taskStackBounds.left,
- taskStackBounds.top,
- taskStackBounds.right,
- taskStackBounds.bottom);
- if (ffPaddedHeight > 0) {
- stackRectOut.top += ffPaddedHeight;
- } else {
- stackRectOut.top += topMargin;
- }
- }
- }
-
- /**
* @return True if we should use the grid layout.
*/
boolean useGridLayout() {
@@ -234,15 +164,11 @@ public class TaskStackLayoutAlgorithm {
}
Context mContext;
- private StackState mState = StackState.SPLIT;
private TaskStackLayoutAlgorithmCallbacks mCb;
// The task bounds (untransformed) for layout. This rect is anchored at mTaskRoot.
@ViewDebug.ExportedProperty(category="recents")
public Rect mTaskRect = new Rect();
- // The freeform workspace bounds, inset by the top system insets and is a fixed height
- @ViewDebug.ExportedProperty(category="recents")
- public Rect mFreeformRect = new Rect();
// The stack bounds, inset from the top system insets, and runs to the bottom of the screen
@ViewDebug.ExportedProperty(category="recents")
public Rect mStackRect = new Rect();
@@ -268,10 +194,6 @@ public class TaskStackLayoutAlgorithm {
private int mBaseBottomMargin;
private int mMinMargin;
- // The gap between the freeform and stack layouts
- @ViewDebug.ExportedProperty(category="recents")
- private int mFreeformStackGap;
-
// The initial offset that the focused task is from the top
@ViewDebug.ExportedProperty(category="recents")
private int mInitialTopOffset;
@@ -331,8 +253,6 @@ public class TaskStackLayoutAlgorithm {
// The last computed task counts
@ViewDebug.ExportedProperty(category="recents")
int mNumStackTasks;
- @ViewDebug.ExportedProperty(category="recents")
- int mNumFreeformTasks;
// The min/max z translations
@ViewDebug.ExportedProperty(category="recents")
@@ -344,8 +264,6 @@ public class TaskStackLayoutAlgorithm {
private SparseIntArray mTaskIndexMap = new SparseIntArray();
private SparseArray<Float> mTaskIndexOverrideMap = new SparseArray<>();
- // The freeform workspace layout
- FreeformWorkspaceLayoutAlgorithm mFreeformLayoutAlgorithm;
TaskGridLayoutAlgorithm mTaskGridLayoutAlgorithm;
TaskStackLowRamLayoutAlgorithm mTaskStackLowRamLayoutAlgorithm;
@@ -356,7 +274,6 @@ public class TaskStackLayoutAlgorithm {
public TaskStackLayoutAlgorithm(Context context, TaskStackLayoutAlgorithmCallbacks cb) {
mContext = context;
mCb = cb;
- mFreeformLayoutAlgorithm = new FreeformWorkspaceLayoutAlgorithm(context);
mTaskGridLayoutAlgorithm = new TaskGridLayoutAlgorithm(context);
mTaskStackLowRamLayoutAlgorithm = new TaskStackLowRamLayoutAlgorithm(context);
reloadOnConfigurationChange(context);
@@ -393,7 +310,6 @@ public class TaskStackLayoutAlgorithm {
R.dimen.recents_layout_initial_bottom_offset_tablet,
R.dimen.recents_layout_initial_bottom_offset_tablet,
R.dimen.recents_layout_initial_bottom_offset_tablet);
- mFreeformLayoutAlgorithm.reloadOnConfigurationChange(context);
mTaskGridLayoutAlgorithm.reloadOnConfigurationChange(context);
mTaskStackLowRamLayoutAlgorithm.reloadOnConfigurationChange(context);
mMinMargin = res.getDimensionPixelSize(R.dimen.recents_layout_min_margin);
@@ -408,8 +324,6 @@ public class TaskStackLayoutAlgorithm {
R.dimen.recents_layout_side_margin_tablet_xlarge,
R.dimen.recents_layout_side_margin_tablet);
mBaseBottomMargin = res.getDimensionPixelSize(R.dimen.recents_layout_bottom_margin);
- mFreeformStackGap =
- res.getDimensionPixelSize(R.dimen.recents_freeform_layout_bottom_margin);
mTitleBarHeight = getDimensionForDevice(mContext,
R.dimen.recents_task_view_header_height,
R.dimen.recents_task_view_header_height,
@@ -462,8 +376,7 @@ public class TaskStackLayoutAlgorithm {
* Computes the stack and task rects. The given task stack bounds already has the top/right
* insets and left/right padding already applied.
*/
- public void initialize(Rect displayRect, Rect windowRect, Rect taskStackBounds,
- StackState state) {
+ public void initialize(Rect displayRect, Rect windowRect, Rect taskStackBounds) {
Rect lastStackRect = new Rect(mStackRect);
int topMargin = getScaleForExtent(windowRect, displayRect, mBaseTopMargin, mMinMargin, HEIGHT);
@@ -474,10 +387,9 @@ public class TaskStackLayoutAlgorithm {
mInitialBottomOffset = mBaseInitialBottomOffset;
// Compute the stack bounds
- mState = state;
mStackBottomOffset = mSystemInsets.bottom + bottomMargin;
- state.computeRects(mFreeformRect, mStackRect, taskStackBounds, topMargin,
- mFreeformStackGap, mStackBottomOffset);
+ mStackRect.set(taskStackBounds);
+ mStackRect.top += topMargin;
// The stack action button will take the full un-padded header space above the stack
mStackActionButtonRect.set(mStackRect.left, mStackRect.top - topMargin,
@@ -519,37 +431,31 @@ public class TaskStackLayoutAlgorithm {
* in the stack.
*/
public void update(TaskStack stack, ArraySet<Task.TaskKey> ignoreTasksSet,
- RecentsActivityLaunchState launchState) {
+ RecentsActivityLaunchState launchState, float lastScrollPPercent) {
SystemServicesProxy ssp = Recents.getSystemServices();
// Clear the progress map
mTaskIndexMap.clear();
// Return early if we have no tasks
- ArrayList<Task> tasks = stack.getStackTasks();
+ ArrayList<Task> tasks = stack.getTasks();
if (tasks.isEmpty()) {
mFrontMostTaskP = 0;
mMinScrollP = mMaxScrollP = mInitialScrollP = 0;
- mNumStackTasks = mNumFreeformTasks = 0;
+ mNumStackTasks = 0;
return;
}
- // Filter the set of freeform and stack tasks
- ArrayList<Task> freeformTasks = new ArrayList<>();
+ // Filter the set of stack tasks
ArrayList<Task> stackTasks = new ArrayList<>();
for (int i = 0; i < tasks.size(); i++) {
Task task = tasks.get(i);
if (ignoreTasksSet.contains(task.key)) {
continue;
}
- if (task.isFreeformTask()) {
- freeformTasks.add(task);
- } else {
- stackTasks.add(task);
- }
+ stackTasks.add(task);
}
mNumStackTasks = stackTasks.size();
- mNumFreeformTasks = freeformTasks.size();
// Put each of the tasks in the progress map at a fixed index (does not need to actually
// map to a scroll position, just by index)
@@ -559,15 +465,10 @@ public class TaskStackLayoutAlgorithm {
mTaskIndexMap.put(task.key.id, i);
}
- // Update the freeform tasks
- if (!freeformTasks.isEmpty()) {
- mFreeformLayoutAlgorithm.update(freeformTasks, this);
- }
-
// Calculate the min/max/initial scroll
Task launchTask = stack.getLaunchTarget();
int launchTaskIndex = launchTask != null
- ? stack.indexOfStackTask(launchTask)
+ ? stack.indexOfTask(launchTask)
: mNumStackTasks - 1;
if (getInitialFocusState() == STATE_FOCUSED) {
int maxBottomOffset = mStackBottomOffset + mTaskRect.height();
@@ -582,7 +483,7 @@ public class TaskStackLayoutAlgorithm {
} else {
mInitialScrollP = Utilities.clamp(launchTaskIndex - 1, mMinScrollP, mMaxScrollP);
}
- } else if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1) {
+ } else if (mNumStackTasks == 1) {
// If there is one stack task, ignore the min/max/initial scroll positions
mMinScrollP = 0;
mMaxScrollP = 0;
@@ -603,10 +504,10 @@ public class TaskStackLayoutAlgorithm {
boolean scrollToFront = launchState.launchedFromHome || launchState.launchedFromPipApp
|| launchState.launchedWithNextPipApp || launchState.launchedViaDockGesture;
- if (launchState.launchedFromBlacklistedApp) {
- mInitialScrollP = mMaxScrollP;
- } else if (launchState.launchedWithAltTab) {
+ if (launchState.launchedWithAltTab) {
mInitialScrollP = Utilities.clamp(launchTaskIndex, mMinScrollP, mMaxScrollP);
+ } else if (0 <= lastScrollPPercent && lastScrollPPercent <= 1) {
+ mInitialScrollP = Utilities.mapRange(lastScrollPPercent, mMinScrollP, mMaxScrollP);
} else if (Recents.getConfiguration().isLowRamDevice) {
mInitialScrollP = mTaskStackLowRamLayoutAlgorithm.getInitialScrollP(mNumStackTasks,
scrollToFront);
@@ -633,7 +534,6 @@ public class TaskStackLayoutAlgorithm {
boolean scrollToFront = launchState.launchedFromHome ||
launchState.launchedFromPipApp ||
launchState.launchedWithNextPipApp ||
- launchState.launchedFromBlacklistedApp ||
launchState.launchedViaDockGesture;
if (getInitialFocusState() == STATE_UNFOCUSED && mNumStackTasks > 1) {
if (ignoreScrollToFront || (!launchState.launchedWithAltTab && !scrollToFront)) {
@@ -659,7 +559,7 @@ public class TaskStackLayoutAlgorithm {
}
mUnfocusedRange.offset(0f);
- List<Task> tasks = stack.getStackTasks();
+ List<Task> tasks = stack.getTasks();
int taskCount = tasks.size();
for (int i = taskCount - 1; i >= 0; i--) {
int indexFromFront = taskCount - i - 1;
@@ -767,7 +667,7 @@ public class TaskStackLayoutAlgorithm {
public int getInitialFocusState() {
RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
RecentsDebugFlags debugFlags = Recents.getDebugFlags();
- if (debugFlags.isPagingEnabled() || launchState.launchedWithAltTab) {
+ if (launchState.launchedWithAltTab) {
return STATE_FOCUSED;
} else {
return STATE_UNFOCUSED;
@@ -794,13 +694,6 @@ public class TaskStackLayoutAlgorithm {
}
/**
- * Returns the current stack state.
- */
- public StackState getStackState() {
- return mState;
- }
-
- /**
* Returns whether this stack layout has been initialized.
*/
public boolean isInitialized() {
@@ -825,62 +718,44 @@ public class TaskStackLayoutAlgorithm {
return new VisibilityReport(1, 1);
}
- // Quick return when there are no stack tasks
- if (mNumStackTasks == 0) {
- return new VisibilityReport(mNumFreeformTasks > 0 ? Math.max(mNumFreeformTasks, 1) : 0,
- mNumFreeformTasks > 0 ? Math.max(mNumFreeformTasks, 1) : 0);
- }
-
// Otherwise, walk backwards in the stack and count the number of tasks and visible
- // thumbnails and add that to the total freeform task count
+ // thumbnails and add that to the total task count
TaskViewTransform tmpTransform = new TaskViewTransform();
Range currentRange = getInitialFocusState() > 0f ? mFocusedRange : mUnfocusedRange;
currentRange.offset(mInitialScrollP);
int taskBarHeight = mContext.getResources().getDimensionPixelSize(
R.dimen.recents_task_view_header_height);
- int numVisibleTasks = mNumFreeformTasks > 0 ? Math.max(mNumFreeformTasks, 1) : 0;
- int numVisibleThumbnails = mNumFreeformTasks > 0 ? Math.max(mNumFreeformTasks, 0) : 0;
+ int numVisibleTasks = 0;
+ int numVisibleThumbnails = 0;
float prevScreenY = Integer.MAX_VALUE;
for (int i = tasks.size() - 1; i >= 0; i--) {
Task task = tasks.get(i);
- // Skip freeform
- if (task.isFreeformTask()) {
- continue;
- }
-
// Skip invisible
float taskProgress = getStackScrollForTask(task);
if (!currentRange.isInRange(taskProgress)) {
continue;
}
- boolean isFrontMostTaskInGroup = task.group == null || task.group.isFrontMostTask(task);
- if (isFrontMostTaskInGroup) {
- getStackTransform(taskProgress, taskProgress, mInitialScrollP, mFocusState,
- tmpTransform, null, false /* ignoreSingleTaskCase */,
- false /* forceUpdate */);
- float screenY = tmpTransform.rect.top;
- boolean hasVisibleThumbnail = (prevScreenY - screenY) > taskBarHeight;
- if (hasVisibleThumbnail) {
- numVisibleThumbnails++;
- numVisibleTasks++;
- prevScreenY = screenY;
- } else {
- // Once we hit the next front most task that does not have a visible thumbnail,
- // walk through remaining visible set
- for (int j = i; j >= 0; j--) {
- taskProgress = getStackScrollForTask(tasks.get(j));
- if (!currentRange.isInRange(taskProgress)) {
- break;
- }
- numVisibleTasks++;
+ getStackTransform(taskProgress, taskProgress, mInitialScrollP, mFocusState,
+ tmpTransform, null, false /* ignoreSingleTaskCase */, false /* forceUpdate */);
+ float screenY = tmpTransform.rect.top;
+ boolean hasVisibleThumbnail = (prevScreenY - screenY) > taskBarHeight;
+ if (hasVisibleThumbnail) {
+ numVisibleThumbnails++;
+ numVisibleTasks++;
+ prevScreenY = screenY;
+ } else {
+ // Once we hit the next front most task that does not have a visible thumbnail,
+ // walk through remaining visible set
+ for (int j = i; j >= 0; j--) {
+ taskProgress = getStackScrollForTask(tasks.get(j));
+ if (!currentRange.isInRange(taskProgress)) {
+ break;
}
- break;
+ numVisibleTasks++;
}
- } else {
- // Affiliated task, no thumbnail
- numVisibleTasks++;
+ break;
}
}
return new VisibilityReport(numVisibleTasks, numVisibleThumbnails);
@@ -906,10 +781,7 @@ public class TaskStackLayoutAlgorithm {
public TaskViewTransform getStackTransform(Task task, float stackScroll, int focusState,
TaskViewTransform transformOut, TaskViewTransform frontTransform, boolean forceUpdate,
boolean ignoreTaskOverrides) {
- if (mFreeformLayoutAlgorithm.isTransformAvailable(task, this)) {
- mFreeformLayoutAlgorithm.getTransform(task, transformOut, this);
- return transformOut;
- } else if (useGridLayout()) {
+ if (useGridLayout()) {
int taskIndex = mTaskIndexMap.get(task.key.id);
int taskCount = mTaskIndexMap.size();
mTaskGridLayoutAlgorithm.getTransform(taskIndex, taskCount, transformOut, this);
@@ -1024,7 +896,7 @@ public class TaskStackLayoutAlgorithm {
float z;
float dimAlpha;
float viewOutlineAlpha;
- if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1 && !ignoreSingleTaskCase) {
+ if (mNumStackTasks == 1 && !ignoreSingleTaskCase) {
// When there is exactly one task, then decouple the task from the stack and just move
// in screen space
float tmpP = (mMinScrollP - stackScroll) / mNumStackTasks;
@@ -1378,7 +1250,6 @@ public class TaskStackLayoutAlgorithm {
writer.print("insets="); writer.print(Utilities.dumpRect(mSystemInsets));
writer.print(" stack="); writer.print(Utilities.dumpRect(mStackRect));
writer.print(" task="); writer.print(Utilities.dumpRect(mTaskRect));
- writer.print(" freeform="); writer.print(Utilities.dumpRect(mFreeformRect));
writer.print(" actionButton="); writer.print(Utilities.dumpRect(mStackActionButtonRect));
writer.println();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index d32b2208697a..3cc3273c0db4 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -16,23 +16,15 @@
package com.android.systemui.recents.views;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.IntDef;
import android.content.ComponentName;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.graphics.Canvas;
import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.GradientDrawable;
import android.os.Bundle;
import android.provider.Settings;
import android.util.ArrayMap;
@@ -65,7 +57,6 @@ import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimatio
import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
import com.android.systemui.recents.events.activity.HideRecentsEvent;
import com.android.systemui.recents.events.activity.HideStackActionButtonEvent;
-import com.android.systemui.recents.events.activity.IterateRecentsEvent;
import com.android.systemui.recents.events.activity.LaunchMostRecentTaskRequestEvent;
import com.android.systemui.recents.events.activity.LaunchNextTaskRequestEvent;
import com.android.systemui.recents.events.activity.LaunchTaskEvent;
@@ -84,13 +75,11 @@ import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent;
import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
import com.android.systemui.recents.events.ui.RecentsGrowingEvent;
import com.android.systemui.recents.events.ui.TaskViewDismissedEvent;
-import com.android.systemui.recents.events.ui.UpdateFreeformTaskViewVisibilityEvent;
import com.android.systemui.recents.events.ui.UserInteractionEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragEndCancelledEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
-import com.android.systemui.recents.events.ui.dragndrop.DragStartInitializeDropTargetsEvent;
import com.android.systemui.recents.events.ui.focus.DismissFocusedTaskViewEvent;
import com.android.systemui.recents.events.ui.focus.FocusNextTaskViewEvent;
import com.android.systemui.recents.events.ui.focus.FocusPreviousTaskViewEvent;
@@ -98,13 +87,15 @@ import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent;
import com.android.systemui.recents.misc.DozeTrigger;
import com.android.systemui.recents.misc.ReferenceCountedTrigger;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.shared.recents.utilities.AnimationProps;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.TaskStack;
import com.android.systemui.recents.views.grid.GridTaskView;
import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm;
import com.android.systemui.recents.views.grid.TaskViewFocusFrame;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -154,8 +145,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
@ViewDebug.ExportedProperty(deepExport=true, prefix="touch_")
private TaskStackViewTouchHandler mTouchHandler;
private TaskStackAnimationHelper mAnimationHelper;
- private GradientDrawable mFreeformWorkspaceBackground;
- private ObjectAnimator mFreeformWorkspaceBackgroundAnimator;
private ViewPool<TaskView, Task> mViewPool;
private ArrayList<TaskView> mTaskViews = new ArrayList<>();
@@ -221,6 +210,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
private int mLastHeight;
private boolean mStackActionButtonVisible;
+ // Percentage of last ScrollP from the min to max scrollP that lives after configuration changes
+ private float mLastScrollPPercent = -1;
+
// We keep track of the task view focused by user interaction and draw a frame around it in the
// grid layout.
private TaskViewFocusFrame mTaskViewFocusFrame;
@@ -240,20 +232,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
};
- // The drop targets for a task drag
- private DropTarget mFreeformWorkspaceDropTarget = new DropTarget() {
- @Override
- public boolean acceptsDrop(int x, int y, int width, int height, Rect insets,
- boolean isCurrentTarget) {
- // This drop target has a fixed bounds and should be checked last, so just fall through
- // if it is the current target
- if (!isCurrentTarget) {
- return mLayoutAlgorithm.mFreeformRect.contains(x, y);
- }
- return false;
- }
- };
-
private DropTarget mStackDropTarget = new DropTarget() {
@Override
public boolean acceptsDrop(int x, int y, int width, int height, Rect insets,
@@ -313,17 +291,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
});
setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
- if (ssp.hasFreeformWorkspaceSupport()) {
- setWillNotDraw(false);
- }
-
- mFreeformWorkspaceBackground = (GradientDrawable) getContext().getDrawable(
- R.drawable.recents_freeform_workspace_bg);
- mFreeformWorkspaceBackground.setCallback(this);
- if (ssp.hasFreeformWorkspaceSupport()) {
- mFreeformWorkspaceBackground.setColor(
- getContext().getColor(R.color.recents_freeform_workspace_bg_color));
- }
}
@Override
@@ -360,15 +327,11 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
readSystemFlags();
mTaskViewsClipDirty = true;
mUIDozeTrigger.stopDozing();
- if (isResumingFromVisible) {
- // Animate in the freeform workspace
- int ffBgAlpha = mLayoutAlgorithm.getStackState().freeformBackgroundAlpha;
- animateFreeformWorkspaceBackgroundAlpha(ffBgAlpha, new AnimationProps(150,
- Interpolators.FAST_OUT_SLOW_IN));
- } else {
+ if (!isResumingFromVisible) {
mStackScroller.reset();
mStableLayoutAlgorithm.reset();
mLayoutAlgorithm.reset();
+ mLastScrollPPercent = -1;
}
// Since we always animate to the same place in (the initial state), always reset the stack
@@ -388,7 +351,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// Only notify if we are already initialized, otherwise, everything will pick up all the
// new and old tasks when we next layout
- mStack.setTasks(getContext(), stack, allowNotifyStackChanges && isInitialized);
+ mStack.setTasks(stack, allowNotifyStackChanges && isInitialized);
}
/** Returns the task stack. */
@@ -423,23 +386,13 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
/**
* Returns the front most task view.
- *
- * @param stackTasksOnly if set, will return the front most task view in the stack (by default
- * the front most task view will be freeform since they are placed above
- * stack tasks)
*/
- private TaskView getFrontMostTaskView(boolean stackTasksOnly) {
+ private TaskView getFrontMostTaskView() {
List<TaskView> taskViews = getTaskViews();
- int taskViewCount = taskViews.size();
- for (int i = taskViewCount - 1; i >= 0; i--) {
- TaskView tv = taskViews.get(i);
- Task task = tv.getTask();
- if (stackTasksOnly && task.isFreeformTask()) {
- continue;
- }
- return tv;
+ if (taskViews.isEmpty()) {
+ return null;
}
- return null;
+ return taskViews.get(taskViews.size() - 1);
}
/**
@@ -501,8 +454,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
* visible range includes all tasks at the target stack scroll. This is useful for ensure that
* all views necessary for a transition or animation will be visible at the start.
*
- * This call ignores freeform tasks.
- *
* @param taskTransforms The set of task view transforms to reuse, this list will be sized to
* match the size of {@param tasks}
* @param tasks The set of tasks for which to generate transforms
@@ -525,7 +476,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
boolean useTargetStackScroll = Float.compare(curStackScroll, targetStackScroll) != 0;
// We can reuse the task transforms where possible to reduce object allocation
- Utilities.matchTaskListSize(tasks, taskTransforms);
+ matchTaskListSize(tasks, taskTransforms);
// Update the stack transforms
TaskViewTransform frontTransform = null;
@@ -555,12 +506,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
continue;
}
- // For freeform tasks, only calculate the stack transform and skip the calculation of
- // the visible stack indices
- if (task.isFreeformTask()) {
- continue;
- }
-
frontTransform = transform;
frontTransformAtTarget = transformAtTarget;
if (transform.visible) {
@@ -595,7 +540,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
*/
void bindVisibleTaskViews(float targetStackScroll, boolean ignoreTaskOverrides) {
// Get all the task transforms
- ArrayList<Task> tasks = mStack.getStackTasks();
+ ArrayList<Task> tasks = mStack.getTasks();
int[] visibleTaskRange = computeVisibleTaskTransforms(mCurrentTaskTransforms, tasks,
mStackScroller.getStackScroll(), targetStackScroll, mIgnoreTasks,
ignoreTaskOverrides);
@@ -617,13 +562,13 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// It is possible for the set of lingering TaskViews to differ from the stack if the
// stack was updated before the relayout. If the task view is no longer in the stack,
// then just return it back to the view pool.
- int taskIndex = mStack.indexOfStackTask(task);
+ int taskIndex = mStack.indexOfTask(task);
TaskViewTransform transform = null;
if (taskIndex != -1) {
transform = mCurrentTaskTransforms.get(taskIndex);
}
- if (task.isFreeformTask() || (transform != null && transform.visible)) {
+ if (transform != null && transform.visible) {
mTmpTaskViewMap.put(task.key, tv);
} else {
if (mTouchExplorationEnabled && Utilities.isDescendentAccessibilityFocused(tv)) {
@@ -644,28 +589,24 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
continue;
}
- // Skip the invisible non-freeform stack tasks
- if (!task.isFreeformTask() && !transform.visible) {
+ // Skip the invisible stack tasks
+ if (!transform.visible) {
continue;
}
TaskView tv = mTmpTaskViewMap.get(task.key);
if (tv == null) {
tv = mViewPool.pickUpViewFromPool(task, task);
- if (task.isFreeformTask()) {
- updateTaskViewToTransform(tv, transform, AnimationProps.IMMEDIATE);
+ if (transform.rect.top <= mLayoutAlgorithm.mStackRect.top) {
+ updateTaskViewToTransform(tv, mLayoutAlgorithm.getBackOfStackTransform(),
+ AnimationProps.IMMEDIATE);
} else {
- if (transform.rect.top <= mLayoutAlgorithm.mStackRect.top) {
- updateTaskViewToTransform(tv, mLayoutAlgorithm.getBackOfStackTransform(),
- AnimationProps.IMMEDIATE);
- } else {
- updateTaskViewToTransform(tv, mLayoutAlgorithm.getFrontOfStackTransform(),
- AnimationProps.IMMEDIATE);
- }
+ updateTaskViewToTransform(tv, mLayoutAlgorithm.getFrontOfStackTransform(),
+ AnimationProps.IMMEDIATE);
}
} else {
// Reattach it in the right z order
- final int taskIndex = mStack.indexOfStackTask(task);
+ final int taskIndex = mStack.indexOfTask(task);
final int insertIndex = findTaskViewInsertIndex(task, taskIndex);
if (insertIndex != getTaskViews().indexOf(tv)){
detachViewFromParent(tv);
@@ -707,14 +648,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
* an animation provided in {@param animationOverrides}, that will be used instead.
*/
private void relayoutTaskViews(AnimationProps animation,
- ArrayMap<Task, AnimationProps> animationOverrides,
- boolean ignoreTaskOverrides) {
+ ArrayMap<Task, AnimationProps> animationOverrides, boolean ignoreTaskOverrides) {
// If we had a deferred animation, cancel that
cancelDeferredTaskViewLayoutAnimation();
// Synchronize the current set of TaskViews
- bindVisibleTaskViews(mStackScroller.getStackScroll(),
- ignoreTaskOverrides /* ignoreTaskOverrides */);
+ bindVisibleTaskViews(mStackScroller.getStackScroll(), ignoreTaskOverrides);
// Animate them to their final transforms with the given animation
List<TaskView> taskViews = getTaskViews();
@@ -727,7 +666,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
continue;
}
- int taskIndex = mStack.indexOfStackTask(task);
+ int taskIndex = mStack.indexOfTask(task);
TaskViewTransform transform = mCurrentTaskTransforms.get(taskIndex);
if (animationOverrides != null && animationOverrides.containsKey(task)) {
animation = animationOverrides.get(task);
@@ -765,7 +704,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
*/
public void getCurrentTaskTransforms(ArrayList<Task> tasks,
ArrayList<TaskViewTransform> transformsOut) {
- Utilities.matchTaskListSize(tasks, transformsOut);
+ matchTaskListSize(tasks, transformsOut);
int focusState = mLayoutAlgorithm.getFocusState();
for (int i = tasks.size() - 1; i >= 0; i--) {
Task task = tasks.get(i);
@@ -788,7 +727,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
*/
public void getLayoutTaskTransforms(float stackScroll, int focusState, ArrayList<Task> tasks,
boolean ignoreTaskOverrides, ArrayList<TaskViewTransform> transformsOut) {
- Utilities.matchTaskListSize(tasks, transformsOut);
+ matchTaskListSize(tasks, transformsOut);
for (int i = tasks.size() - 1; i >= 0; i--) {
Task task = tasks.get(i);
TaskViewTransform transform = transformsOut.get(i);
@@ -886,14 +825,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
public void updateLayoutAlgorithm(boolean boundScrollToNewMinMax,
RecentsActivityLaunchState launchState) {
// Compute the min and max scroll values
- mLayoutAlgorithm.update(mStack, mIgnoreTasks, launchState);
-
- // Update the freeform workspace background
- SystemServicesProxy ssp = Recents.getSystemServices();
- if (ssp.hasFreeformWorkspaceSupport()) {
- mTmpRect.set(mLayoutAlgorithm.mFreeformRect);
- mFreeformWorkspaceBackground.setBounds(mTmpRect);
- }
+ mLayoutAlgorithm.update(mStack, mIgnoreTasks, launchState, mLastScrollPPercent);
if (boundScrollToNewMinMax) {
mStackScroller.boundScroll();
@@ -907,8 +839,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
mWindowRect.set(mStableWindowRect);
mStackBounds.set(mStableStackBounds);
mLayoutAlgorithm.setSystemInsets(mStableLayoutAlgorithm.mSystemInsets);
- mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds,
- TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
+ mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds);
updateLayoutAlgorithm(true /* boundScroll */);
}
@@ -938,7 +869,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
int newFocusedTaskIndex = mStack.getTaskCount() > 0 ?
Utilities.clamp(focusTaskIndex, 0, mStack.getTaskCount() - 1) : -1;
final Task newFocusedTask = (newFocusedTaskIndex != -1) ?
- mStack.getStackTasks().get(newFocusedTaskIndex) : null;
+ mStack.getTasks().get(newFocusedTaskIndex) : null;
// Reset the last focused task state if changed
if (mFocusedTask != null) {
@@ -1025,25 +956,14 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
public void setRelativeFocusedTask(boolean forward, boolean stackTasksOnly, boolean animated,
boolean cancelWindowAnimations, int timerIndicatorDuration) {
Task focusedTask = getFocusedTask();
- int newIndex = mStack.indexOfStackTask(focusedTask);
+ int newIndex = mStack.indexOfTask(focusedTask);
if (focusedTask != null) {
if (stackTasksOnly) {
- List<Task> tasks = mStack.getStackTasks();
- if (focusedTask.isFreeformTask()) {
- // Try and focus the front most stack task
- TaskView tv = getFrontMostTaskView(stackTasksOnly);
- if (tv != null) {
- newIndex = mStack.indexOfStackTask(tv.getTask());
- }
- } else {
- // Try the next task if it is a stack task
- int tmpNewIndex = newIndex + (forward ? -1 : 1);
- if (0 <= tmpNewIndex && tmpNewIndex < tasks.size()) {
- Task t = tasks.get(tmpNewIndex);
- if (!t.isFreeformTask()) {
- newIndex = tmpNewIndex;
- }
- }
+ List<Task> tasks = mStack.getTasks();
+ // Try the next task if it is a stack task
+ int tmpNewIndex = newIndex + (forward ? -1 : 1);
+ if (0 <= tmpNewIndex && tmpNewIndex < tasks.size()) {
+ newIndex = tmpNewIndex;
}
} else {
// No restrictions, lets just move to the new task (looping forward/backwards if
@@ -1054,7 +974,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
} else {
// We don't have a focused task
float stackScroll = mStackScroller.getStackScroll();
- ArrayList<Task> tasks = mStack.getStackTasks();
+ ArrayList<Task> tasks = mStack.getTasks();
int taskCount = tasks.size();
if (useGridLayout()) {
// For the grid layout, we directly set focus to the most recently used task
@@ -1128,7 +1048,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
return tv.getTask();
}
}
- TaskView frontTv = getFrontMostTaskView(true /* stackTasksOnly */);
+ TaskView frontTv = getFrontMostTaskView();
if (frontTv != null) {
return frontTv.getTask();
}
@@ -1143,8 +1063,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
if (taskViewCount > 0) {
TaskView backMostTask = taskViews.get(0);
TaskView frontMostTask = taskViews.get(taskViewCount - 1);
- event.setFromIndex(mStack.indexOfStackTask(backMostTask.getTask()));
- event.setToIndex(mStack.indexOfStackTask(frontMostTask.getTask()));
+ event.setFromIndex(mStack.indexOfTask(backMostTask.getTask()));
+ event.setToIndex(mStack.indexOfTask(frontMostTask.getTask()));
event.setContentDescription(frontMostTask.getTask().title);
}
event.setItemCount(mStack.getTaskCount());
@@ -1163,7 +1083,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// Find the accessibility focused task
Task focusedTask = getAccessibilityFocusedTask();
info.setScrollable(true);
- int focusedTaskIndex = mStack.indexOfStackTask(focusedTask);
+ int focusedTaskIndex = mStack.indexOfTask(focusedTask);
if (focusedTaskIndex > 0 || !mStackActionButtonVisible) {
info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
}
@@ -1184,7 +1104,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
return true;
}
Task focusedTask = getAccessibilityFocusedTask();
- int taskIndex = mStack.indexOfStackTask(focusedTask);
+ int taskIndex = mStack.indexOfTask(focusedTask);
if (0 <= taskIndex && taskIndex < mStack.getTaskCount()) {
switch (action) {
case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
@@ -1233,6 +1153,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
if (mTaskViewsClipDirty) {
clipTaskViews();
}
+ mLastScrollPPercent = Utilities.clamp(Utilities.unmapRange(mStackScroller.getStackScroll(),
+ mLayoutAlgorithm.mMinScrollP, mLayoutAlgorithm.mMaxScrollP), 0, 1);
}
/**
@@ -1240,7 +1162,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
* updateLayoutForStack() is called first.
*/
public TaskStackLayoutAlgorithm.VisibilityReport computeStackVisibilityReport() {
- return mLayoutAlgorithm.computeStackVisibilityReport(mStack.getStackTasks());
+ return mLayoutAlgorithm.computeStackVisibilityReport(mStack.getTasks());
}
/**
@@ -1279,10 +1201,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
// Compute the rects in the stack algorithm
- mStableLayoutAlgorithm.initialize(mDisplayRect, mStableWindowRect, mStableStackBounds,
- TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
- mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds,
- TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
+ mStableLayoutAlgorithm.initialize(mDisplayRect, mStableWindowRect, mStableStackBounds);
+ mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds);
updateLayoutAlgorithm(false /* boundScroll */);
// If this is the first layout, then scroll to the front of the stack, then update the
@@ -1405,11 +1325,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// Setup the view for the enter animation
mAnimationHelper.prepareForEnterAnimation();
- // Animate in the freeform workspace
- int ffBgAlpha = mLayoutAlgorithm.getStackState().freeformBackgroundAlpha;
- animateFreeformWorkspaceBackgroundAlpha(ffBgAlpha, new AnimationProps(150,
- Interpolators.FAST_OUT_SLOW_IN));
-
// Set the task focused state without requesting view focus, and leave the focus animations
// until after the enter-animation
RecentsConfiguration config = Recents.getConfiguration();
@@ -1418,10 +1333,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// We set the initial focused task view iff the following conditions are satisfied:
// 1. Recents is showing task views in stack layout.
// 2. Recents is launched with ALT + TAB.
- boolean setFocusOnFirstLayout = !useGridLayout() ||
- Recents.getConfiguration().getLaunchState().launchedWithAltTab;
+ boolean setFocusOnFirstLayout = !useGridLayout() || launchState.launchedWithAltTab;
if (setFocusOnFirstLayout) {
- int focusedTaskIndex = launchState.getInitialFocusTaskIndex(mStack.getTaskCount(),
+ int focusedTaskIndex = getInitialFocusTaskIndex(launchState, mStack.getTaskCount(),
useGridLayout());
if (focusedTaskIndex != -1) {
setFocusedTask(focusedTaskIndex, false /* scrollToTask */,
@@ -1457,43 +1371,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
return null;
}
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
-
- // Draw the freeform workspace background
- SystemServicesProxy ssp = Recents.getSystemServices();
- if (ssp.hasFreeformWorkspaceSupport()) {
- if (mFreeformWorkspaceBackground.getAlpha() > 0) {
- mFreeformWorkspaceBackground.draw(canvas);
- }
- }
- }
-
- @Override
- protected boolean verifyDrawable(Drawable who) {
- if (who == mFreeformWorkspaceBackground) {
- return true;
- }
- return super.verifyDrawable(who);
- }
-
- /**
- * Launches the freeform tasks.
- */
- public boolean launchFreeformTasks() {
- ArrayList<Task> tasks = mStack.getFreeformTasks();
- if (!tasks.isEmpty()) {
- Task frontTask = tasks.get(tasks.size() - 1);
- if (frontTask != null && frontTask.isFreeformTask()) {
- EventBus.getDefault().send(new LaunchTaskEvent(getChildViewForTask(frontTask),
- frontTask, null, INVALID_STACK_ID, false));
- return true;
- }
- }
- return false;
- }
-
/**** TaskStackCallbacks Implementation ****/
@Override
@@ -1629,7 +1506,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
@Override
public void onPickUpViewFromPool(TaskView tv, Task task, boolean isNewView) {
// Find the index where this task should be placed in the stack
- int taskIndex = mStack.indexOfStackTask(task);
+ int taskIndex = mStack.indexOfTask(task);
int insertIndex = findTaskViewInsertIndex(task, taskIndex);
// Add/attach the view to the hierarchy
@@ -1672,8 +1549,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
// Restore the action button visibility if it is the front most task view
- if (mScreenPinningEnabled && tv.getTask() ==
- mStack.getStackFrontMostTask(false /* includeFreeform */)) {
+ if (mScreenPinningEnabled && tv.getTask() == mStack.getFrontMostTask()) {
tv.showActionButton(false /* fadeIn */, 0 /* fadeInDuration */);
}
}
@@ -1689,7 +1565,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// If the doze trigger has already fired, then update the state for this task view
if (mUIDozeTrigger.isAsleep() ||
- Recents.getSystemServices().hasFreeformWorkspaceSupport() ||
useGridLayout() || Recents.getConfiguration().isLowRamDevice) {
tv.setNoUserInteractionState();
}
@@ -1798,7 +1673,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
event.packageName, event.userId);
// For other tasks, just remove them directly if they no longer exist
- ArrayList<Task> tasks = mStack.getStackTasks();
+ ArrayList<Task> tasks = mStack.getTasks();
for (int i = tasks.size() - 1; i >= 0; i--) {
final Task t = tasks.get(i);
if (removedComponents.contains(t.key.getComponent())) {
@@ -1821,21 +1696,17 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
public final void onBusEvent(LaunchMostRecentTaskRequestEvent event) {
if (mStack.getTaskCount() > 0) {
- Task mostRecentTask = mStack.getStackFrontMostTask(true /* includeFreefromTasks */);
+ Task mostRecentTask = mStack.getFrontMostTask();
launchTask(mostRecentTask);
}
}
public final void onBusEvent(ShowStackActionButtonEvent event) {
- if (RecentsDebugFlags.Static.EnableStackActionButton) {
- mStackActionButtonVisible = true;
- }
+ mStackActionButtonVisible = true;
}
public final void onBusEvent(HideStackActionButtonEvent event) {
- if (RecentsDebugFlags.Static.EnableStackActionButton) {
- mStackActionButtonVisible = false;
- }
+ mStackActionButtonVisible = false;
}
public final void onBusEvent(LaunchNextTaskRequestEvent event) {
@@ -1892,11 +1763,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// Start the task animations
mAnimationHelper.startExitToHomeAnimation(event.animated, event.getAnimationTrigger());
- // Dismiss the freeform workspace background
- int taskViewExitToHomeDuration = TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION;
- animateFreeformWorkspaceBackgroundAlpha(0, new AnimationProps(taskViewExitToHomeDuration,
- Interpolators.FAST_OUT_SLOW_IN));
-
// Dismiss the grid task view focus frame
if (mTaskViewFocusFrame != null) {
mTaskViewFocusFrame.moveGridTaskViewFocus(null);
@@ -1924,7 +1790,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
public final void onBusEvent(final DismissAllTaskViewsEvent event) {
// Keep track of the tasks which will have their data removed
- ArrayList<Task> tasks = new ArrayList<>(mStack.getStackTasks());
+ ArrayList<Task> tasks = new ArrayList<>(mStack.getTasks());
mAnimationHelper.startDeleteAllTasksAnimation(
getTaskViews(), useGridLayout(), event.getAnimationTrigger());
event.addPostAnimationCallback(new Runnable() {
@@ -1978,8 +1844,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
mStackScroller.stopScroller();
mStackScroller.stopBoundScrollAnimation();
- setRelativeFocusedTask(true, false /* stackTasksOnly */, true /* animated */, false,
- event.timerIndicatorDuration);
+ setRelativeFocusedTask(true, false /* stackTasksOnly */, true /* animated */, false, 0);
}
public final void onBusEvent(FocusPreviousTaskViewEvent event) {
@@ -1993,7 +1858,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
public final void onBusEvent(NavigateTaskViewEvent event) {
if (useGridLayout()) {
final int taskCount = mStack.getTaskCount();
- final int currentIndex = mStack.indexOfStackTask(getFocusedTask());
+ final int currentIndex = mStack.indexOfTask(getFocusedTask());
final int nextIndex = mLayoutAlgorithm.mTaskGridLayoutAlgorithm.navigateFocus(taskCount,
currentIndex, event.direction);
setFocusedTask(nextIndex, false, true);
@@ -2003,8 +1868,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
EventBus.getDefault().send(new FocusPreviousTaskViewEvent());
break;
case DOWN:
- EventBus.getDefault().send(
- new FocusNextTaskViewEvent(0 /* timerIndicatorDuration */));
+ EventBus.getDefault().send(new FocusNextTaskViewEvent());
break;
}
}
@@ -2015,7 +1879,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
mUIDozeTrigger.poke();
RecentsDebugFlags debugFlags = Recents.getDebugFlags();
- if (debugFlags.isFastToggleRecentsEnabled() && mFocusedTask != null) {
+ if (mFocusedTask != null) {
TaskView tv = getChildViewForTask(mFocusedTask);
if (tv != null) {
tv.getHeaderView().cancelFocusTimerIndicator();
@@ -2027,11 +1891,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// Ensure that the drag task is not animated
addIgnoreTask(event.task);
- if (event.task.isFreeformTask()) {
- // Animate to the front of the stack
- mStackScroller.animateScroll(mLayoutAlgorithm.mInitialScrollP, null);
- }
-
// Enlarge the dragged view slightly
float finalScale = event.taskView.getScaleX() * DRAG_SCALE_FACTOR;
mLayoutAlgorithm.getStackTransform(event.task, getScroller().getStackScroll(),
@@ -2043,22 +1902,14 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
new AnimationProps(DRAG_SCALE_DURATION, Interpolators.FAST_OUT_SLOW_IN));
}
- public final void onBusEvent(DragStartInitializeDropTargetsEvent event) {
- SystemServicesProxy ssp = Recents.getSystemServices();
- if (ssp.hasFreeformWorkspaceSupport()) {
- event.handler.registerDropTargetForCurrentDrag(mStackDropTarget);
- event.handler.registerDropTargetForCurrentDrag(mFreeformWorkspaceDropTarget);
- }
- }
-
public final void onBusEvent(DragDropTargetChangedEvent event) {
AnimationProps animation = new AnimationProps(SLOW_SYNC_STACK_DURATION,
Interpolators.FAST_OUT_SLOW_IN);
boolean ignoreTaskOverrides = false;
- if (event.dropTarget instanceof TaskStack.DockState) {
+ if (event.dropTarget instanceof DockState) {
// Calculate the new task stack bounds that matches the window size that Recents will
// have after the drop
- final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
+ final DockState dockState = (DockState) event.dropTarget;
Rect systemInsets = new Rect(mStableLayoutAlgorithm.mSystemInsets);
// When docked, the nav bar insets are consumed and the activity is measured without
// insets. However, the window bounds include the insets, so we need to subtract them
@@ -2070,8 +1921,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
height, mDividerSize, systemInsets,
mLayoutAlgorithm, getResources(), mWindowRect));
mLayoutAlgorithm.setSystemInsets(systemInsets);
- mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds,
- TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
+ mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds);
updateLayoutAlgorithm(true /* boundScroll */);
ignoreTaskOverrides = true;
} else {
@@ -2086,39 +1936,13 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
public final void onBusEvent(final DragEndEvent event) {
// We don't handle drops on the dock regions
- if (event.dropTarget instanceof TaskStack.DockState) {
+ if (event.dropTarget instanceof DockState) {
// However, we do need to reset the overrides, since the last state of this task stack
// view layout was ignoring task overrides (see DragDropTargetChangedEvent handler)
mLayoutAlgorithm.clearUnfocusedTaskOverrides();
return;
}
- boolean isFreeformTask = event.task.isFreeformTask();
- boolean hasChangedStacks =
- (!isFreeformTask && event.dropTarget == mFreeformWorkspaceDropTarget) ||
- (isFreeformTask && event.dropTarget == mStackDropTarget);
-
- if (hasChangedStacks) {
- // Move the task to the right position in the stack (ie. the front of the stack if
- // freeform or the front of the stack if fullscreen). Note, we MUST move the tasks
- // before we update their stack ids, otherwise, the keys will have changed.
- if (event.dropTarget == mFreeformWorkspaceDropTarget) {
- mStack.moveTaskToStack(event.task, FREEFORM_WORKSPACE_STACK_ID);
- } else if (event.dropTarget == mStackDropTarget) {
- mStack.moveTaskToStack(event.task, FULLSCREEN_WORKSPACE_STACK_ID);
- }
- updateLayoutAlgorithm(true /* boundScroll */);
-
- // Move the task to the new stack in the system after the animation completes
- event.addPostAnimationCallback(new Runnable() {
- @Override
- public void run() {
- SystemServicesProxy ssp = Recents.getSystemServices();
- ssp.moveTaskToStack(event.task.key.id, event.task.key.stackId);
- }
- });
- }
-
// Restore the task, so that relayout will apply to it below
removeIgnoreTask(event.task);
@@ -2153,13 +1977,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
event.getAnimationTrigger().increment();
}
- public final void onBusEvent(IterateRecentsEvent event) {
- if (!mEnterAnimationComplete) {
- // Cancel the previous task's window transition before animating the focused state
- EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(null));
- }
- }
-
public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) {
mEnterAnimationComplete = true;
tryStartEnterAnimation();
@@ -2178,9 +1995,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// Add a runnable to the post animation ref counter to clear all the views
trigger.addLastDecrementRunnable(() -> {
// Start the dozer to trigger to trigger any UI that shows after a timeout
- if (!Recents.getSystemServices().hasFreeformWorkspaceSupport()) {
- mUIDozeTrigger.startDozing();
- }
+ mUIDozeTrigger.startDozing();
// Update the focused state here -- since we only set the focused task without
// requesting view focus in onFirstLayout(), actually request view focus and
@@ -2189,7 +2004,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
if (mFocusedTask != null) {
RecentsConfiguration config = Recents.getConfiguration();
RecentsActivityLaunchState launchState = config.getLaunchState();
- setFocusedTask(mStack.indexOfStackTask(mFocusedTask),
+ setFocusedTask(mStack.indexOfTask(mFocusedTask),
false /* scrollToTask */, launchState.launchedWithAltTab);
TaskView focusedTaskView = getChildViewForTask(mFocusedTask);
if (mTouchExplorationEnabled && focusedTaskView != null) {
@@ -2203,18 +2018,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
mStackReloaded = false;
}
- public final void onBusEvent(UpdateFreeformTaskViewVisibilityEvent event) {
- List<TaskView> taskViews = getTaskViews();
- int taskViewCount = taskViews.size();
- for (int i = 0; i < taskViewCount; i++) {
- TaskView tv = taskViews.get(i);
- Task task = tv.getTask();
- if (task.isFreeformTask()) {
- tv.setVisibility(event.visible ? View.VISIBLE : View.INVISIBLE);
- }
- }
- }
-
public final void onBusEvent(final MultiWindowStateChangedEvent event) {
if (event.inMultiWindow || !event.showDeferredAnimation) {
setTasks(event.stack, true /* allowNotifyStackChanges */);
@@ -2263,8 +2066,11 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// Update the Clear All button in case we're switching in or out of grid layout.
updateStackActionButtonVisibility();
- // Trigger a new layout and update to the initial state if necessary
- if (event.fromMultiWindow) {
+ // Trigger a new layout and update to the initial state if necessary. When entering split
+ // screen, the multi-window configuration change event can happen after the stack is already
+ // reloaded (but pending measure/layout), in this case, do not override the intiial state
+ // and just wait for the upcoming measure/layout pass.
+ if (event.fromMultiWindow && mInitialState == INITIAL_STATE_UPDATE_NONE) {
mInitialState = INITIAL_STATE_UPDATE_LAYOUT_ONLY;
requestLayout();
} else if (event.fromDeviceOrientationChange) {
@@ -2316,27 +2122,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
/**
- * Starts an alpha animation on the freeform workspace background.
- */
- private void animateFreeformWorkspaceBackgroundAlpha(int targetAlpha,
- AnimationProps animation) {
- if (mFreeformWorkspaceBackground.getAlpha() == targetAlpha) {
- return;
- }
-
- Utilities.cancelAnimationWithoutCallbacks(mFreeformWorkspaceBackgroundAnimator);
- mFreeformWorkspaceBackgroundAnimator = ObjectAnimator.ofInt(mFreeformWorkspaceBackground,
- Utilities.DRAWABLE_ALPHA, mFreeformWorkspaceBackground.getAlpha(), targetAlpha);
- mFreeformWorkspaceBackgroundAnimator.setStartDelay(
- animation.getDuration(AnimationProps.ALPHA));
- mFreeformWorkspaceBackgroundAnimator.setDuration(
- animation.getDuration(AnimationProps.ALPHA));
- mFreeformWorkspaceBackgroundAnimator.setInterpolator(
- animation.getInterpolator(AnimationProps.ALPHA));
- mFreeformWorkspaceBackgroundAnimator.start();
- }
-
- /**
* Returns the insert index for the task in the current set of task views. If the given task
* is already in the task view list, then this method returns the insert index assuming it
* is first removed at the previous index.
@@ -2353,7 +2138,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
Task tvTask = taskViews.get(i).getTask();
if (tvTask == task) {
foundTaskView = true;
- } else if (taskIndex < mStack.indexOfStackTask(tvTask)) {
+ } else if (taskIndex < mStack.indexOfTask(tvTask)) {
if (foundTaskView) {
return i - 1;
} else {
@@ -2381,12 +2166,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
public void run() {
EventBus.getDefault().send(new LaunchTaskEvent(
getChildViewForTask(task), task, null,
- INVALID_STACK_ID, false /* screenPinningRequested */));
+ false /* screenPinningRequested */));
}
});
} else {
- EventBus.getDefault().send(new LaunchTaskEvent(getChildViewForTask(task),
- task, null, INVALID_STACK_ID, false /* screenPinningRequested */));
+ EventBus.getDefault().send(new LaunchTaskEvent(getChildViewForTask(task), task, null,
+ false /* screenPinningRequested */));
}
}
@@ -2403,8 +2188,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
private void readSystemFlags() {
SystemServicesProxy ssp = Recents.getSystemServices();
mTouchExplorationEnabled = ssp.isTouchExplorationEnabled();
- mScreenPinningEnabled = ssp.getSystemSetting(getContext(),
- Settings.System.LOCK_TO_APP_ENABLED) != 0;
+ mScreenPinningEnabled = ActivityManagerWrapper.getInstance().isLockToAppEnabled();
}
private void updateStackActionButtonVisibility() {
@@ -2422,6 +2206,43 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
}
+ /**
+ * Returns the task to focus given the current launch state.
+ */
+ private int getInitialFocusTaskIndex(RecentsActivityLaunchState launchState, int numTasks,
+ boolean useGridLayout) {
+ if (launchState.launchedFromApp) {
+ if (useGridLayout) {
+ // If coming from another app to the grid layout, focus the front most task
+ return numTasks - 1;
+ }
+
+ // If coming from another app, focus the next task
+ return Math.max(0, numTasks - 2);
+ } else {
+ // If coming from home, focus the front most task
+ return numTasks - 1;
+ }
+ }
+
+ /**
+ * Updates {@param transforms} to be the same size as {@param tasks}.
+ */
+ private void matchTaskListSize(List<Task> tasks, List<TaskViewTransform> transforms) {
+ // We can reuse the task transforms where possible to reduce object allocation
+ int taskTransformCount = transforms.size();
+ int taskCount = tasks.size();
+ if (taskTransformCount < taskCount) {
+ // If there are less transforms than tasks, then add as many transforms as necessary
+ for (int i = taskTransformCount; i < taskCount; i++) {
+ transforms.add(new TaskViewTransform());
+ }
+ } else if (taskTransformCount > taskCount) {
+ // If there are more transforms than tasks, then just subset the transform list
+ transforms.subList(taskCount, taskTransformCount).clear();
+ }
+ }
+
public void dump(String prefix, PrintWriter writer) {
String innerPrefix = prefix + " ";
String id = Integer.toHexString(System.identityHashCode(this));
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
index 0b20b105617d..6b23977410c7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
@@ -24,7 +24,6 @@ import android.animation.ValueAnimator;
import android.content.Context;
import android.util.FloatProperty;
import android.util.Log;
-import android.util.MutableFloat;
import android.util.Property;
import android.view.ViewConfiguration;
import android.view.ViewDebug;
@@ -33,7 +32,8 @@ import android.widget.OverScroller;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.misc.Utilities;
+import com.android.systemui.shared.recents.utilities.AnimationProps;
+import com.android.systemui.shared.recents.utilities.Utilities;
import com.android.systemui.recents.views.lowram.TaskStackLowRamLayoutAlgorithm;
import com.android.systemui.statusbar.FlingAnimationUtils;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index 32a249c289c6..c91cdfc379c4 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -21,7 +21,6 @@ import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Path;
-import android.graphics.Rect;
import android.util.ArrayMap;
import android.util.MutableBoolean;
import android.view.InputDevice;
@@ -45,9 +44,9 @@ import com.android.systemui.recents.events.activity.HideRecentsEvent;
import com.android.systemui.recents.events.ui.StackViewScrolledEvent;
import com.android.systemui.recents.events.ui.TaskViewDismissedEvent;
import com.android.systemui.recents.misc.FreePathInterpolator;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.Task;
+import com.android.systemui.shared.recents.utilities.AnimationProps;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.statusbar.FlingAnimationUtils;
import java.util.ArrayList;
@@ -257,6 +256,9 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
}
case MotionEvent.ACTION_MOVE: {
int activePointerIndex = ev.findPointerIndex(mActivePointerId);
+ if (activePointerIndex == -1) {
+ break;
+ }
int y = (int) ev.getY(activePointerIndex);
int x = (int) ev.getX(activePointerIndex);
if (!mIsScrolling) {
@@ -403,18 +405,6 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
return;
}
- // If tapping on the freeform workspace background, just launch the first freeform task
- SystemServicesProxy ssp = Recents.getSystemServices();
- if (ssp.hasFreeformWorkspaceSupport()) {
- Rect freeformRect = mSv.mLayoutAlgorithm.mFreeformRect;
- if (freeformRect.top <= y && y <= freeformRect.bottom) {
- if (mSv.launchFreeformTasks()) {
- // TODO: Animate Recents away as we launch the freeform tasks
- return;
- }
- }
- }
-
// The user intentionally tapped on the background, which is like a tap on the "desktop".
// Hide recents and transition to the launcher.
EventBus.getDefault().send(new HideRecentsEvent(false, true));
@@ -459,7 +449,7 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
TaskView tv = (TaskView) v;
Task task = tv.getTask();
return !mSwipeHelperAnimations.containsKey(v) &&
- (mSv.getStack().indexOfStackTask(task) != -1);
+ (mSv.getStack().indexOfTask(task) != -1);
}
/**
@@ -489,7 +479,7 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
mSv.addIgnoreTask(tv.getTask());
// Determine if we are animating the other tasks while dismissing this task
- mCurrentTasks = new ArrayList<Task>(mSv.getStack().getStackTasks());
+ mCurrentTasks = new ArrayList<Task>(mSv.getStack().getTasks());
MutableBoolean isFrontMostTask = new MutableBoolean(false);
Task anchorTask = mSv.findAnchorTask(mCurrentTasks, isFrontMostTask);
TaskStackLayoutAlgorithm layoutAlgorithm = mSv.getStackAlgorithm();
@@ -686,7 +676,7 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
/** Returns the view at the specified coordinates */
private TaskView findViewAtPoint(int x, int y) {
- List<Task> tasks = mSv.getStack().getStackTasks();
+ List<Task> tasks = mSv.getStack().getTasks();
int taskCount = tasks.size();
for (int i = taskCount - 1; i >= 0; i--) {
TaskView tv = mSv.getChildViewForTask(tasks.get(i));
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index ceeebd96a3e1..f0278a69e116 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -16,8 +16,6 @@
package com.android.systemui.recents.views;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
@@ -53,10 +51,11 @@ import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
import com.android.systemui.recents.misc.ReferenceCountedTrigger;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.TaskStack;
-import com.android.systemui.recents.model.ThumbnailData;
+import com.android.systemui.shared.recents.utilities.AnimationProps;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.recents.view.AnimateableViewBounds;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -196,9 +195,7 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks
* Called from RecentsActivity when it is relaunched.
*/
void onReload(boolean isResumingFromVisible) {
- if (!Recents.getSystemServices().hasFreeformWorkspaceSupport()) {
- resetNoUserInteractionState();
- }
+ resetNoUserInteractionState();
if (!isResumingFromVisible) {
resetViewProperties();
}
@@ -415,9 +412,7 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks
* view.
*/
boolean shouldClipViewInStack() {
- // Never clip for freeform tasks or if invisible
- if (mTask.isFreeformTask() || getVisibility() != View.VISIBLE ||
- Recents.getConfiguration().isLowRamDevice) {
+ if (getVisibility() != View.VISIBLE || Recents.getConfiguration().isLowRamDevice) {
return false;
}
return mClipViewInStack;
@@ -647,7 +642,7 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks
}
@Override
- public void onTaskStackIdChanged() {
+ public void onTaskWindowingModeChanged() {
// Force rebind the header, the thumbnail does not change due to stack changes
mHeaderView.bindToTask(mTask, mTouchExplorationEnabled, mIsDisabledInSafeMode);
mHeaderView.onTaskDataLoaded();
@@ -674,8 +669,7 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks
mActionButtonView.setTranslationZ(0f);
screenPinningRequested = true;
}
- EventBus.getDefault().send(new LaunchTaskEvent(this, mTask, null, INVALID_STACK_ID,
- screenPinningRequested));
+ EventBus.getDefault().send(new LaunchTaskEvent(this, mTask, null, screenPinningRequested));
MetricsLogger.action(v.getContext(), MetricsEvent.ACTION_OVERVIEW_SELECT,
mTask.key.getComponent().toString());
@@ -690,7 +684,7 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks
}
SystemServicesProxy ssp = Recents.getSystemServices();
boolean inBounds = false;
- Rect clipBounds = new Rect(mViewBounds.mClipBounds);
+ Rect clipBounds = new Rect(mViewBounds.getClipBounds());
if (!clipBounds.isEmpty()) {
// If we are clipping the view to the bounds, manually do the hit test.
clipBounds.scale(getScaleX());
@@ -716,7 +710,7 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks
/**** Events ****/
public final void onBusEvent(DragEndEvent event) {
- if (!(event.dropTarget instanceof TaskStack.DockState)) {
+ if (!(event.dropTarget instanceof DockState)) {
event.addPostAnimationCallback(() -> {
// Reset the clip state for the drag view after the end animation completes
setClipViewInStack(true);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewAccessibilityDelegate.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewAccessibilityDelegate.java
index 0c6b6b842655..0fc507b92bf3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewAccessibilityDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewAccessibilityDelegate.java
@@ -28,11 +28,10 @@ import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import com.android.systemui.R;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
-import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.model.TaskStack;
public class TaskViewAccessibilityDelegate extends View.AccessibilityDelegate {
private static final String TAG = "TaskViewAccessibilityDelegate";
@@ -61,14 +60,14 @@ public class TaskViewAccessibilityDelegate extends View.AccessibilityDelegate {
super.onInitializeAccessibilityNodeInfo(host, info);
if (ActivityManager.supportsSplitScreenMultiWindow(mTaskView.getContext())
&& !Recents.getSystemServices().hasDockedTask()) {
- TaskStack.DockState[] dockStates = Recents.getConfiguration()
+ DockState[] dockStates = Recents.getConfiguration()
.getDockStatesForCurrentOrientation();
- for (TaskStack.DockState dockState: dockStates) {
- if (dockState == TaskStack.DockState.TOP) {
+ for (DockState dockState: dockStates) {
+ if (dockState == DockState.TOP) {
info.addAction(mActions.get(SPLIT_TASK_TOP));
- } else if (dockState == TaskStack.DockState.LEFT) {
+ } else if (dockState == DockState.LEFT) {
info.addAction(mActions.get(SPLIT_TASK_LEFT));
- } else if (dockState == TaskStack.DockState.RIGHT) {
+ } else if (dockState == DockState.RIGHT) {
info.addAction(mActions.get(SPLIT_TASK_RIGHT));
}
}
@@ -78,11 +77,11 @@ public class TaskViewAccessibilityDelegate extends View.AccessibilityDelegate {
@Override
public boolean performAccessibilityAction(View host, int action, Bundle args) {
if (action == SPLIT_TASK_TOP) {
- simulateDragIntoMultiwindow(TaskStack.DockState.TOP);
+ simulateDragIntoMultiwindow(DockState.TOP);
} else if (action == SPLIT_TASK_LEFT) {
- simulateDragIntoMultiwindow(TaskStack.DockState.LEFT);
+ simulateDragIntoMultiwindow(DockState.LEFT);
} else if (action == SPLIT_TASK_RIGHT) {
- simulateDragIntoMultiwindow(TaskStack.DockState.RIGHT);
+ simulateDragIntoMultiwindow(DockState.RIGHT);
} else {
return super.performAccessibilityAction(host, action, args);
}
@@ -90,8 +89,7 @@ public class TaskViewAccessibilityDelegate extends View.AccessibilityDelegate {
}
/** Simulate a user drag event to split the screen to the respected side */
- private void simulateDragIntoMultiwindow(TaskStack.DockState dockState) {
- int orientation = Utilities.getAppConfiguration(mTaskView.getContext()).orientation;
+ private void simulateDragIntoMultiwindow(DockState dockState) {
EventBus.getDefault().send(new DragStartEvent(mTaskView.getTask(), mTaskView,
new Point(0,0), false /* isUserTouchInitiated */));
EventBus.getDefault().send(new DragEndEvent(mTaskView.getTask(), mTaskView, dockState));
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
index ae922fcc218e..0272a9038ba8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
@@ -16,6 +16,9 @@
package com.android.systemui.recents.views;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.Nullable;
@@ -28,7 +31,6 @@ import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
-import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.RippleDrawable;
@@ -54,12 +56,10 @@ import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.LaunchTaskEvent;
import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.Task;
-
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.PackageManagerWrapper;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.model.Task;
/* The task bar view */
public class TaskViewHeader extends FrameLayout
@@ -163,8 +163,6 @@ public class TaskViewHeader extends FrameLayout
float mDimAlpha;
Drawable mLightDismissDrawable;
Drawable mDarkDismissDrawable;
- Drawable mLightFreeformIcon;
- Drawable mDarkFreeformIcon;
Drawable mLightFullscreenIcon;
Drawable mDarkFullscreenIcon;
Drawable mLightInfoIcon;
@@ -172,7 +170,9 @@ public class TaskViewHeader extends FrameLayout
int mTaskBarViewLightTextColor;
int mTaskBarViewDarkTextColor;
int mDisabledTaskBarBackgroundColor;
- int mMoveTaskTargetStackId = INVALID_STACK_ID;
+ String mDismissDescFormat;
+ String mAppInfoDescFormat;
+ int mTaskWindowingMode = WINDOWING_MODE_UNDEFINED;
// Header background
private HighlightColorDrawable mBackground;
@@ -214,14 +214,15 @@ public class TaskViewHeader extends FrameLayout
mHighlightHeight = res.getDimensionPixelSize(R.dimen.recents_task_view_highlight);
mTaskBarViewLightTextColor = context.getColor(R.color.recents_task_bar_light_text_color);
mTaskBarViewDarkTextColor = context.getColor(R.color.recents_task_bar_dark_text_color);
- mLightFreeformIcon = context.getDrawable(R.drawable.recents_move_task_freeform_light);
- mDarkFreeformIcon = context.getDrawable(R.drawable.recents_move_task_freeform_dark);
mLightFullscreenIcon = context.getDrawable(R.drawable.recents_move_task_fullscreen_light);
mDarkFullscreenIcon = context.getDrawable(R.drawable.recents_move_task_fullscreen_dark);
mLightInfoIcon = context.getDrawable(R.drawable.recents_info_light);
mDarkInfoIcon = context.getDrawable(R.drawable.recents_info_dark);
mDisabledTaskBarBackgroundColor =
context.getColor(R.color.recents_task_bar_disabled_background_color);
+ mDismissDescFormat = mContext.getString(
+ R.string.accessibility_recents_item_will_be_dismissed);
+ mAppInfoDescFormat = mContext.getString(R.string.accessibility_recents_item_open_app_info);
// Configure the background and dim
mBackground = new HighlightColorDrawable();
@@ -248,9 +249,6 @@ public class TaskViewHeader extends FrameLayout
mIconView.setOnLongClickListener(this);
mTitleView = findViewById(R.id.title);
mDismissButton = findViewById(R.id.dismiss_task);
- if (ssp.hasFreeformWorkspaceSupport()) {
- mMoveTaskButton = findViewById(R.id.move_task);
- }
onConfigurationChanged();
}
@@ -340,20 +338,6 @@ public class TaskViewHeader extends FrameLayout
boolean showDismissIcon = true;
int rightInset = width - getMeasuredWidth();
- if (mTask != null && mTask.isFreeformTask()) {
- // For freeform tasks, we always show the app icon, and only show the title, move-task
- // icon, and the dismiss icon if there is room
- int appIconWidth = mIconView.getMeasuredWidth();
- int titleWidth = (int) mTitleView.getPaint().measureText(mTask.title);
- int dismissWidth = mDismissButton.getMeasuredWidth();
- int moveTaskWidth = mMoveTaskButton != null
- ? mMoveTaskButton.getMeasuredWidth()
- : 0;
- showTitle = width >= (appIconWidth + dismissWidth + moveTaskWidth + titleWidth);
- showMoveIcon = width >= (appIconWidth + dismissWidth + moveTaskWidth);
- showDismissIcon = width >= (appIconWidth + dismissWidth);
- }
-
mTitleView.setVisibility(showTitle ? View.VISIBLE : View.INVISIBLE);
if (mMoveTaskButton != null) {
mMoveTaskButton.setVisibility(showMoveIcon ? View.VISIBLE : View.INVISIBLE);
@@ -476,44 +460,14 @@ public class TaskViewHeader extends FrameLayout
mTaskBarViewLightTextColor : mTaskBarViewDarkTextColor);
mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ?
mLightDismissDrawable : mDarkDismissDrawable);
- mDismissButton.setContentDescription(t.dismissDescription);
+ mDismissButton.setContentDescription(String.format(mDismissDescFormat, t.titleDescription));
mDismissButton.setOnClickListener(this);
mDismissButton.setClickable(false);
((RippleDrawable) mDismissButton.getBackground()).setForceSoftware(true);
- // When freeform workspaces are enabled, then update the move-task button depending on the
- // current task
- if (mMoveTaskButton != null) {
- if (t.isFreeformTask()) {
- mMoveTaskTargetStackId = FULLSCREEN_WORKSPACE_STACK_ID;
- mMoveTaskButton.setImageDrawable(t.useLightOnPrimaryColor
- ? mLightFullscreenIcon
- : mDarkFullscreenIcon);
- } else {
- mMoveTaskTargetStackId = FREEFORM_WORKSPACE_STACK_ID;
- mMoveTaskButton.setImageDrawable(t.useLightOnPrimaryColor
- ? mLightFreeformIcon
- : mDarkFreeformIcon);
- }
- mMoveTaskButton.setOnClickListener(this);
- mMoveTaskButton.setClickable(false);
- ((RippleDrawable) mMoveTaskButton.getBackground()).setForceSoftware(true);
- }
-
- if (Recents.getDebugFlags().isFastToggleRecentsEnabled()) {
- if (mFocusTimerIndicator == null) {
- mFocusTimerIndicator = (ProgressBar) Utilities.findViewStubById(this,
- R.id.focus_timer_indicator_stub).inflate();
- }
- mFocusTimerIndicator.getProgressDrawable()
- .setColorFilter(
- getSecondaryColor(t.colorPrimary, t.useLightOnPrimaryColor),
- PorterDuff.Mode.SRC_IN);
- }
-
// In accessibility, a single click on the focused app info button will show it
if (touchExplorationEnabled) {
- mIconView.setContentDescription(t.appInfoDescription);
+ mIconView.setContentDescription(String.format(mAppInfoDescFormat, t.titleDescription));
mIconView.setOnClickListener(this);
mIconView.setClickable(true);
}
@@ -621,8 +575,8 @@ public class TaskViewHeader extends FrameLayout
Constants.Metrics.DismissSourceHeaderButton);
} else if (v == mMoveTaskButton) {
TaskView tv = Utilities.findParent(this, TaskView.class);
- EventBus.getDefault().send(new LaunchTaskEvent(tv, mTask, null,
- mMoveTaskTargetStackId, false));
+ EventBus.getDefault().send(new LaunchTaskEvent(tv, mTask, null, false,
+ mTaskWindowingMode, ACTIVITY_TYPE_UNDEFINED));
} else if (v == mAppInfoView) {
EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask));
} else if (v == mAppIconView) {
@@ -650,7 +604,7 @@ public class TaskViewHeader extends FrameLayout
SystemServicesProxy ssp = Recents.getSystemServices();
ComponentName cn = mTask.key.getComponent();
int userId = mTask.key.userId;
- ActivityInfo activityInfo = ssp.getActivityInfo(cn, userId);
+ ActivityInfo activityInfo = PackageManagerWrapper.getInstance().getActivityInfo(cn, userId);
if (activityInfo == null) {
return;
}
@@ -670,11 +624,12 @@ public class TaskViewHeader extends FrameLayout
}
// Update the overlay contents for the current app
- mAppTitleView.setText(ssp.getBadgedApplicationLabel(activityInfo.applicationInfo, userId));
+ mAppTitleView.setText(ActivityManagerWrapper.getInstance().getBadgedApplicationLabel(
+ activityInfo.applicationInfo, userId));
mAppTitleView.setTextColor(mTask.useLightOnPrimaryColor ?
mTaskBarViewLightTextColor : mTaskBarViewDarkTextColor);
- mAppIconView.setImageDrawable(ssp.getBadgedApplicationIcon(activityInfo.applicationInfo,
- userId));
+ mAppIconView.setImageDrawable(ActivityManagerWrapper.getInstance().getBadgedApplicationIcon(
+ activityInfo.applicationInfo, userId));
mAppInfoView.setImageDrawable(mTask.useLightOnPrimaryColor
? mLightInfoIcon
: mDarkInfoIcon);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
index a2190b3a3d19..4152b05a960e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
@@ -37,9 +37,9 @@ import android.view.ViewDebug;
import com.android.systemui.R;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.ui.TaskSnapshotChangedEvent;
-import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.ThumbnailData;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.ThumbnailData;
import java.io.PrintWriter;
@@ -245,10 +245,6 @@ public class TaskViewThumbnail extends View {
public void updateThumbnailMatrix() {
mThumbnailScale = 1f;
if (mBitmapShader != null && mThumbnailData != null) {
- // We consider this a stack task if it is not freeform (ie. has no bounds) or has been
- // dragged into the stack from the freeform workspace
- boolean isStackTask = !mTask.isFreeformTask() || mTask.bounds == null;
- int xOffset, yOffset = 0;
if (mTaskViewRect.isEmpty()) {
// If we haven't measured , skip the thumbnail drawing and only draw the background
// color
@@ -266,7 +262,7 @@ public class TaskViewThumbnail extends View {
mThumbnailScale = (float) (mTaskViewRect.height() - mTitleBarHeight)
/ (float) mThumbnailRect.height();
}
- } else if (isStackTask) {
+ } else {
float invThumbnailScale = 1f / mFullscreenThumbnailScale;
if (mDisplayOrientation == Configuration.ORIENTATION_PORTRAIT) {
if (mThumbnailData.orientation == Configuration.ORIENTATION_PORTRAIT) {
@@ -283,12 +279,6 @@ public class TaskViewThumbnail extends View {
// Otherwise, scale the screenshot to fit 1:1 in the current orientation
mThumbnailScale = invThumbnailScale;
}
- } else {
- // Otherwise, if this is a freeform task with task bounds, then scale the thumbnail
- // to fit the entire bitmap into the task bounds
- mThumbnailScale = Math.min(
- (float) mTaskViewRect.width() / mThumbnailRect.width(),
- (float) mTaskViewRect.height() / mThumbnailRect.height());
}
mMatrix.setTranslate(-mThumbnailData.insets.left * mFullscreenThumbnailScale,
-mThumbnailData.insets.top * mFullscreenThumbnailScale);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
index 397f24eb86d3..9b717e0e5e2f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
@@ -21,11 +21,11 @@ import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.graphics.Rect;
import android.graphics.RectF;
-import android.util.IntProperty;
import android.util.Property;
import android.view.View;
-import com.android.systemui.recents.misc.Utilities;
+import com.android.systemui.shared.recents.utilities.AnimationProps;
+import com.android.systemui.shared.recents.utilities.Utilities;
import java.util.ArrayList;
@@ -59,7 +59,7 @@ public class TaskViewTransform {
public boolean visible = false;
- // This is a window-space rect used for positioning the task in the stack and freeform workspace
+ // This is a window-space rect used for positioning the task in the stack
public RectF rect = new RectF();
/**
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/grid/AnimateableGridViewBounds.java b/packages/SystemUI/src/com/android/systemui/recents/views/grid/AnimateableGridViewBounds.java
index a029478c2045..3bdad314d7f3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/grid/AnimateableGridViewBounds.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/grid/AnimateableGridViewBounds.java
@@ -17,7 +17,7 @@
package com.android.systemui.recents.views.grid;
import android.view.View;
-import com.android.systemui.recents.views.AnimateableViewBounds;
+import com.android.systemui.shared.recents.view.AnimateableViewBounds;
/* An outline provider for grid-based task views. */
class AnimateableGridViewBounds extends AnimateableViewBounds {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/grid/GridTaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/grid/GridTaskView.java
index 8b4700c54b00..0d5115444b01 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/grid/GridTaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/grid/GridTaskView.java
@@ -19,7 +19,7 @@ package com.android.systemui.recents.views.grid;
import android.content.Context;
import android.util.AttributeSet;
import com.android.systemui.R;
-import com.android.systemui.recents.views.AnimateableViewBounds;
+import com.android.systemui.shared.recents.view.AnimateableViewBounds;
import com.android.systemui.recents.views.TaskView;
public class GridTaskView extends TaskView {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java
index c5132024d505..ccda4b5aaf1f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java
@@ -25,10 +25,9 @@ import android.graphics.Rect;
import android.view.WindowManager;
import com.android.systemui.R;
-import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent;
import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent.Direction;
-import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.Task;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
import com.android.systemui.recents.views.TaskViewTransform;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskViewFocusFrame.java b/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskViewFocusFrame.java
index 86ed583b07aa..fe6bafb27c7c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskViewFocusFrame.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskViewFocusFrame.java
@@ -23,7 +23,7 @@ import android.view.View;
import android.view.ViewTreeObserver.OnGlobalFocusChangeListener;
import com.android.systemui.R;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.shared.recents.model.TaskStack;
import com.android.systemui.recents.views.TaskStackView;
public class TaskViewFocusFrame extends View implements OnGlobalFocusChangeListener {
@@ -125,7 +125,7 @@ public class TaskViewFocusFrame extends View implements OnGlobalFocusChangeListe
// We're returning from touch mode, set the focus to the previously focused task.
final TaskStack stack = mSv.getStack();
final int taskCount = stack.getTaskCount();
- final int k = stack.indexOfStackTask(mSv.getFocusedTask());
+ final int k = stack.indexOfTask(mSv.getFocusedTask());
final int taskIndexToFocus = k == -1 ? (taskCount - 1) : (k % taskCount);
mSv.setFocusedTask(taskIndexToFocus, false, true);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/lowram/TaskStackLowRamLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/lowram/TaskStackLowRamLayoutAlgorithm.java
index 17e6b9e3c195..49cac269f51d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/lowram/TaskStackLowRamLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/lowram/TaskStackLowRamLayoutAlgorithm.java
@@ -23,8 +23,8 @@ import android.view.ViewConfiguration;
import com.android.systemui.R;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsActivityLaunchState;
-import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.Task;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
import com.android.systemui.recents.views.TaskViewTransform;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 991c3c83cbc1..2acb1bb2613b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -43,13 +43,13 @@ import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Matrix;
import android.graphics.Paint;
+import android.graphics.Picture;
import android.graphics.PixelFormat;
import android.graphics.PointF;
import android.graphics.Rect;
import android.media.MediaActionSound;
import android.net.Uri;
import android.os.AsyncTask;
-import android.os.Bundle;
import android.os.Environment;
import android.os.PowerManager;
import android.os.Process;
@@ -154,7 +154,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
int previewWidth = data.previewWidth;
int previewHeight = data.previewheight;
- Canvas c = new Canvas();
Paint paint = new Paint();
ColorMatrix desat = new ColorMatrix();
desat.setSaturation(0.25f);
@@ -162,23 +161,17 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
Matrix matrix = new Matrix();
int overlayColor = 0x40FFFFFF;
- Bitmap picture = Bitmap.createBitmap(previewWidth, previewHeight, data.image.getConfig());
matrix.setTranslate((previewWidth - mImageWidth) / 2, (previewHeight - mImageHeight) / 2);
- c.setBitmap(picture);
- c.drawBitmap(data.image, matrix, paint);
- c.drawColor(overlayColor);
- c.setBitmap(null);
+ Bitmap picture = generateAdjustedHwBitmap(data.image, previewWidth, previewHeight, matrix,
+ paint, overlayColor);
// Note, we can't use the preview for the small icon, since it is non-square
float scale = (float) iconSize / Math.min(mImageWidth, mImageHeight);
- Bitmap icon = Bitmap.createBitmap(iconSize, iconSize, data.image.getConfig());
matrix.setScale(scale, scale);
matrix.postTranslate((iconSize - (scale * mImageWidth)) / 2,
(iconSize - (scale * mImageHeight)) / 2);
- c.setBitmap(icon);
- c.drawBitmap(data.image, matrix, paint);
- c.drawColor(overlayColor);
- c.setBitmap(null);
+ Bitmap icon = generateAdjustedHwBitmap(data.image, iconSize, iconSize, matrix, paint,
+ overlayColor);
// Show the intermediate notification
mTickerAddSpace = !mTickerAddSpace;
@@ -191,7 +184,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
// The public notification will show similar info but with the actual screenshot omitted
mPublicNotificationBuilder =
- new Notification.Builder(context, NotificationChannels.SCREENSHOTS)
+ new Notification.Builder(context, NotificationChannels.SCREENSHOTS_HEADSUP)
.setContentTitle(r.getString(R.string.screenshot_saving_title))
.setContentText(r.getString(R.string.screenshot_saving_text))
.setSmallIcon(R.drawable.stat_notify_image)
@@ -202,7 +195,8 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
com.android.internal.R.color.system_notification_accent_color));
SystemUI.overrideNotificationAppName(context, mPublicNotificationBuilder);
- mNotificationBuilder = new Notification.Builder(context, NotificationChannels.SCREENSHOTS)
+ mNotificationBuilder = new Notification.Builder(context,
+ NotificationChannels.SCREENSHOTS_HEADSUP)
.setTicker(r.getString(R.string.screenshot_saving_ticker)
+ (mTickerAddSpace ? " " : ""))
.setContentTitle(r.getString(R.string.screenshot_saving_title))
@@ -232,6 +226,20 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
mNotificationStyle.bigLargeIcon((Bitmap) null);
}
+ /**
+ * Generates a new hardware bitmap with specified values, copying the content from the passed
+ * in bitmap.
+ */
+ private Bitmap generateAdjustedHwBitmap(Bitmap bitmap, int width, int height, Matrix matrix,
+ Paint paint, int color) {
+ Picture picture = new Picture();
+ Canvas canvas = picture.beginRecording(width, height);
+ canvas.drawColor(color);
+ canvas.drawBitmap(bitmap, matrix, paint);
+ picture.endRecording();
+ return Bitmap.createBitmap(picture);
+ }
+
@Override
protected Void doInBackground(Void... params) {
if (isCancelled()) {
@@ -282,13 +290,15 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
sharingIntent.setType("image/png");
sharingIntent.putExtra(Intent.EXTRA_STREAM, uri);
sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
+ sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- // Create a share action for the notification. Note, we proxy the call to ShareReceiver
- // because RemoteViews currently forces an activity options on the PendingIntent being
- // launched, and since we don't want to trigger the share sheet in this case, we will
- // start the chooser activitiy directly in ShareReceiver.
+ // Create a share action for the notification. Note, we proxy the call to
+ // ScreenshotActionReceiver because RemoteViews currently forces an activity options
+ // on the PendingIntent being launched, and since we don't want to trigger the share
+ // sheet in this case, we start the chooser activity directly in
+ // ScreenshotActionReceiver.
PendingIntent shareAction = PendingIntent.getBroadcast(context, 0,
- new Intent(context, GlobalScreenshot.ShareReceiver.class)
+ new Intent(context, GlobalScreenshot.ScreenshotActionReceiver.class)
.putExtra(SHARING_INTENT, sharingIntent),
PendingIntent.FLAG_CANCEL_CURRENT);
Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder(
@@ -296,15 +306,21 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
r.getString(com.android.internal.R.string.share), shareAction);
mNotificationBuilder.addAction(shareActionBuilder.build());
- // Create a delete action for the notification
- PendingIntent deleteAction = PendingIntent.getBroadcast(context, 0,
- new Intent(context, GlobalScreenshot.DeleteScreenshotReceiver.class)
- .putExtra(GlobalScreenshot.SCREENSHOT_URI_ID, uri.toString()),
- PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
- Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder(
- R.drawable.ic_screenshot_delete,
- r.getString(com.android.internal.R.string.delete), deleteAction);
- mNotificationBuilder.addAction(deleteActionBuilder.build());
+ Intent editIntent = new Intent(Intent.ACTION_EDIT);
+ editIntent.setType("image/png");
+ editIntent.setData(uri);
+ editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ editIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
+ // Create a edit action for the notification the same way.
+ PendingIntent editAction = PendingIntent.getBroadcast(context, 1,
+ new Intent(context, GlobalScreenshot.ScreenshotActionReceiver.class)
+ .putExtra(SHARING_INTENT, editIntent),
+ PendingIntent.FLAG_CANCEL_CURRENT);
+ Notification.Action.Builder editActionBuilder = new Notification.Action.Builder(
+ R.drawable.ic_screenshot_edit,
+ r.getString(com.android.internal.R.string.screenshot_edit), editAction);
+ mNotificationBuilder.addAction(editActionBuilder.build());
mParams.imageUri = uri;
mParams.image = null;
@@ -557,25 +573,14 @@ class GlobalScreenshot {
/**
* Takes a screenshot of the current display and shows an animation.
*/
- void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible,
- int x, int y, int width, int height) {
- // We need to orient the screenshot correctly (and the Surface api seems to take screenshots
- // only in the natural orientation of the device :!)
- mDisplay.getRealMetrics(mDisplayMetrics);
- float[] dims = {mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels};
- float degrees = getDegreesForRotation(mDisplay.getRotation());
- boolean requiresRotation = (degrees > 0);
- if (requiresRotation) {
- // Get the dimensions of the device in its native orientation
- mDisplayMatrix.reset();
- mDisplayMatrix.preRotate(-degrees);
- mDisplayMatrix.mapPoints(dims);
- dims[0] = Math.abs(dims[0]);
- dims[1] = Math.abs(dims[1]);
- }
+ private void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible,
+ Rect crop) {
+ int rot = mDisplay.getRotation();
+ int width = crop.width();
+ int height = crop.height();
// Take the screenshot
- mScreenBitmap = SurfaceControl.screenshot((int) dims[0], (int) dims[1]);
+ mScreenBitmap = SurfaceControl.screenshot(crop, width, height, rot);
if (mScreenBitmap == null) {
notifyScreenshotError(mContext, mNotificationManager,
R.string.screenshot_failed_to_capture_text);
@@ -583,29 +588,6 @@ class GlobalScreenshot {
return;
}
- if (requiresRotation) {
- // Rotate the screenshot to the current orientation
- Bitmap ss = Bitmap.createBitmap(mDisplayMetrics.widthPixels,
- mDisplayMetrics.heightPixels, Bitmap.Config.ARGB_8888,
- mScreenBitmap.hasAlpha(), mScreenBitmap.getColorSpace());
- Canvas c = new Canvas(ss);
- c.translate(ss.getWidth() / 2, ss.getHeight() / 2);
- c.rotate(degrees);
- c.translate(-dims[0] / 2, -dims[1] / 2);
- c.drawBitmap(mScreenBitmap, 0, 0, null);
- c.setBitmap(null);
- // Recycle the previous bitmap
- mScreenBitmap.recycle();
- mScreenBitmap = ss;
- }
-
- if (width != mDisplayMetrics.widthPixels || height != mDisplayMetrics.heightPixels) {
- // Crop the screenshot to selected region
- Bitmap cropped = Bitmap.createBitmap(mScreenBitmap, x, y, width, height);
- mScreenBitmap.recycle();
- mScreenBitmap = cropped;
- }
-
// Optimizations
mScreenBitmap.setHasAlpha(false);
mScreenBitmap.prepareToDraw();
@@ -617,8 +599,8 @@ class GlobalScreenshot {
void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible) {
mDisplay.getRealMetrics(mDisplayMetrics);
- takeScreenshot(finisher, statusBarVisible, navBarVisible, 0, 0, mDisplayMetrics.widthPixels,
- mDisplayMetrics.heightPixels);
+ takeScreenshot(finisher, statusBarVisible, navBarVisible,
+ new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels));
}
/**
@@ -648,7 +630,7 @@ class GlobalScreenshot {
mScreenshotLayout.post(new Runnable() {
public void run() {
takeScreenshot(finisher, statusBarVisible, navBarVisible,
- rect.left, rect.top, rect.width(), rect.height());
+ rect);
}
});
}
@@ -903,9 +885,9 @@ class GlobalScreenshot {
}
/**
- * Receiver to proxy the share intent.
+ * Receiver to proxy the share or edit intent.
*/
- public static class ShareReceiver extends BroadcastReceiver {
+ public static class ScreenshotActionReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
try {
@@ -920,6 +902,7 @@ class GlobalScreenshot {
Intent chooserIntent = Intent.createChooser(sharingIntent, null,
chooseAction.getIntentSender())
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
+
ActivityOptions opts = ActivityOptions.makeBasic();
opts.setDisallowEnterPictureInPictureWhileLaunching(true);
context.startActivityAsUser(chooserIntent, opts.toBundle(), UserHandle.CURRENT);
@@ -927,7 +910,7 @@ class GlobalScreenshot {
}
/**
- * Removes the notification for a screenshot after a share target is chosen.
+ * Removes the notification for a screenshot after a share or edit target is chosen.
*/
public static class TargetChosenReceiver extends BroadcastReceiver {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index f3bae20e544a..34b8bfe59e7e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -67,6 +67,8 @@ public class TakeScreenshotService extends Service {
case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION:
mScreenshot.takeScreenshotPartial(finisher, msg.arg1 > 0, msg.arg2 > 0);
break;
+ default:
+ Log.d(TAG, "Invalid screenshot option: " + msg.what);
}
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java
index d3f997a04500..406eef82f737 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java
@@ -16,9 +16,11 @@
package com.android.systemui.settings;
+import android.animation.ValueAnimator;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
+import android.hardware.display.DisplayManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Handler;
@@ -29,6 +31,7 @@ import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
+import android.os.UserManager;
import android.provider.Settings;
import android.service.vr.IVrManager;
import android.service.vr.IVrStateCallbacks;
@@ -37,6 +40,7 @@ import android.widget.ImageView;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.settingslib.RestrictedLockUtils;
import com.android.systemui.Dependency;
import java.util.ArrayList;
@@ -45,11 +49,7 @@ public class BrightnessController implements ToggleSlider.Listener {
private static final String TAG = "StatusBar.BrightnessController";
private static final boolean SHOW_AUTOMATIC_ICON = false;
- /**
- * {@link android.provider.Settings.System#SCREEN_AUTO_BRIGHTNESS_ADJ} uses the range [-1, 1].
- * Using this factor, it is converted to [0, BRIGHTNESS_ADJ_RESOLUTION] for the SeekBar.
- */
- private static final float BRIGHTNESS_ADJ_RESOLUTION = 2048;
+ private static final int SLIDER_ANIMATION_DURATION = 3000;
private static final int MSG_UPDATE_ICON = 0;
private static final int MSG_UPDATE_SLIDER = 1;
@@ -67,7 +67,7 @@ public class BrightnessController implements ToggleSlider.Listener {
private final ImageView mIcon;
private final ToggleSlider mControl;
private final boolean mAutomaticAvailable;
- private final IPowerManager mPower;
+ private final DisplayManager mDisplayManager;
private final CurrentUserTracker mUserTracker;
private final IVrManager mVrManager;
@@ -81,6 +81,9 @@ public class BrightnessController implements ToggleSlider.Listener {
private volatile boolean mIsVrModeEnabled;
private boolean mListening;
private boolean mExternalChange;
+ private boolean mControlValueInitialized;
+
+ private ValueAnimator mSliderAnimator;
public interface BrightnessStateChangeCallback {
public void onBrightnessLevelChanged();
@@ -95,8 +98,6 @@ public class BrightnessController implements ToggleSlider.Listener {
Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS);
private final Uri BRIGHTNESS_FOR_VR_URI =
Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FOR_VR);
- private final Uri BRIGHTNESS_ADJ_URI =
- Settings.System.getUriFor(Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ);
public BrightnessObserver(Handler handler) {
super(handler);
@@ -114,12 +115,10 @@ public class BrightnessController implements ToggleSlider.Listener {
if (BRIGHTNESS_MODE_URI.equals(uri)) {
mBackgroundHandler.post(mUpdateModeRunnable);
mBackgroundHandler.post(mUpdateSliderRunnable);
- } else if (BRIGHTNESS_URI.equals(uri) && !mAutomatic) {
+ } else if (BRIGHTNESS_URI.equals(uri)) {
mBackgroundHandler.post(mUpdateSliderRunnable);
} else if (BRIGHTNESS_FOR_VR_URI.equals(uri)) {
mBackgroundHandler.post(mUpdateSliderRunnable);
- } else if (BRIGHTNESS_ADJ_URI.equals(uri) && mAutomatic) {
- mBackgroundHandler.post(mUpdateSliderRunnable);
} else {
mBackgroundHandler.post(mUpdateModeRunnable);
mBackgroundHandler.post(mUpdateSliderRunnable);
@@ -141,9 +140,6 @@ public class BrightnessController implements ToggleSlider.Listener {
cr.registerContentObserver(
BRIGHTNESS_FOR_VR_URI,
false, this, UserHandle.USER_ALL);
- cr.registerContentObserver(
- BRIGHTNESS_ADJ_URI,
- false, this, UserHandle.USER_ALL);
}
public void stopObserving() {
@@ -214,12 +210,6 @@ public class BrightnessController implements ToggleSlider.Listener {
mHandler.obtainMessage(MSG_UPDATE_SLIDER,
mMaximumBacklightForVr - mMinimumBacklightForVr,
value - mMinimumBacklightForVr).sendToTarget();
- } else if (mAutomatic) {
- float value = Settings.System.getFloatForUser(mContext.getContentResolver(),
- Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0,
- UserHandle.USER_CURRENT);
- mHandler.obtainMessage(MSG_UPDATE_SLIDER, (int) BRIGHTNESS_ADJ_RESOLUTION,
- (int) ((value + 1) * BRIGHTNESS_ADJ_RESOLUTION / 2f)).sendToTarget();
} else {
int value;
value = Settings.System.getIntForUser(mContext.getContentResolver(),
@@ -250,7 +240,7 @@ public class BrightnessController implements ToggleSlider.Listener {
break;
case MSG_UPDATE_SLIDER:
mControl.setMax(msg.arg1);
- mControl.setValue(msg.arg2);
+ animateSliderTo(msg.arg2);
break;
case MSG_SET_CHECKED:
mControl.setChecked(msg.arg1 != 0);
@@ -295,8 +285,7 @@ public class BrightnessController implements ToggleSlider.Listener {
mAutomaticAvailable = context.getResources().getBoolean(
com.android.internal.R.bool.config_automatic_brightness_available);
- mPower = IPowerManager.Stub.asInterface(ServiceManager.getService(
- Context.POWER_SERVICE));
+ mDisplayManager = context.getSystemService(DisplayManager.class);
mVrManager = IVrManager.Stub.asInterface(ServiceManager.getService(
Context.VR_SERVICE));
}
@@ -348,6 +337,7 @@ public class BrightnessController implements ToggleSlider.Listener {
mBackgroundHandler.post(mStopListeningRunnable);
mListening = false;
+ mControlValueInitialized = false;
}
@Override
@@ -356,6 +346,10 @@ public class BrightnessController implements ToggleSlider.Listener {
updateIcon(mAutomatic);
if (mExternalChange) return;
+ if (mSliderAnimator != null) {
+ mSliderAnimator.cancel();
+ }
+
if (mIsVrModeEnabled) {
final int val = value + mMinimumBacklightForVr;
if (stopTracking) {
@@ -371,7 +365,7 @@ public class BrightnessController implements ToggleSlider.Listener {
}
});
}
- } else if (!mAutomatic) {
+ } else {
final int val = value + mMinimumBacklight;
if (stopTracking) {
MetricsLogger.action(mContext, MetricsEvent.ACTION_BRIGHTNESS, val);
@@ -386,21 +380,6 @@ public class BrightnessController implements ToggleSlider.Listener {
}
});
}
- } else {
- final float adj = value / (BRIGHTNESS_ADJ_RESOLUTION / 2f) - 1;
- if (stopTracking) {
- MetricsLogger.action(mContext, MetricsEvent.ACTION_BRIGHTNESS_AUTO, value);
- }
- setBrightnessAdj(adj);
- if (!tracking) {
- AsyncTask.execute(new Runnable() {
- public void run() {
- Settings.System.putFloatForUser(mContext.getContentResolver(),
- Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, adj,
- UserHandle.USER_CURRENT);
- }
- });
- }
}
for (BrightnessStateChangeCallback cb : mChangeCallbacks) {
@@ -408,6 +387,18 @@ public class BrightnessController implements ToggleSlider.Listener {
}
}
+ public void checkRestrictionAndSetEnabled() {
+ mBackgroundHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ ((ToggleSliderView)mControl).setEnforcedAdmin(
+ RestrictedLockUtils.checkIfRestrictionEnforced(mContext,
+ UserManager.DISALLOW_CONFIG_BRIGHTNESS,
+ mUserTracker.getCurrentUserId()));
+ }
+ });
+ }
+
private void setMode(int mode) {
Settings.System.putIntForUser(mContext.getContentResolver(),
Settings.System.SCREEN_BRIGHTNESS_MODE, mode,
@@ -415,17 +406,11 @@ public class BrightnessController implements ToggleSlider.Listener {
}
private void setBrightness(int brightness) {
- try {
- mPower.setTemporaryScreenBrightnessSettingOverride(brightness);
- } catch (RemoteException ex) {
- }
+ mDisplayManager.setTemporaryBrightness(brightness);
}
private void setBrightnessAdj(float adj) {
- try {
- mPower.setTemporaryScreenAutoBrightnessAdjustmentSettingOverride(adj);
- } catch (RemoteException ex) {
- }
+ mDisplayManager.setTemporaryAutoBrightnessAdjustment(adj);
}
private void updateIcon(boolean automatic) {
@@ -442,4 +427,23 @@ public class BrightnessController implements ToggleSlider.Listener {
mBackgroundHandler.post(mUpdateSliderRunnable);
}
}
+
+ private void animateSliderTo(int target) {
+ if (!mControlValueInitialized) {
+ // Don't animate the first value since it's default state isn't meaningful to users.
+ mControl.setValue(target);
+ mControlValueInitialized = true;
+ }
+ if (mSliderAnimator != null && mSliderAnimator.isStarted()) {
+ mSliderAnimator.cancel();
+ }
+ mSliderAnimator = ValueAnimator.ofInt(mControl.getValue(), target);
+ mSliderAnimator.addUpdateListener((ValueAnimator animation) -> {
+ mExternalChange = true;
+ mControl.setValue((int)animation.getAnimatedValue());
+ mExternalChange = false;
+ });
+ mSliderAnimator.setDuration(SLIDER_ANIMATION_DURATION);
+ mSliderAnimator.start();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/ToggleSeekBar.java b/packages/SystemUI/src/com/android/systemui/settings/ToggleSeekBar.java
index 722aba591ea8..8ed4c75e8673 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/ToggleSeekBar.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/ToggleSeekBar.java
@@ -17,14 +17,21 @@
package com.android.systemui.settings;
import android.content.Context;
+import android.content.Intent;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.SeekBar;
+import com.android.settingslib.RestrictedLockUtils;
+import com.android.systemui.Dependency;
+import com.android.systemui.plugins.ActivityStarter;
+
public class ToggleSeekBar extends SeekBar {
private String mAccessibilityLabel;
+ private RestrictedLockUtils.EnforcedAdmin mEnforcedAdmin = null;
+
public ToggleSeekBar(Context context) {
super(context);
}
@@ -39,6 +46,12 @@ public class ToggleSeekBar extends SeekBar {
@Override
public boolean onTouchEvent(MotionEvent event) {
+ if (mEnforcedAdmin != null) {
+ Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(
+ mContext, mEnforcedAdmin);
+ Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(intent, 0);
+ return true;
+ }
if (!isEnabled()) {
setEnabled(true);
}
@@ -57,4 +70,8 @@ public class ToggleSeekBar extends SeekBar {
info.setText(mAccessibilityLabel);
}
}
+
+ public void setEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin) {
+ mEnforcedAdmin = admin;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/ToggleSlider.java b/packages/SystemUI/src/com/android/systemui/settings/ToggleSlider.java
index 62abf3d6c3e2..135f89d3f343 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/ToggleSlider.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/ToggleSlider.java
@@ -28,4 +28,5 @@ public interface ToggleSlider {
default boolean isChecked() { return false; }
void setMax(int max);
void setValue(int value);
+ int getValue();
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/ToggleSliderView.java b/packages/SystemUI/src/com/android/systemui/settings/ToggleSliderView.java
index 5b234e9c5576..90744a62677d 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/ToggleSliderView.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/ToggleSliderView.java
@@ -29,6 +29,7 @@ import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
+import com.android.settingslib.RestrictedLockUtils;
import com.android.systemui.R;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
@@ -95,6 +96,12 @@ public class ToggleSliderView extends RelativeLayout implements ToggleSlider {
}
}
+ public void setEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin) {
+ mToggle.setEnabled(admin == null);
+ mSlider.setEnabled(admin == null);
+ mSlider.setEnforcedAdmin(admin);
+ }
+
public void setOnChangedListener(Listener l) {
mListener = l;
}
@@ -126,6 +133,11 @@ public class ToggleSliderView extends RelativeLayout implements ToggleSlider {
}
@Override
+ public int getValue() {
+ return mSlider.getProgress();
+ }
+
+ @Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mMirror != null) {
MotionEvent copy = ev.copy();
diff --git a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
index 7699bb90e611..da798848f7e8 100644
--- a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
@@ -16,37 +16,31 @@
package com.android.systemui.shortcut;
-import android.accessibilityservice.AccessibilityServiceInfo;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
+import static android.os.UserHandle.USER_CURRENT;
+
+import static com.android.systemui.statusbar.phone.NavigationBarGestureHelper.DRAG_MODE_NONE;
+
import android.app.ActivityManager;
-import android.app.IActivityManager;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.ServiceInfo;
import android.content.res.Configuration;
import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.ArraySet;
-import android.util.DisplayMetrics;
import android.util.Log;
import android.view.IWindowManager;
import android.view.KeyEvent;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
-import android.view.accessibility.AccessibilityManager;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
import com.android.internal.policy.DividerSnapAlgorithm;
-import com.android.settingslib.accessibility.AccessibilityUtils;
-import com.android.systemui.R;
import com.android.systemui.SystemUI;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.stackdivider.Divider;
import com.android.systemui.stackdivider.DividerView;
import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
import java.util.List;
-import java.util.Set;
/**
* Dispatches shortcut to System UI components
@@ -58,7 +52,6 @@ public class ShortcutKeyDispatcher extends SystemUI
private ShortcutKeyServiceProxy mShortcutKeyServiceProxy = new ShortcutKeyServiceProxy(this);
private IWindowManager mWindowManagerService = WindowManagerGlobal.getWindowManagerService();
- private IActivityManager mActivityManager = ActivityManager.getService();
protected final long META_MASK = ((long) KeyEvent.META_META_ON) << Integer.SIZE;
protected final long ALT_MASK = ((long) KeyEvent.META_ALT_ON) << Integer.SIZE;
@@ -99,21 +92,11 @@ public class ShortcutKeyDispatcher extends SystemUI
try {
int dockSide = mWindowManagerService.getDockedStackSide();
if (dockSide == WindowManager.DOCKED_INVALID) {
- // If there is no window docked, we dock the top-most window.
+ // Split the screen
Recents recents = getComponent(Recents.class);
- int dockMode = (shortcutCode == SC_DOCK_LEFT)
- ? ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT
- : ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
- List<ActivityManager.RecentTaskInfo> taskList =
- SystemServicesProxy.getInstance(mContext).getRecentTasks(1,
- UserHandle.USER_CURRENT, false, new ArraySet<>());
- recents.showRecentApps(
- false /* triggeredFromAltTab */,
- false /* fromHome */);
- if (!taskList.isEmpty()) {
- SystemServicesProxy.getInstance(mContext).startTaskInDockedMode(
- taskList.get(0).id, dockMode);
- }
+ recents.splitPrimaryTask(DRAG_MODE_NONE, (shortcutCode == SC_DOCK_LEFT)
+ ? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT
+ : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT, null, -1);
} else {
// If there is already a docked window, we respond by resizing the docking pane.
DividerView dividerView = getComponent(Divider.class).getView();
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index 23a7dae34cfb..1596d120c16f 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -16,6 +16,9 @@
package com.android.systemui.stackdivider;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
@@ -23,7 +26,6 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.Nullable;
-import android.app.ActivityManager.StackId;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
@@ -36,8 +38,6 @@ import android.util.AttributeSet;
import android.view.Choreographer;
import android.view.Display;
import android.view.DisplayInfo;
-import android.view.GestureDetector;
-import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
import android.view.PointerIcon;
import android.view.VelocityTracker;
@@ -62,7 +62,6 @@ import com.android.internal.policy.DockedDividerUtils;
import com.android.internal.view.SurfaceFlingerVsyncChoreographer;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.recents.Recents;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent;
import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
@@ -94,7 +93,6 @@ public class DividerView extends FrameLayout implements OnTouchListener,
private static final int LOG_VALUE_UNDOCK_MAX_OTHER = 1;
private static final int TASK_POSITION_SAME = Integer.MAX_VALUE;
- private static final boolean SWAPPING_ENABLED = false;
/**
* How much the background gets scaled when we are in the minimized dock state.
@@ -154,7 +152,6 @@ public class DividerView extends FrameLayout implements OnTouchListener,
private boolean mEntranceAnimationRunning;
private boolean mExitAnimationRunning;
private int mExitStartPosition;
- private GestureDetector mGestureDetector;
private boolean mDockedStackMinimized;
private boolean mHomeStackResizable;
private boolean mAdjustedForIme;
@@ -296,21 +293,6 @@ public class DividerView extends FrameLayout implements OnTouchListener,
landscape ? TYPE_HORIZONTAL_DOUBLE_ARROW : TYPE_VERTICAL_DOUBLE_ARROW));
getViewTreeObserver().addOnComputeInternalInsetsListener(this);
mHandle.setAccessibilityDelegate(mHandleDelegate);
- mGestureDetector = new GestureDetector(mContext, new SimpleOnGestureListener() {
- @Override
- public boolean onSingleTapUp(MotionEvent e) {
- if (SWAPPING_ENABLED) {
- updateDockSide();
- SystemServicesProxy ssp = Recents.getSystemServices();
- if (mDockSide != WindowManager.DOCKED_INVALID
- && !ssp.isRecentsActivityVisible()) {
- mWindowManagerProxy.swapTasks();
- return true;
- }
- }
- return false;
- }
- });
}
@Override
@@ -457,7 +439,7 @@ public class DividerView extends FrameLayout implements OnTouchListener,
if (mMinimizedSnapAlgorithm == null) {
mMinimizedSnapAlgorithm = new DividerSnapAlgorithm(getContext().getResources(),
mDisplayWidth, mDisplayHeight, mDividerSize, isHorizontalDivision(),
- mStableInsets, mDockedStackMinimized && mHomeStackResizable);
+ mStableInsets, mDockSide, mDockedStackMinimized && mHomeStackResizable);
}
}
@@ -479,7 +461,6 @@ public class DividerView extends FrameLayout implements OnTouchListener,
@Override
public boolean onTouch(View v, MotionEvent event) {
convertToScreenCoordinates(event);
- mGestureDetector.onTouchEvent(event);
final int action = event.getAction() & MotionEvent.ACTION_MASK;
switch (action) {
case MotionEvent.ACTION_DOWN:
@@ -680,7 +661,7 @@ public class DividerView extends FrameLayout implements OnTouchListener,
} else {
mWindowManagerProxy.maximizeDockedStack();
}
- mWindowManagerProxy.setResizeDimLayer(false, -1, 0f);
+ mWindowManagerProxy.setResizeDimLayer(false, WINDOWING_MODE_UNDEFINED, 0f);
}
private void liftBackground() {
@@ -1016,8 +997,7 @@ public class DividerView extends FrameLayout implements OnTouchListener,
SnapTarget closestDismissTarget = getSnapAlgorithm().getClosestDismissTarget(position);
float dimFraction = getDimFraction(position, closestDismissTarget);
mWindowManagerProxy.setResizeDimLayer(dimFraction != 0f,
- getStackIdForDismissTarget(closestDismissTarget),
- dimFraction);
+ getWindowingModeForDismissTarget(closestDismissTarget), dimFraction);
}
private void applyExitAnimationParallax(Rect taskRect, int position) {
@@ -1151,13 +1131,13 @@ public class DividerView extends FrameLayout implements OnTouchListener,
}
}
- private int getStackIdForDismissTarget(SnapTarget dismissTarget) {
+ private int getWindowingModeForDismissTarget(SnapTarget dismissTarget) {
if ((dismissTarget.flag == SnapTarget.FLAG_DISMISS_START && dockSideTopLeft(mDockSide))
|| (dismissTarget.flag == SnapTarget.FLAG_DISMISS_END
&& dockSideBottomRight(mDockSide))) {
- return StackId.DOCKED_STACK_ID;
+ return WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
} else {
- return StackId.RECENTS_STACK_ID;
+ return WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java b/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java
index 578a18a09c6c..826fa6cefccc 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java
@@ -31,8 +31,9 @@ import com.android.systemui.R;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.AppTransitionFinishedEvent;
import com.android.systemui.recents.events.component.ShowUserToastEvent;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.stackdivider.events.StartedDragingEvent;
import com.android.systemui.stackdivider.events.StoppedDragingEvent;
@@ -75,8 +76,8 @@ public class ForcedResizableInfoActivityController {
public ForcedResizableInfoActivityController(Context context) {
mContext = context;
EventBus.getDefault().register(this);
- SystemServicesProxy.getInstance(context).registerTaskStackListener(
- new TaskStackListener() {
+ ActivityManagerWrapper.getInstance().registerTaskStackListener(
+ new SysUiTaskStackChangeListener() {
@Override
public void onActivityForcedResizable(String packageName, int taskId,
int reason) {
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
index c24512649c2f..85a60624c275 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
@@ -16,7 +16,6 @@
package com.android.systemui.stackdivider;
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
import static android.view.WindowManager.DOCKED_INVALID;
import android.app.ActivityManager;
@@ -56,7 +55,7 @@ public class WindowManagerProxy {
private final Rect mTouchableRegion = new Rect();
private boolean mDimLayerVisible;
- private int mDimLayerTargetStack;
+ private int mDimLayerTargetWindowingMode;
private float mDimLayerAlpha;
private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
@@ -88,8 +87,7 @@ public class WindowManagerProxy {
@Override
public void run() {
try {
- ActivityManager.getService().moveTasksToFullscreenStack(
- DOCKED_STACK_ID, false /* onTop */);
+ ActivityManager.getService().dismissSplitScreenMode(false /* onTop */);
} catch (RemoteException e) {
Log.w(TAG, "Failed to remove stack: " + e);
}
@@ -100,8 +98,7 @@ public class WindowManagerProxy {
@Override
public void run() {
try {
- ActivityManager.getService().resizeStack(
- DOCKED_STACK_ID, null, true, true, false, -1);
+ ActivityManager.getService().dismissSplitScreenMode(true /* onTop */);
} catch (RemoteException e) {
Log.w(TAG, "Failed to resize stack: " + e);
}
@@ -113,18 +110,7 @@ public class WindowManagerProxy {
public void run() {
try {
WindowManagerGlobal.getWindowManagerService().setResizeDimLayer(mDimLayerVisible,
- mDimLayerTargetStack, mDimLayerAlpha);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to resize stack: " + e);
- }
- }
- };
-
- private final Runnable mSwapRunnable = new Runnable() {
- @Override
- public void run() {
- try {
- ActivityManager.getService().swapDockedAndFullscreenStack();
+ mDimLayerTargetWindowingMode, mDimLayerAlpha);
} catch (RemoteException e) {
Log.w(TAG, "Failed to resize stack: " + e);
}
@@ -211,17 +197,13 @@ public class WindowManagerProxy {
return DOCKED_INVALID;
}
- public void setResizeDimLayer(boolean visible, int targetStackId, float alpha) {
+ public void setResizeDimLayer(boolean visible, int targetWindowingMode, float alpha) {
mDimLayerVisible = visible;
- mDimLayerTargetStack = targetStackId;
+ mDimLayerTargetWindowingMode = targetWindowingMode;
mDimLayerAlpha = alpha;
mExecutor.execute(mDimLayerRunnable);
}
- public void swapTasks() {
- mExecutor.execute(mSwapRunnable);
- }
-
public void setTouchRegion(Rect region) {
synchronized (mDockedRect) {
mTouchableRegion.set(region);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index 68fe9a83c707..eb5619b11487 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -29,7 +29,6 @@ import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewAnimationUtils;
-import android.view.ViewConfiguration;
import android.view.accessibility.AccessibilityManager;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
@@ -123,7 +122,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
private Interpolator mCurrentAppearInterpolator;
private Interpolator mCurrentAlphaInterpolator;
- private NotificationBackgroundView mBackgroundNormal;
+ protected NotificationBackgroundView mBackgroundNormal;
private NotificationBackgroundView mBackgroundDimmed;
private ObjectAnimator mBackgroundAnimator;
private RectF mAppearAnimationRect = new RectF();
@@ -173,12 +172,12 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
private int mOverrideTint;
private float mOverrideAmount;
private boolean mShadowHidden;
- private boolean mWasActivatedOnDown;
/**
* Similar to mDimmed but is also true if it's not dimmable but should be
*/
private boolean mNeedsDimming;
private int mDimmedAlpha;
+ private boolean mBlockNextTouch;
public ActivatableNotificationView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -204,7 +203,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
} else {
makeInactive(true /* animate */);
}
- }, this::performClick, this::handleSlideBack, mFalsingManager::onNotificationDoubleTap);
+ }, super::performClick, this::handleSlideBack, mFalsingManager::onNotificationDoubleTap);
}
@Override
@@ -241,9 +240,15 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
- if (mNeedsDimming && !mActivated && ev.getActionMasked() == MotionEvent.ACTION_DOWN
+ if (mNeedsDimming && ev.getActionMasked() == MotionEvent.ACTION_DOWN
&& disallowSingleClick(ev) && !isTouchExplorationEnabled()) {
- return true;
+ if (!mActivated) {
+ return true;
+ } else if (!mDoubleTapHelper.isWithinDoubleTapSlop(ev)) {
+ mBlockNextTouch = true;
+ makeInactive(true /* animate */);
+ return true;
+ }
}
return super.onInterceptTouchEvent(ev);
}
@@ -263,10 +268,11 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean result;
- if (event.getAction() == MotionEvent.ACTION_DOWN) {
- mWasActivatedOnDown = mActivated;
+ if (mBlockNextTouch) {
+ mBlockNextTouch = false;
+ return false;
}
- if ((mNeedsDimming && !mActivated) && !isTouchExplorationEnabled() && isInteractive()) {
+ if (mNeedsDimming && !isTouchExplorationEnabled() && isInteractive()) {
boolean wasActivated = mActivated;
result = handleTouchEventDimmed(event);
if (wasActivated && result && event.getAction() == MotionEvent.ACTION_UP) {
@@ -312,7 +318,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
@Override
public boolean performClick() {
- if (mWasActivatedOnDown || !mNeedsDimming || isTouchExplorationEnabled()) {
+ if (!mNeedsDimming || isTouchExplorationEnabled()) {
return super.performClick();
}
return false;
@@ -689,6 +695,11 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
mBackgroundNormal.getVisibility() == View.VISIBLE ? 1.0f : 0.0f);
}
+ protected void updateBackgroundClipping() {
+ mBackgroundNormal.setBottomAmountClips(!isChildInGroup());
+ mBackgroundDimmed.setBottomAmountClips(!isChildInGroup());
+ }
+
protected boolean shouldHideBackground() {
return mDark;
}
@@ -895,12 +906,38 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
contentView.setAlpha(contentAlpha);
}
+ @Override
+ protected void applyRoundness() {
+ super.applyRoundness();
+ applyBackgroundRoundness(getCurrentBackgroundRadiusTop(),
+ getCurrentBackgroundRadiusBottom());
+ }
+
+ protected void applyBackgroundRoundness(float topRadius, float bottomRadius) {
+ mBackgroundDimmed.setRoundness(topRadius, bottomRadius);
+ mBackgroundNormal.setRoundness(topRadius, bottomRadius);
+ }
+
+ @Override
+ protected void setBackgroundTop(int backgroundTop) {
+ mBackgroundDimmed.setBackgroundTop(backgroundTop);
+ mBackgroundNormal.setBackgroundTop(backgroundTop);
+ }
+
protected abstract View getContentView();
public int calculateBgColor() {
return calculateBgColor(true /* withTint */, true /* withOverRide */);
}
+ @Override
+ protected boolean childNeedsClipping(View child) {
+ if (child instanceof NotificationBackgroundView && isClippingNeeded()) {
+ return true;
+ }
+ return super.childNeedsClipping(child);
+ }
+
/**
* @param withTint should a possible tint be factored in?
* @param withOverRide should the value be interpolated with {@link #mOverrideTint}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 634927503747..fa177f2bb26c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -18,11 +18,13 @@ package com.android.systemui.statusbar;
import android.content.ComponentName;
import android.graphics.Rect;
+import android.hardware.fingerprint.IFingerprintDialogReceiver;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.RemoteException;
import android.support.annotation.VisibleForTesting;
import android.util.Pair;
@@ -82,6 +84,15 @@ public class CommandQueue extends IStatusBar.Stub {
private static final int MSG_TOGGLE_PANEL = 35 << MSG_SHIFT;
private static final int MSG_SHOW_SHUTDOWN_UI = 36 << MSG_SHIFT;
private static final int MSG_SET_TOP_APP_HIDES_STATUS_BAR = 37 << MSG_SHIFT;
+ private static final int MSG_ROTATION_PROPOSAL = 38 << MSG_SHIFT;
+ private static final int MSG_FINGERPRINT_SHOW = 39 << MSG_SHIFT;
+ private static final int MSG_FINGERPRINT_AUTHENTICATED = 40 << MSG_SHIFT;
+ private static final int MSG_FINGERPRINT_HELP = 41 << MSG_SHIFT;
+ private static final int MSG_FINGERPRINT_ERROR = 42 << MSG_SHIFT;
+ private static final int MSG_FINGERPRINT_HIDE = 43 << MSG_SHIFT;
+ private static final int MSG_SHOW_CHARGING_ANIMATION = 44 << MSG_SHIFT;
+ private static final int MSG_SHOW_PINNING_TOAST_ENTER_EXIT = 45 << MSG_SHIFT;
+ private static final int MSG_SHOW_PINNING_TOAST_ESCAPE = 46 << MSG_SHIFT;
public static final int FLAG_EXCLUDE_NONE = 0;
public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -115,7 +126,7 @@ public class CommandQueue extends IStatusBar.Stub {
default void topAppWindowChanged(boolean visible) { }
default void setImeWindowStatus(IBinder token, int vis, int backDisposition,
boolean showImeSwitcher) { }
- default void showRecentApps(boolean triggeredFromAltTab, boolean fromHome) { }
+ default void showRecentApps(boolean triggeredFromAltTab) { }
default void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) { }
default void toggleRecentApps() { }
default void toggleSplitScreen() { }
@@ -140,8 +151,20 @@ public class CommandQueue extends IStatusBar.Stub {
default void clickTile(ComponentName tile) { }
default void handleSystemKey(int arg1) { }
+ default void showPinningEnterExitToast(boolean entering) { }
+ default void showPinningEscapeToast() { }
default void handleShowGlobalActionsMenu() { }
default void handleShowShutdownUi(boolean isReboot, String reason) { }
+
+ default void showWirelessChargingAnimation(int batteryLevel) { }
+
+ default void onRotationProposal(int rotation, boolean isValid) { }
+
+ default void showFingerprintDialog(Bundle bundle, IFingerprintDialogReceiver receiver) { }
+ default void onFingerprintAuthenticated() { }
+ default void onFingerprintHelp(String message) { }
+ default void onFingerprintError(String error) { }
+ default void hideFingerprintDialog() { }
}
@VisibleForTesting
@@ -265,11 +288,11 @@ public class CommandQueue extends IStatusBar.Stub {
}
}
- public void showRecentApps(boolean triggeredFromAltTab, boolean fromHome) {
+ public void showRecentApps(boolean triggeredFromAltTab) {
synchronized (mLock) {
mHandler.removeMessages(MSG_SHOW_RECENT_APPS);
- mHandler.obtainMessage(MSG_SHOW_RECENT_APPS,
- triggeredFromAltTab ? 1 : 0, fromHome ? 1 : 0, null).sendToTarget();
+ mHandler.obtainMessage(MSG_SHOW_RECENT_APPS, triggeredFromAltTab ? 1 : 0, 0,
+ null).sendToTarget();
}
}
@@ -435,6 +458,21 @@ public class CommandQueue extends IStatusBar.Stub {
}
@Override
+ public void showPinningEnterExitToast(boolean entering) {
+ synchronized (mLock) {
+ mHandler.obtainMessage(MSG_SHOW_PINNING_TOAST_ENTER_EXIT, entering).sendToTarget();
+ }
+ }
+
+ @Override
+ public void showPinningEscapeToast() {
+ synchronized (mLock) {
+ mHandler.obtainMessage(MSG_SHOW_PINNING_TOAST_ESCAPE).sendToTarget();
+ }
+ }
+
+
+ @Override
public void showGlobalActionsMenu() {
synchronized (mLock) {
mHandler.removeMessages(MSG_SHOW_GLOBAL_ACTIONS);
@@ -458,6 +496,61 @@ public class CommandQueue extends IStatusBar.Stub {
}
}
+ @Override
+ public void showWirelessChargingAnimation(int batteryLevel) {
+ mHandler.removeMessages(MSG_SHOW_CHARGING_ANIMATION);
+ mHandler.obtainMessage(MSG_SHOW_CHARGING_ANIMATION, batteryLevel, 0)
+ .sendToTarget();
+ }
+
+ @Override
+ public void onProposedRotationChanged(int rotation, boolean isValid) {
+ synchronized (mLock) {
+ mHandler.removeMessages(MSG_ROTATION_PROPOSAL);
+ mHandler.obtainMessage(MSG_ROTATION_PROPOSAL, rotation, isValid ? 1 : 0,
+ null).sendToTarget();
+ }
+ }
+
+ @Override
+ public void showFingerprintDialog(Bundle bundle, IFingerprintDialogReceiver receiver) {
+ synchronized (mLock) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = bundle;
+ args.arg2 = receiver;
+ mHandler.obtainMessage(MSG_FINGERPRINT_SHOW, args)
+ .sendToTarget();
+ }
+ }
+
+ @Override
+ public void onFingerprintAuthenticated() {
+ synchronized (mLock) {
+ mHandler.obtainMessage(MSG_FINGERPRINT_AUTHENTICATED).sendToTarget();
+ }
+ }
+
+ @Override
+ public void onFingerprintHelp(String message) {
+ synchronized (mLock) {
+ mHandler.obtainMessage(MSG_FINGERPRINT_HELP, message).sendToTarget();
+ }
+ }
+
+ @Override
+ public void onFingerprintError(String error) {
+ synchronized (mLock) {
+ mHandler.obtainMessage(MSG_FINGERPRINT_ERROR, error).sendToTarget();
+ }
+ }
+
+ @Override
+ public void hideFingerprintDialog() {
+ synchronized (mLock) {
+ mHandler.obtainMessage(MSG_FINGERPRINT_HIDE).sendToTarget();
+ }
+ }
+
private final class H extends Handler {
private H(Looper l) {
super(l);
@@ -529,7 +622,7 @@ public class CommandQueue extends IStatusBar.Stub {
break;
case MSG_SHOW_RECENT_APPS:
for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).showRecentApps(msg.arg1 != 0, msg.arg2 != 0);
+ mCallbacks.get(i).showRecentApps(msg.arg1 != 0);
}
break;
case MSG_HIDE_RECENT_APPS:
@@ -654,6 +747,56 @@ public class CommandQueue extends IStatusBar.Stub {
mCallbacks.get(i).setTopAppHidesStatusBar(msg.arg1 != 0);
}
break;
+ case MSG_ROTATION_PROPOSAL:
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).onRotationProposal(msg.arg1, msg.arg2 != 0);
+ }
+ break;
+ case MSG_FINGERPRINT_SHOW:
+ mHandler.removeMessages(MSG_FINGERPRINT_ERROR);
+ mHandler.removeMessages(MSG_FINGERPRINT_HELP);
+ mHandler.removeMessages(MSG_FINGERPRINT_AUTHENTICATED);
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).showFingerprintDialog(
+ (Bundle)((SomeArgs)msg.obj).arg1,
+ (IFingerprintDialogReceiver)((SomeArgs)msg.obj).arg2);
+ }
+ break;
+ case MSG_FINGERPRINT_AUTHENTICATED:
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).onFingerprintAuthenticated();
+ }
+ break;
+ case MSG_FINGERPRINT_HELP:
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).onFingerprintHelp((String) msg.obj);
+ }
+ break;
+ case MSG_FINGERPRINT_ERROR:
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).onFingerprintError((String) msg.obj);
+ }
+ break;
+ case MSG_FINGERPRINT_HIDE:
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).hideFingerprintDialog();
+ }
+ break;
+ case MSG_SHOW_CHARGING_ANIMATION:
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).showWirelessChargingAnimation(msg.arg1);
+ }
+ break;
+ case MSG_SHOW_PINNING_TOAST_ENTER_EXIT:
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).showPinningEnterExitToast((Boolean) msg.obj);
+ }
+ break;
+ case MSG_SHOW_PINNING_TOAST_ESCAPE:
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).showPinningEscapeToast();
+ }
+ break;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 966e78997244..b3f68d357083 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar;
+import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
import static com.android.systemui.statusbar.notification.NotificationInflater.InflationCallback;
import android.animation.Animator;
@@ -26,16 +27,20 @@ import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.Configuration;
+import android.graphics.Path;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
+import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.util.AttributeSet;
import android.util.FloatProperty;
+import android.util.MathUtils;
import android.util.Property;
+import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.NotificationHeaderView;
@@ -64,14 +69,16 @@ import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
import com.android.systemui.statusbar.NotificationGuts.GutsContent;
import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
-import com.android.systemui.statusbar.notification.AboveShelfObserver;
+import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
import com.android.systemui.statusbar.notification.HybridNotificationView;
import com.android.systemui.statusbar.notification.NotificationInflater;
import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.notification.NotificationViewWrapper;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.stack.AmbientState;
import com.android.systemui.statusbar.stack.AnimationProperties;
import com.android.systemui.statusbar.stack.ExpandableViewState;
import com.android.systemui.statusbar.stack.NotificationChildrenContainer;
@@ -100,7 +107,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private int mIconTransformContentShift;
private int mIconTransformContentShiftNoIcon;
private int mNotificationMinHeightLegacy;
+ private int mNotificationMinHeightBeforeP;
private int mMaxHeadsUpHeightLegacy;
+ private int mMaxHeadsUpHeightBeforeP;
private int mMaxHeadsUpHeight;
private int mMaxHeadsUpHeightIncreased;
private int mNotificationMinHeight;
@@ -108,6 +117,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private int mNotificationMaxHeight;
private int mNotificationAmbientHeight;
private int mIncreasedPaddingBetweenElements;
+ private int mNotificationLaunchHeight;
+ private boolean mMustStayOnScreen;
/** Does this row contain layouts that can adapt to row expansion */
private boolean mExpandable;
@@ -166,14 +177,21 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private boolean mIsSystemChildExpanded;
private boolean mIsPinned;
private FalsingManager mFalsingManager;
+ private boolean mExpandAnimationRunning;
private AboveShelfChangedListener mAboveShelfChangedListener;
private HeadsUpManager mHeadsUpManager;
+ private View mHelperButton;
private boolean mJustClicked;
private boolean mIconAnimationRunning;
private boolean mShowNoBackground;
private ExpandableNotificationRow mNotificationParent;
private OnExpandClickListener mOnExpandClickListener;
+
+ // Listener will be called when receiving a long click event.
+ // Use #setLongPressPosition to optionally assign positional data with the long press.
+ private LongPressListener mLongPressListener;
+
private boolean mGroupExpansionChanging;
/**
@@ -196,7 +214,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private OnClickListener mExpandClickListener = new OnClickListener() {
@Override
public void onClick(View v) {
- if (!mShowingPublic && (!mIsLowPriority || isExpanded())
+ if (!shouldShowPublic() && (!mIsLowPriority || isExpanded())
&& mGroupManager.isSummaryOfGroup(mStatusBarNotification)) {
mGroupExpansionChanging = true;
final boolean wasExpanded = mGroupManager.isGroupExpanded(mStatusBarNotification);
@@ -258,6 +276,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private float mTranslationWhenRemoved;
private boolean mWasChildInGroupWhenRemoved;
private int mNotificationColorAmbient;
+ private NotificationViewState mNotificationViewState;
@Override
public boolean isGroupExpansionChanging() {
@@ -351,6 +370,14 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mNotificationInflater.inflateNotificationViews();
}
+ @Override
+ public void setPressed(boolean pressed) {
+ if (isOnKeyguard() || mEntry.notification.getNotification().contentIntent == null) {
+ // We're dropping the ripple if we have a collapse / launch animation
+ super.setPressed(pressed);
+ }
+ }
+
public void onNotificationUpdated() {
for (NotificationContentView l : mLayouts) {
l.onNotificationUpdated(mEntry);
@@ -377,6 +404,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
updateLimits();
updateIconVisibilities();
updateShelfIconColor();
+
+ showBlockingHelper(mEntry.userSentiment ==
+ NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE);
}
@VisibleForTesting
@@ -428,9 +458,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
boolean customView = layout.getContractedChild().getId()
!= com.android.internal.R.id.status_bar_latest_event_content;
boolean beforeN = mEntry.targetSdk < Build.VERSION_CODES.N;
+ boolean beforeP = mEntry.targetSdk < Build.VERSION_CODES.P;
int minHeight;
- if (customView && beforeN && !mIsSummaryWithChildren) {
- minHeight = mNotificationMinHeightLegacy;
+ if (customView && beforeP && !mIsSummaryWithChildren) {
+ minHeight = beforeN ? mNotificationMinHeightLegacy : mNotificationMinHeightBeforeP;
} else if (mUseIncreasedCollapsedHeight && layout == mPrivateLayout) {
minHeight = mNotificationMinHeightLarge;
} else {
@@ -440,13 +471,18 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
layout.getHeadsUpChild().getId()
!= com.android.internal.R.id.status_bar_latest_event_content;
int headsUpheight;
- if (headsUpCustom && beforeN) {
- headsUpheight = mMaxHeadsUpHeightLegacy;
+ if (headsUpCustom && beforeP) {
+ headsUpheight = beforeN ? mMaxHeadsUpHeightLegacy : mMaxHeadsUpHeightBeforeP;
} else if (mUseIncreasedHeadsUpHeight && layout == mPrivateLayout) {
headsUpheight = mMaxHeadsUpHeightIncreased;
} else {
headsUpheight = mMaxHeadsUpHeight;
}
+ NotificationViewWrapper headsUpWrapper = layout.getVisibleWrapper(
+ NotificationContentView.VISIBLE_TYPE_HEADSUP);
+ if (headsUpWrapper != null) {
+ headsUpheight = Math.max(headsUpheight, headsUpWrapper.getMinLayoutHeight());
+ }
layout.setHeights(minHeight, headsUpheight, mNotificationMaxHeight,
mNotificationAmbientHeight);
}
@@ -476,6 +512,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
notifyHeightChanged(false /* needsAnimation */);
}
if (isHeadsUp) {
+ mMustStayOnScreen = true;
setAboveShelf(true);
} else if (isAboveShelf() != wasAboveShelf) {
mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf);
@@ -502,6 +539,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
addChildNotification(row, -1);
}
+ @Override
+ public void setHeadsUpIsVisible() {
+ super.setHeadsUpIsVisible();
+ mMustStayOnScreen = false;
+ }
+
/**
* Add a child notification to this view.
*
@@ -523,6 +566,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
onChildrenCountChanged();
row.setIsChildInGroup(false, null);
+ row.setBottomRoundness(0.0f, false /* animate */);
}
@Override
@@ -551,6 +595,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mNotificationParent.updateBackgroundForGroupState();
}
updateIconVisibilities();
+ updateBackgroundClipping();
}
@Override
@@ -765,7 +810,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
* {@link #getNotificationHeader()} in case it is a low-priority group.
*/
public NotificationHeaderView getVisibleNotificationHeader() {
- if (mIsSummaryWithChildren && !mShowingPublic) {
+ if (mIsSummaryWithChildren && !shouldShowPublic()) {
return mChildrenContainer.getVisibleHeader();
}
return getShowingLayout().getVisibleNotificationHeader();
@@ -788,6 +833,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mOnExpandClickListener = onExpandClickListener;
}
+ public void setLongPressListener(LongPressListener longPressListener) {
+ mLongPressListener = longPressListener;
+ }
+
@Override
public void setOnClickListener(@Nullable OnClickListener l) {
super.setOnClickListener(l);
@@ -900,6 +949,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
addView(mMenuRow.getMenuView(), menuIndex);
}
for (NotificationContentView l : mLayouts) {
+ l.initView();
l.reInflateViews();
}
mNotificationInflater.onDensityOrFontScaleChanged();
@@ -1009,6 +1059,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mKeepInParent = keepInParent;
}
+ @Override
public boolean isRemoved() {
return mRemoved;
}
@@ -1060,9 +1111,19 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
return mGroupParentWhenDismissed;
}
- public void performDismiss() {
- if (mOnDismissRunnable != null) {
- mOnDismissRunnable.run();
+ public void performDismiss(boolean fromAccessibility) {
+ if (mGroupManager.isOnlyChildInGroup(getStatusBarNotification())) {
+ ExpandableNotificationRow groupSummary =
+ mGroupManager.getLogicalGroupSummary(getStatusBarNotification());
+ if (groupSummary.isClearable()) {
+ groupSummary.performDismiss(fromAccessibility);
+ }
+ }
+ setDismissed(true, fromAccessibility);
+ if (isClearable()) {
+ if (mOnDismissRunnable != null) {
+ mOnDismissRunnable.run();
+ }
}
}
@@ -1123,6 +1184,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
private void updateContentTransformation() {
+ if (mExpandAnimationRunning) {
+ return;
+ }
float contentAlpha;
float translationY = -mContentTransformationAmount * mIconTransformContentShift;
if (mIsLastChild) {
@@ -1246,16 +1310,25 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
private void initDimens() {
- mNotificationMinHeightLegacy = getFontScaledHeight(R.dimen.notification_min_height_legacy);
- mNotificationMinHeight = getFontScaledHeight(R.dimen.notification_min_height);
- mNotificationMinHeightLarge = getFontScaledHeight(
+ mNotificationMinHeightLegacy = NotificationUtils.getFontScaledHeight(mContext,
+ R.dimen.notification_min_height_legacy);
+ mNotificationMinHeightBeforeP = NotificationUtils.getFontScaledHeight(mContext,
+ R.dimen.notification_min_height_before_p);
+ mNotificationMinHeight = NotificationUtils.getFontScaledHeight(mContext,
+ R.dimen.notification_min_height);
+ mNotificationMinHeightLarge = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_min_height_increased);
- mNotificationMaxHeight = getFontScaledHeight(R.dimen.notification_max_height);
- mNotificationAmbientHeight = getFontScaledHeight(R.dimen.notification_ambient_height);
- mMaxHeadsUpHeightLegacy = getFontScaledHeight(
+ mNotificationMaxHeight = NotificationUtils.getFontScaledHeight(mContext,
+ R.dimen.notification_max_height);
+ mNotificationAmbientHeight = NotificationUtils.getFontScaledHeight(mContext,
+ R.dimen.notification_ambient_height);
+ mMaxHeadsUpHeightLegacy = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_max_heads_up_height_legacy);
- mMaxHeadsUpHeight = getFontScaledHeight(R.dimen.notification_max_heads_up_height);
- mMaxHeadsUpHeightIncreased = getFontScaledHeight(
+ mMaxHeadsUpHeightBeforeP = NotificationUtils.getFontScaledHeight(mContext,
+ R.dimen.notification_max_heads_up_height_before_p);
+ mMaxHeadsUpHeight = NotificationUtils.getFontScaledHeight(mContext,
+ R.dimen.notification_max_heads_up_height);
+ mMaxHeadsUpHeightIncreased = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_max_heads_up_height_increased);
Resources res = getResources();
@@ -1270,17 +1343,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
/**
- * @param dimenId the dimen to look up
- * @return the font scaled dimen as if it were in sp but doesn't shrink sizes below dp
- */
- private int getFontScaledHeight(int dimenId) {
- int dimensionPixelSize = getResources().getDimensionPixelSize(dimenId);
- float factor = Math.max(1.0f, getResources().getDisplayMetrics().scaledDensity /
- getResources().getDisplayMetrics().density);
- return (int) (dimensionPixelSize * factor);
- }
-
- /**
* Resets this view so it can be re-used for an updated notification.
*/
public void reset() {
@@ -1289,6 +1351,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
requestLayout();
}
+ public void showBlockingHelper(boolean show) {
+ mHelperButton.setVisibility(show ? View.VISIBLE : View.GONE);
+ }
+
@Override
protected void onFinishInflate() {
super.onFinishInflate();
@@ -1296,6 +1362,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mPrivateLayout = (NotificationContentView) findViewById(R.id.expanded);
mLayouts = new NotificationContentView[] {mPrivateLayout, mPublicLayout};
+ final NotificationGutsManager gutsMan = Dependency.get(NotificationGutsManager.class);
+ mHelperButton = findViewById(R.id.helper);
+ mHelperButton.setOnClickListener(view -> {
+ doLongClickCallback();
+ });
+
for (NotificationContentView l : mLayouts) {
l.setExpandClickListener(mExpandClickListener);
l.setContainingNotification(this);
@@ -1338,6 +1410,47 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
}
+ private void doLongClickCallback() {
+ doLongClickCallback(getWidth() / 2, getHeight() / 2);
+ }
+
+ public void doLongClickCallback(int x, int y) {
+ createMenu();
+ MenuItem menuItem = getProvider().getLongpressMenuItem(mContext);
+ if (mLongPressListener != null && menuItem != null) {
+ mLongPressListener.onLongPress(this, x, y, menuItem);
+ }
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (KeyEvent.isConfirmKey(keyCode)) {
+ event.startTracking();
+ return true;
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (KeyEvent.isConfirmKey(keyCode)) {
+ if (!event.isCanceled()) {
+ performClick();
+ }
+ return true;
+ }
+ return super.onKeyUp(keyCode, event);
+ }
+
+ @Override
+ public boolean onKeyLongPress(int keyCode, KeyEvent event) {
+ if (KeyEvent.isConfirmKey(keyCode)) {
+ doLongClickCallback();
+ return true;
+ }
+ return false;
+ }
+
public void resetTranslation() {
if (mTranslateAnim != null) {
mTranslateAnim.cancel();
@@ -1355,6 +1468,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mMenuRow.resetMenu();
}
+ public CharSequence getActiveRemoteInputText() {
+ return mPrivateLayout.getActiveRemoteInputText();
+ }
+
public void animateTranslateNotification(final float leftTarget) {
if (mTranslateAnim != null) {
mTranslateAnim.cancel();
@@ -1442,10 +1559,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
private void updateChildrenVisibility() {
- mPrivateLayout.setVisibility(!mShowingPublic && !mIsSummaryWithChildren ? VISIBLE
- : INVISIBLE);
+ boolean hideContentWhileLaunching = mExpandAnimationRunning && mGuts != null
+ && mGuts.isExposed();
+ mPrivateLayout.setVisibility(!shouldShowPublic() && !mIsSummaryWithChildren
+ && !hideContentWhileLaunching ? VISIBLE : INVISIBLE);
if (mChildrenContainer != null) {
- mChildrenContainer.setVisibility(!mShowingPublic && mIsSummaryWithChildren ? VISIBLE
+ mChildrenContainer.setVisibility(!shouldShowPublic() && mIsSummaryWithChildren
+ && !hideContentWhileLaunching ? VISIBLE
: INVISIBLE);
}
// The limits might have changed if the view suddenly became a group or vice versa
@@ -1484,6 +1604,62 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
updateShelfIconColor();
}
+ public void applyExpandAnimationParams(ExpandAnimationParameters params) {
+ if (params == null) {
+ return;
+ }
+ setTranslationY(params.getTop());
+ float zProgress = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
+ params.getProgress(0, 50));
+ float translationZ = MathUtils.lerp(params.getStartTranslationZ(),
+ mNotificationLaunchHeight,
+ zProgress);
+ setTranslationZ(translationZ);
+ setActualHeight(params.getHeight());
+ mBackgroundNormal.setExpandAnimationParams(params);
+ }
+
+ public void setExpandAnimationRunning(boolean expandAnimationRunning) {
+ if (expandAnimationRunning) {
+ View contentView;
+ if (mIsSummaryWithChildren) {
+ contentView = mChildrenContainer;
+ } else {
+ contentView = getShowingLayout();
+ }
+ if (mGuts != null && mGuts.isExposed()) {
+ contentView = mGuts;
+ }
+ contentView.animate()
+ .alpha(0f)
+ .setDuration(ActivityLaunchAnimator.ANIMATION_DURATION_FADE_CONTENT)
+ .setInterpolator(Interpolators.ALPHA_OUT);
+ setAboveShelf(true);
+ mExpandAnimationRunning = true;
+ mNotificationViewState.cancelAnimations(this);
+ mNotificationLaunchHeight = AmbientState.getNotificationLaunchHeight(getContext());
+ } else {
+ mExpandAnimationRunning = false;
+ setAboveShelf(isAboveShelf());
+ if (mGuts != null) {
+ mGuts.setAlpha(1.0f);
+ }
+ }
+ updateChildrenVisibility();
+ updateClipping();
+ mBackgroundNormal.setExpandAnimationRunning(expandAnimationRunning);
+ }
+
+ @Override
+ protected boolean shouldClipToActualHeight() {
+ return super.shouldClipToActualHeight() && !mExpandAnimationRunning;
+ }
+
+ @Override
+ public boolean isExpandAnimationRunning() {
+ return mExpandAnimationRunning;
+ }
+
/**
* Tap sounds should not be played when we're unlocking.
* Doing so would cause audio collision and the system would feel unpolished.
@@ -1496,7 +1672,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
public boolean isExpandable() {
- if (mIsSummaryWithChildren && !mShowingPublic) {
+ if (mIsSummaryWithChildren && !shouldShowPublic()) {
return !mChildrenExpanded;
}
return mEnableNonGroupedNotificationExpand && mExpandable;
@@ -1541,7 +1717,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
*/
public void setUserExpanded(boolean userExpanded, boolean allowChildExpansion) {
mFalsingManager.setNotificationExpanded();
- if (mIsSummaryWithChildren && !mShowingPublic && allowChildExpansion
+ if (mIsSummaryWithChildren && !shouldShowPublic() && allowChildExpansion
&& !mChildrenContainer.showingAsLowPriority()) {
final boolean wasExpanded = mGroupManager.isGroupExpanded(mStatusBarNotification);
mGroupManager.setGroupExpanded(mStatusBarNotification, userExpanded);
@@ -1701,6 +1877,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mPrivateLayout.updateExpandButtons(isExpandable());
updateChildrenHeaderAppearance();
updateChildrenVisibility();
+ applyChildrenRoundness();
}
public void updateChildrenHeaderAppearance() {
@@ -1835,7 +2012,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mPublicLayout.setVisibility(mShowingPublic ? View.VISIBLE : View.INVISIBLE);
updateChildrenVisibility();
} else {
- animateShowingPublic(delay, duration);
+ animateShowingPublic(delay, duration, mShowingPublic);
}
NotificationContentView showingLayout = getShowingLayout();
showingLayout.updateBackgroundColor(animated);
@@ -1845,13 +2022,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mShowingPublicInitialized = true;
}
- private void animateShowingPublic(long delay, long duration) {
+ private void animateShowingPublic(long delay, long duration, boolean showingPublic) {
View[] privateViews = mIsSummaryWithChildren
? new View[] {mChildrenContainer}
: new View[] {mPrivateLayout};
View[] publicViews = new View[] {mPublicLayout};
- View[] hiddenChildren = mShowingPublic ? privateViews : publicViews;
- View[] shownChildren = mShowingPublic ? publicViews : privateViews;
+ View[] hiddenChildren = showingPublic ? privateViews : publicViews;
+ View[] shownChildren = showingPublic ? publicViews : privateViews;
for (final View hiddenView : hiddenChildren) {
hiddenView.setVisibility(View.VISIBLE);
hiddenView.animate().cancel();
@@ -1879,7 +2056,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@Override
public boolean mustStayOnScreen() {
- return mIsHeadsUp;
+ return mIsHeadsUp && mMustStayOnScreen;
}
/**
@@ -1888,7 +2065,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
* see {@link #isClearable()}.
*/
public boolean canViewBeDismissed() {
- return isClearable() && (!mShowingPublic || !mSensitiveHiddenInGeneral);
+ return isClearable() && (!shouldShowPublic() || !mSensitiveHiddenInGeneral);
+ }
+
+ private boolean shouldShowPublic() {
+ return mSensitive && mHideSensitiveForIntrinsicHeight;
}
public void makeActionsVisibile() {
@@ -1934,7 +2115,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@Override
public boolean isContentExpandable() {
- if (mIsSummaryWithChildren && !mShowingPublic) {
+ if (mIsSummaryWithChildren && !shouldShowPublic()) {
return true;
}
NotificationContentView showingLayout = getShowingLayout();
@@ -1943,7 +2124,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@Override
protected View getContentView() {
- if (mIsSummaryWithChildren && !mShowingPublic) {
+ if (mIsSummaryWithChildren && !shouldShowPublic()) {
return mChildrenContainer;
}
return getShowingLayout();
@@ -2008,7 +2189,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@Override
public int getMaxContentHeight() {
- if (mIsSummaryWithChildren && !mShowingPublic) {
+ if (mIsSummaryWithChildren && !shouldShowPublic()) {
return mChildrenContainer.getMaxContentHeight();
}
NotificationContentView showingLayout = getShowingLayout();
@@ -2022,7 +2203,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
} else if (!ignoreTemporaryStates && isHeadsUpAllowed() && mIsHeadsUp
&& mHeadsUpManager.isTrackingHeadsUp()) {
return getPinnedHeadsUpHeight(false /* atLeastMinHeight */);
- } else if (mIsSummaryWithChildren && !isGroupExpanded() && !mShowingPublic) {
+ } else if (mIsSummaryWithChildren && !isGroupExpanded() && !shouldShowPublic()) {
return mChildrenContainer.getMinHeight();
} else if (!ignoreTemporaryStates && isHeadsUpAllowed() && mIsHeadsUp) {
return mHeadsUpHeight;
@@ -2033,7 +2214,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@Override
public int getCollapsedHeight() {
- if (mIsSummaryWithChildren && !mShowingPublic) {
+ if (mIsSummaryWithChildren && !shouldShowPublic()) {
return mChildrenContainer.getCollapsedHeight();
}
return getMinHeight();
@@ -2052,6 +2233,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@Override
public void setClipBottomAmount(int clipBottomAmount) {
+ if (mExpandAnimationRunning) {
+ return;
+ }
if (clipBottomAmount != mClipBottomAmount) {
super.setClipBottomAmount(clipBottomAmount);
for (NotificationContentView l : mLayouts) {
@@ -2073,7 +2257,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
public NotificationContentView getShowingLayout() {
- return mShowingPublic ? mPublicLayout : mPrivateLayout;
+ return shouldShowPublic() ? mPublicLayout : mPrivateLayout;
}
public void setLegacy(boolean legacy) {
@@ -2179,7 +2363,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
if (header != null && header.isInTouchRect(x - getTranslation(), y)) {
return true;
}
- if ((!mIsSummaryWithChildren || mShowingPublic)
+ if ((!mIsSummaryWithChildren || shouldShowPublic())
&& getShowingLayout().disallowSingleClick(x, y)) {
return true;
}
@@ -2205,10 +2389,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@Override
public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfoInternal(info);
+ info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK);
if (canViewBeDismissed()) {
info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_DISMISS);
}
- boolean expandable = mShowingPublic;
+ boolean expandable = shouldShowPublic();
boolean isExpanded = false;
if (!expandable) {
if (mIsSummaryWithChildren) {
@@ -2237,13 +2422,15 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
switch (action) {
case AccessibilityNodeInfo.ACTION_DISMISS:
- NotificationStackScrollLayout.performDismiss(this, mGroupManager,
- true /* fromAccessibility */);
+ performDismiss(true /* fromAccessibility */);
return true;
case AccessibilityNodeInfo.ACTION_COLLAPSE:
case AccessibilityNodeInfo.ACTION_EXPAND:
mExpandClickListener.onClick(this);
return true;
+ case AccessibilityNodeInfo.ACTION_LONG_CLICK:
+ doLongClickCallback();
+ return true;
}
return false;
}
@@ -2258,13 +2445,15 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@Override
public ExpandableViewState createNewViewState(StackScrollState stackScrollState) {
- return new NotificationViewState(stackScrollState);
+ mNotificationViewState = new NotificationViewState(stackScrollState);
+ return mNotificationViewState;
}
@Override
public boolean isAboveShelf() {
return !isOnKeyguard()
- && (mIsPinned || mHeadsupDisappearRunning || (mIsHeadsUp && mAboveShelf));
+ && (mIsPinned || mHeadsupDisappearRunning || (mIsHeadsUp && mAboveShelf)
+ || mExpandAnimationRunning);
}
public void setShowAmbient(boolean showAmbient) {
@@ -2277,6 +2466,56 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
}
+ @Override
+ protected boolean childNeedsClipping(View child) {
+ if (child instanceof NotificationContentView) {
+ NotificationContentView contentView = (NotificationContentView) child;
+ if (isClippingNeeded()) {
+ return true;
+ } else if (!hasNoRounding()
+ && contentView.shouldClipToRounding(getCurrentTopRoundness() != 0.0f,
+ getCurrentBottomRoundness() != 0.0f)) {
+ return true;
+ }
+ } else if (child == mChildrenContainer) {
+ if (isClippingNeeded() || !hasNoRounding()) {
+ return true;
+ }
+ } else if (child instanceof NotificationGuts) {
+ return !hasNoRounding();
+ }
+ return super.childNeedsClipping(child);
+ }
+
+ @Override
+ protected void applyRoundness() {
+ super.applyRoundness();
+ applyChildrenRoundness();
+ }
+
+ private void applyChildrenRoundness() {
+ if (mIsSummaryWithChildren) {
+ mChildrenContainer.setCurrentBottomRoundness(getCurrentBottomRoundness());
+ }
+ }
+
+ @Override
+ public Path getCustomClipPath(View child) {
+ if (child instanceof NotificationGuts) {
+ return getClipPath(true, /* ignoreTranslation */
+ false /* clipRoundedToBottom */);
+ }
+ if (child instanceof NotificationChildrenContainer) {
+ return getClipPath(false, /* ignoreTranslation */
+ true /* clipRoundedToBottom */);
+ }
+ return super.getCustomClipPath(child);
+ }
+
+ private boolean hasNoRounding() {
+ return getCurrentBottomRoundness() == 0.0f && getCurrentTopRoundness() == 0.0f;
+ }
+
public boolean isShowingAmbient() {
return mShowAmbient;
}
@@ -2300,9 +2539,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@Override
public void applyToView(View view) {
- super.applyToView(view);
if (view instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+ if (row.isExpandAnimationRunning()) {
+ return;
+ }
+ super.applyToView(view);
row.applyChildrenState(mOverallState);
}
}
@@ -2320,9 +2562,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@Override
public void animateTo(View child, AnimationProperties properties) {
- super.animateTo(child, properties);
if (child instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ if (row.isExpandAnimationRunning()) {
+ return;
+ }
+ super.animateTo(child, properties);
row.startChildAnimation(mOverallState, properties);
}
}
@@ -2332,4 +2577,15 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
protected void setChildrenContainer(NotificationChildrenContainer childrenContainer) {
mChildrenContainer = childrenContainer;
}
+
+ /**
+ * Equivalent to View.OnLongClickListener with coordinates
+ */
+ public interface LongPressListener {
+ /**
+ * Equivalent to {@link View.OnLongClickListener#onLongClick(View)} with coordinates
+ * @return whether the longpress was handled
+ */
+ boolean onLongPress(View v, int x, int y, MenuItem item);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
index 255689072b12..66b3a75f4037 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
@@ -18,88 +18,344 @@ package com.android.systemui.statusbar;
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.Canvas;
import android.graphics.Outline;
+import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewOutlineProvider;
+
+import com.android.settingslib.Utils;
import com.android.systemui.R;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
+import com.android.systemui.statusbar.notification.PropertyAnimator;
+import com.android.systemui.statusbar.stack.AnimationProperties;
+import com.android.systemui.statusbar.stack.StackStateAnimator;
/**
* Like {@link ExpandableView}, but setting an outline for the height and clipping.
*/
public abstract class ExpandableOutlineView extends ExpandableView {
+ private static final AnimatableProperty TOP_ROUNDNESS = AnimatableProperty.from(
+ "topRoundness",
+ ExpandableOutlineView::setTopRoundnessInternal,
+ ExpandableOutlineView::getCurrentTopRoundness,
+ R.id.top_roundess_animator_tag,
+ R.id.top_roundess_animator_end_tag,
+ R.id.top_roundess_animator_start_tag);
+ private static final AnimatableProperty BOTTOM_ROUNDNESS = AnimatableProperty.from(
+ "bottomRoundness",
+ ExpandableOutlineView::setBottomRoundnessInternal,
+ ExpandableOutlineView::getCurrentBottomRoundness,
+ R.id.bottom_roundess_animator_tag,
+ R.id.bottom_roundess_animator_end_tag,
+ R.id.bottom_roundess_animator_start_tag);
+ private static final AnimationProperties ROUNDNESS_PROPERTIES =
+ new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+ private static final Path EMPTY_PATH = new Path();
+
private final Rect mOutlineRect = new Rect();
+ private final Path mClipPath = new Path();
private boolean mCustomOutline;
private float mOutlineAlpha = -1f;
private float mOutlineRadius;
+ private boolean mAlwaysRoundBothCorners;
+ private Path mTmpPath = new Path();
+ private Path mTmpPath2 = new Path();
+ private float mCurrentBottomRoundness;
+ private float mCurrentTopRoundness;
+ private float mBottomRoundness;
+ private float mTopRoundness;
+ private int mBackgroundTop;
/**
* {@code true} if the children views of the {@link ExpandableOutlineView} are translated when
* it is moved. Otherwise, the translation is set on the {@code ExpandableOutlineView} itself.
*/
protected boolean mShouldTranslateContents;
+ private boolean mClipRoundedToClipTopAmount;
+ private float mDistanceToTopRoundness = -1;
private final ViewOutlineProvider mProvider = new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
- int translation = mShouldTranslateContents ? (int) getTranslation() : 0;
- if (!mCustomOutline) {
- outline.setRoundRect(translation,
- mClipTopAmount,
- getWidth() + translation,
- Math.max(getActualHeight() - mClipBottomAmount, mClipTopAmount),
- mOutlineRadius);
+ if (!mCustomOutline && mCurrentTopRoundness == 0.0f
+ && mCurrentBottomRoundness == 0.0f && !mAlwaysRoundBothCorners) {
+ int translation = mShouldTranslateContents ? (int) getTranslation() : 0;
+ int left = Math.max(translation, 0);
+ int top = mClipTopAmount + mBackgroundTop;
+ int right = getWidth() + Math.min(translation, 0);
+ int bottom = Math.max(getActualHeight() - mClipBottomAmount, top);
+ outline.setRect(left, top, right, bottom);
} else {
- outline.setRoundRect(mOutlineRect, mOutlineRadius);
+ Path clipPath = getClipPath();
+ if (clipPath != null && clipPath.isConvex()) {
+ // The path might not be convex in border cases where the view is small and
+ // clipped
+ outline.setConvexPath(clipPath);
+ }
}
outline.setAlpha(mOutlineAlpha);
}
};
+ private Path getClipPath() {
+ return getClipPath(false, /* ignoreTranslation */
+ false /* clipRoundedToBottom */);
+ }
+
+ protected Path getClipPath(boolean ignoreTranslation, boolean clipRoundedToBottom) {
+ int left;
+ int top;
+ int right;
+ int bottom;
+ int height;
+ Path intersectPath = null;
+ if (!mCustomOutline) {
+ int translation = mShouldTranslateContents && !ignoreTranslation
+ ? (int) getTranslation() : 0;
+ left = Math.max(translation, 0);
+ top = mClipTopAmount + mBackgroundTop;
+ right = getWidth() + Math.min(translation, 0);
+ bottom = Math.max(getActualHeight(), top);
+ int intersectBottom = Math.max(getActualHeight() - mClipBottomAmount, top);
+ if (bottom != intersectBottom) {
+ if (clipRoundedToBottom) {
+ bottom = intersectBottom;
+ } else {
+ getRoundedRectPath(left, top, right,
+ intersectBottom, 0.0f,
+ 0.0f, mTmpPath2);
+ intersectPath = mTmpPath2;
+ }
+ }
+ } else {
+ left = mOutlineRect.left;
+ top = mOutlineRect.top;
+ right = mOutlineRect.right;
+ bottom = mOutlineRect.bottom;
+ }
+ height = bottom - top;
+ if (height == 0) {
+ return EMPTY_PATH;
+ }
+ float topRoundness = mAlwaysRoundBothCorners
+ ? mOutlineRadius : mCurrentTopRoundness * mOutlineRadius;
+ float bottomRoundness = mAlwaysRoundBothCorners
+ ? mOutlineRadius : mCurrentBottomRoundness * mOutlineRadius;
+ if (topRoundness + bottomRoundness > height) {
+ float overShoot = topRoundness + bottomRoundness - height;
+ topRoundness -= overShoot * mCurrentTopRoundness
+ / (mCurrentTopRoundness + mCurrentBottomRoundness);
+ bottomRoundness -= overShoot * mCurrentBottomRoundness
+ / (mCurrentTopRoundness + mCurrentBottomRoundness);
+ }
+ getRoundedRectPath(left, top, right, bottom, topRoundness,
+ bottomRoundness, mTmpPath);
+ Path roundedRectPath = mTmpPath;
+ if (intersectPath != null) {
+ roundedRectPath.op(intersectPath, Path.Op.INTERSECT);
+ }
+ return roundedRectPath;
+ }
+
+ public static void getRoundedRectPath(int left, int top, int right, int bottom,
+ float topRoundness, float bottomRoundness, Path outPath) {
+ outPath.reset();
+ int width = right - left;
+ float topRoundnessX = topRoundness;
+ float bottomRoundnessX = bottomRoundness;
+ topRoundnessX = Math.min(width / 2, topRoundnessX);
+ bottomRoundnessX = Math.min(width / 2, bottomRoundnessX);
+ if (topRoundness > 0.0f) {
+ outPath.moveTo(left, top + topRoundness);
+ outPath.quadTo(left, top, left + topRoundnessX, top);
+ outPath.lineTo(right - topRoundnessX, top);
+ outPath.quadTo(right, top, right, top + topRoundness);
+ } else {
+ outPath.moveTo(left, top);
+ outPath.lineTo(right, top);
+ }
+ if (bottomRoundness > 0.0f) {
+ outPath.lineTo(right, bottom - bottomRoundness);
+ outPath.quadTo(right, bottom, right - bottomRoundnessX, bottom);
+ outPath.lineTo(left + bottomRoundnessX, bottom);
+ outPath.quadTo(left, bottom, left, bottom - bottomRoundness);
+ } else {
+ outPath.lineTo(right, bottom);
+ outPath.lineTo(left, bottom);
+ }
+ outPath.close();
+ }
+
public ExpandableOutlineView(Context context, AttributeSet attrs) {
super(context, attrs);
setOutlineProvider(mProvider);
initDimens();
}
+ @Override
+ protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+ canvas.save();
+ Path intersectPath = null;
+ if (mClipRoundedToClipTopAmount) {
+ int left = 0;
+ int top = (int) (mClipTopAmount - mDistanceToTopRoundness);
+ int right = getWidth();
+ int bottom = (int) Math.max(getActualHeight() - mClipBottomAmount,
+ top + mOutlineRadius);
+ ExpandableOutlineView.getRoundedRectPath(left, top, right, bottom, mOutlineRadius,
+ 0.0f,
+ mClipPath);
+ intersectPath = mClipPath;
+ }
+ boolean clipped = false;
+ if (childNeedsClipping(child)) {
+ Path clipPath = getCustomClipPath(child);
+ if (clipPath == null) {
+ clipPath = getClipPath();
+ }
+ if (clipPath != null) {
+ if (intersectPath != null) {
+ clipPath.op(intersectPath, Path.Op.INTERSECT);
+ }
+ canvas.clipPath(clipPath);
+ clipped = true;
+ }
+ }
+ if (!clipped && intersectPath != null) {
+ canvas.clipPath(intersectPath);
+ }
+ boolean result = super.drawChild(canvas, child, drawingTime);
+ canvas.restore();
+ return result;
+ }
+
+ @Override
+ public void setDistanceToTopRoundness(float distanceToTopRoundness) {
+ super.setDistanceToTopRoundness(distanceToTopRoundness);
+ if (distanceToTopRoundness != mDistanceToTopRoundness) {
+ mClipRoundedToClipTopAmount = distanceToTopRoundness >= 0;
+ mDistanceToTopRoundness = distanceToTopRoundness;
+ invalidate();
+ }
+ }
+
+ protected boolean childNeedsClipping(View child) {
+ return false;
+ }
+
+ protected boolean isClippingNeeded() {
+ return mAlwaysRoundBothCorners || mCustomOutline || getTranslation() != 0 ;
+
+ }
+
private void initDimens() {
Resources res = getResources();
mShouldTranslateContents =
res.getBoolean(R.bool.config_translateNotificationContentsOnSwipe);
mOutlineRadius = res.getDimension(R.dimen.notification_shadow_radius);
- setClipToOutline(res.getBoolean(R.bool.config_clipNotificationsToOutline));
+ mAlwaysRoundBothCorners = res.getBoolean(R.bool.config_clipNotificationsToOutline);
+ if (!mAlwaysRoundBothCorners) {
+ mOutlineRadius = res.getDimensionPixelSize(
+ Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius));
+ }
+ setClipToOutline(mAlwaysRoundBothCorners);
+ }
+
+ public void setTopRoundness(float topRoundness, boolean animate) {
+ if (mTopRoundness != topRoundness) {
+ mTopRoundness = topRoundness;
+ PropertyAnimator.setProperty(this, TOP_ROUNDNESS, topRoundness,
+ ROUNDNESS_PROPERTIES, animate);
+ }
+ }
+
+ protected void applyRoundness() {
+ invalidateOutline();
+ invalidate();
+ }
+
+ public float getCurrentBackgroundRadiusTop() {
+ return mCurrentTopRoundness * mOutlineRadius;
+ }
+
+ public float getCurrentTopRoundness() {
+ return mCurrentTopRoundness;
+ }
+
+ public float getCurrentBottomRoundness() {
+ return mCurrentBottomRoundness;
+ }
+
+ protected float getCurrentBackgroundRadiusBottom() {
+ return mCurrentBottomRoundness * mOutlineRadius;
+ }
+
+ public void setBottomRoundness(float bottomRoundness, boolean animate) {
+ if (mBottomRoundness != bottomRoundness) {
+ mBottomRoundness = bottomRoundness;
+ PropertyAnimator.setProperty(this, BOTTOM_ROUNDNESS, bottomRoundness,
+ ROUNDNESS_PROPERTIES, animate);
+ }
+ }
+
+ protected void setBackgroundTop(int backgroundTop) {
+ if (mBackgroundTop != backgroundTop) {
+ mBackgroundTop = backgroundTop;
+ invalidateOutline();
+ }
+ }
+
+ private void setTopRoundnessInternal(float topRoundness) {
+ mCurrentTopRoundness = topRoundness;
+ applyRoundness();
+ }
+
+ private void setBottomRoundnessInternal(float bottomRoundness) {
+ mCurrentBottomRoundness = bottomRoundness;
+ applyRoundness();
}
public void onDensityOrFontScaleChanged() {
initDimens();
- invalidateOutline();
+ applyRoundness();
}
@Override
public void setActualHeight(int actualHeight, boolean notifyListeners) {
+ int previousHeight = getActualHeight();
super.setActualHeight(actualHeight, notifyListeners);
- invalidateOutline();
+ if (previousHeight != actualHeight) {
+ applyRoundness();
+ }
}
@Override
public void setClipTopAmount(int clipTopAmount) {
+ int previousAmount = getClipTopAmount();
super.setClipTopAmount(clipTopAmount);
- invalidateOutline();
+ if (previousAmount != clipTopAmount) {
+ applyRoundness();
+ }
}
@Override
public void setClipBottomAmount(int clipBottomAmount) {
+ int previousAmount = getClipBottomAmount();
super.setClipBottomAmount(clipBottomAmount);
- invalidateOutline();
+ if (previousAmount != clipBottomAmount) {
+ applyRoundness();
+ }
}
protected void setOutlineAlpha(float alpha) {
if (alpha != mOutlineAlpha) {
mOutlineAlpha = alpha;
- invalidateOutline();
+ applyRoundness();
}
}
@@ -113,8 +369,7 @@ public abstract class ExpandableOutlineView extends ExpandableView {
setOutlineRect(rect.left, rect.top, rect.right, rect.bottom);
} else {
mCustomOutline = false;
- setClipToOutline(false);
- invalidateOutline();
+ applyRoundness();
}
}
@@ -151,15 +406,16 @@ public abstract class ExpandableOutlineView extends ExpandableView {
protected void setOutlineRect(float left, float top, float right, float bottom) {
mCustomOutline = true;
- setClipToOutline(true);
mOutlineRect.set((int) left, (int) top, (int) right, (int) bottom);
// Outlines need to be at least 1 dp
mOutlineRect.bottom = (int) Math.max(top, mOutlineRect.bottom);
mOutlineRect.right = (int) Math.max(left, mOutlineRect.right);
-
- invalidateOutline();
+ applyRoundness();
}
+ public Path getCustomClipPath(View child) {
+ return null;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
index aac9af8a0234..1496a41a9b47 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
@@ -130,6 +130,14 @@ public abstract class ExpandableView extends FrameLayout {
}
}
+ /**
+ * Set the distance to the top roundness, from where we should start clipping a value above
+ * or equal to 0 is the effective distance, and if a value below 0 is received, there should
+ * be no clipping.
+ */
+ public void setDistanceToTopRoundness(float distanceToTopRoundness) {
+ }
+
public void setActualHeight(int actualHeight) {
setActualHeight(actualHeight, true /* notifyListeners */);
}
@@ -143,6 +151,10 @@ public abstract class ExpandableView extends FrameLayout {
return mActualHeight;
}
+ public boolean isExpandAnimationRunning() {
+ return false;
+ }
+
/**
* @return The maximum height of this notification.
*/
@@ -202,6 +214,10 @@ public abstract class ExpandableView extends FrameLayout {
return mDark;
}
+ public boolean isRemoved() {
+ return false;
+ }
+
/**
* See {@link #setHideSensitive}. This is a variant which notifies this view in advance about
* the upcoming state of hiding sensitive notifications. It gets called at the very beginning
@@ -363,8 +379,8 @@ public abstract class ExpandableView extends FrameLayout {
return false;
}
- private void updateClipping() {
- if (mClipToActualHeight) {
+ protected void updateClipping() {
+ if (mClipToActualHeight && shouldClipToActualHeight()) {
int top = getClipTopAmount();
mClipRect.set(0, top, getWidth(), Math.max(getActualHeight() + getExtraBottomPadding()
- mClipBottomAmount, top));
@@ -374,6 +390,10 @@ public abstract class ExpandableView extends FrameLayout {
}
}
+ protected boolean shouldClipToActualHeight() {
+ return true;
+ }
+
public void setClipToActualHeight(boolean clipToActualHeight) {
mClipToActualHeight = clipToActualHeight;
updateClipping();
@@ -474,6 +494,9 @@ public abstract class ExpandableView extends FrameLayout {
return false;
}
+ public void setHeadsUpIsVisible() {
+ }
+
public boolean isChildInGroup() {
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
index d370a6331ea1..2d16d2209c9e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
@@ -433,24 +433,27 @@ public final class KeyboardShortcuts {
// Assist.
final AssistUtils assistUtils = new AssistUtils(mContext);
final ComponentName assistComponent = assistUtils.getAssistComponentForUser(userId);
- PackageInfo assistPackageInfo = null;
- try {
- assistPackageInfo = mPackageManager.getPackageInfo(
- assistComponent.getPackageName(), 0, userId);
- } catch (RemoteException e) {
- Log.e(TAG, "PackageManagerService is dead");
- }
+ // Not all devices have an assist component.
+ if (assistComponent != null) {
+ PackageInfo assistPackageInfo = null;
+ try {
+ assistPackageInfo = mPackageManager.getPackageInfo(
+ assistComponent.getPackageName(), 0, userId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "PackageManagerService is dead");
+ }
- if (assistPackageInfo != null) {
- final Icon assistIcon = Icon.createWithResource(
- assistPackageInfo.applicationInfo.packageName,
- assistPackageInfo.applicationInfo.icon);
+ if (assistPackageInfo != null) {
+ final Icon assistIcon = Icon.createWithResource(
+ assistPackageInfo.applicationInfo.packageName,
+ assistPackageInfo.applicationInfo.icon);
- keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
- mContext.getString(R.string.keyboard_shortcut_group_applications_assist),
- assistIcon,
- KeyEvent.KEYCODE_UNKNOWN,
- KeyEvent.META_META_ON));
+ keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
+ mContext.getString(R.string.keyboard_shortcut_group_applications_assist),
+ assistIcon,
+ KeyEvent.KEYCODE_UNKNOWN,
+ KeyEvent.META_META_ON));
+ }
}
// Browser.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 569e58d78fae..b7a15005b170 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -44,6 +46,7 @@ import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.settingslib.Utils;
import com.android.systemui.Dependency;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.statusbar.phone.KeyguardIndicationTextView;
import com.android.systemui.statusbar.phone.LockIcon;
@@ -52,6 +55,10 @@ import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.util.wakelock.SettableWakeLock;
import com.android.systemui.util.wakelock.WakeLock;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.text.NumberFormat;
+
/**
* Controls the indications and error messages shown on the Keyguard
*/
@@ -78,7 +85,7 @@ public class KeyguardIndicationController {
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private String mRestingIndication;
- private String mTransientIndication;
+ private CharSequence mTransientIndication;
private int mTransientTextColor;
private int mInitialTextColor;
private boolean mVisible;
@@ -87,6 +94,7 @@ public class KeyguardIndicationController {
private boolean mPowerCharged;
private int mChargingSpeed;
private int mChargingWattage;
+ private int mBatteryLevel;
private String mMessageToShowOnScreenOn;
private KeyguardUpdateMonitorCallback mUpdateMonitorCallback;
@@ -113,11 +121,9 @@ public class KeyguardIndicationController {
WakeLock wakeLock) {
mContext = context;
mIndicationArea = indicationArea;
- mTextView = (KeyguardIndicationTextView) indicationArea.findViewById(
- R.id.keyguard_indication_text);
+ mTextView = indicationArea.findViewById(R.id.keyguard_indication_text);
mInitialTextColor = mTextView != null ? mTextView.getCurrentTextColor() : Color.WHITE;
- mDisclosure = (KeyguardIndicationTextView) indicationArea.findViewById(
- R.id.keyguard_indication_enterprise_disclosure);
+ mDisclosure = indicationArea.findViewById(R.id.keyguard_indication_enterprise_disclosure);
mLockIcon = lockIcon;
mWakeLock = new SettableWakeLock(wakeLock);
@@ -189,7 +195,7 @@ public class KeyguardIndicationController {
if (!mHandler.hasMessages(MSG_HIDE_TRANSIENT)) {
hideTransientIndication();
}
- updateIndication();
+ updateIndication(false);
} else if (!visible) {
// If we unlock and return to keyguard quickly, previous error should not be shown
hideTransientIndication();
@@ -201,7 +207,7 @@ public class KeyguardIndicationController {
*/
public void setRestingIndication(String restingIndication) {
mRestingIndication = restingIndication;
- updateIndication();
+ updateIndication(false);
}
/**
@@ -246,14 +252,14 @@ public class KeyguardIndicationController {
/**
* Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}.
*/
- public void showTransientIndication(String transientIndication) {
+ public void showTransientIndication(CharSequence transientIndication) {
showTransientIndication(transientIndication, mInitialTextColor);
}
/**
* Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}.
*/
- public void showTransientIndication(String transientIndication, int textColor) {
+ public void showTransientIndication(CharSequence transientIndication, int textColor) {
mTransientIndication = transientIndication;
mTransientTextColor = textColor;
mHandler.removeMessages(MSG_HIDE_TRANSIENT);
@@ -262,7 +268,8 @@ public class KeyguardIndicationController {
mWakeLock.setAcquired(true);
hideTransientIndicationDelayed(BaseKeyguardCallback.HIDE_DELAY_MS);
}
- updateIndication();
+
+ updateIndication(false);
}
/**
@@ -272,11 +279,11 @@ public class KeyguardIndicationController {
if (mTransientIndication != null) {
mTransientIndication = null;
mHandler.removeMessages(MSG_HIDE_TRANSIENT);
- updateIndication();
+ updateIndication(false);
}
}
- protected final void updateIndication() {
+ protected final void updateIndication(boolean animate) {
if (TextUtils.isEmpty(mTransientIndication)) {
mWakeLock.setAcquired(false);
}
@@ -285,14 +292,46 @@ public class KeyguardIndicationController {
// Walk down a precedence-ordered list of what indication
// should be shown based on user or device state
if (mDozing) {
- // If we're dozing, never show a persistent indication.
+ mTextView.setTextColor(Color.WHITE);
if (!TextUtils.isEmpty(mTransientIndication)) {
// When dozing we ignore any text color and use white instead, because
// colors can be hard to read in low brightness.
- mTextView.setTextColor(Color.WHITE);
mTextView.switchIndication(mTransientIndication);
+ } else if (mPowerPluggedIn) {
+ String indication = computePowerIndication();
+ if (animate) {
+ int yTranslation = mContext.getResources().getInteger(
+ R.integer.wired_charging_aod_text_animation_distance);
+ int animateUpDuration = mContext.getResources().getInteger(
+ R.integer.wired_charging_aod_text_animation_duration_up);
+ int animateDownDuration = mContext.getResources().getInteger(
+ R.integer.wired_charging_aod_text_animation_duration_down);
+ mTextView.animate()
+ .translationYBy(yTranslation)
+ .setInterpolator(Interpolators.LINEAR)
+ .setDuration(animateUpDuration)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mTextView.switchIndication(indication);
+ }
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mTextView.animate()
+ .setDuration(animateDownDuration)
+ .setInterpolator(Interpolators.BOUNCE)
+ .translationYBy(-1 * yTranslation)
+ .setListener(null);
+ }
+ });
+ } else {
+ mTextView.switchIndication(indication);
+ }
+
} else {
- mTextView.switchIndication(null);
+ String percentage = NumberFormat.getPercentInstance()
+ .format(mBatteryLevel / 100f);
+ mTextView.switchIndication(percentage);
}
return;
}
@@ -383,7 +422,7 @@ public class KeyguardIndicationController {
public void onReceive(Context context, Intent intent) {
mHandler.post(() -> {
if (mVisible) {
- updateIndication();
+ updateIndication(false);
}
});
}
@@ -405,10 +444,25 @@ public class KeyguardIndicationController {
return;
}
mDozing = dozing;
- updateIndication();
+ updateIndication(false);
updateDisclosure();
}
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("KeyguardIndicationController:");
+ pw.println(" mTransientTextColor: " + Integer.toHexString(mTransientTextColor));
+ pw.println(" mInitialTextColor: " + Integer.toHexString(mInitialTextColor));
+ pw.println(" mPowerPluggedIn: " + mPowerPluggedIn);
+ pw.println(" mPowerCharged: " + mPowerCharged);
+ pw.println(" mChargingSpeed: " + mChargingSpeed);
+ pw.println(" mChargingWattage: " + mChargingWattage);
+ pw.println(" mMessageToShowOnScreenOn: " + mMessageToShowOnScreenOn);
+ pw.println(" mDozing: " + mDozing);
+ pw.println(" mBatteryLevel: " + mBatteryLevel);
+ pw.println(" mTextView.getText(): " + (mTextView == null ? null : mTextView.getText()));
+ pw.println(" computePowerIndication(): " + computePowerIndication());
+ }
+
protected class BaseKeyguardCallback extends KeyguardUpdateMonitorCallback {
public static final int HIDE_DELAY_MS = 5000;
private int mLastSuccessiveErrorMessage = -1;
@@ -422,7 +476,8 @@ public class KeyguardIndicationController {
mPowerCharged = status.isCharged();
mChargingWattage = status.maxChargingWattage;
mChargingSpeed = status.getChargingSpeed(mSlowThreshold, mFastThreshold);
- updateIndication();
+ mBatteryLevel = status.level;
+ updateIndication(!wasPluggedIn && mPowerPluggedIn);
if (mDozing) {
if (!wasPluggedIn && mPowerPluggedIn) {
showTransientIndication(computePowerIndication());
@@ -490,6 +545,12 @@ public class KeyguardIndicationController {
}
@Override
+ public void onTrustAgentErrorMessage(CharSequence message) {
+ int errorColor = Utils.getColorError(mContext);
+ showTransientIndication(message, errorColor);
+ }
+
+ @Override
public void onScreenTurnedOn() {
if (mMessageToShowOnScreenOn != null) {
int errorColor = Utils.getColorError(mContext);
@@ -522,7 +583,7 @@ public class KeyguardIndicationController {
@Override
public void onUserUnlocked() {
if (mVisible) {
- updateIndication();
+ updateIndication(false);
}
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java
index 81a99bc1e70c..ab89a5287cdb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java
@@ -19,37 +19,70 @@ package com.android.systemui.statusbar;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Canvas;
-import android.graphics.ColorFilter;
import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.RippleDrawable;
import android.util.AttributeSet;
import android.view.View;
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
+
/**
* A view that can be used for both the dimmed and normal background of an notification.
*/
public class NotificationBackgroundView extends View {
+ private final boolean mDontModifyCorners;
private Drawable mBackground;
private int mClipTopAmount;
private int mActualHeight;
private int mClipBottomAmount;
private int mTintColor;
+ private float[] mCornerRadii = new float[8];
+ private boolean mBottomIsRounded;
+ private int mBackgroundTop;
+ private boolean mBottomAmountClips = true;
+ private boolean mExpandAnimationRunning;
+ private float mActualWidth;
+ private int mDrawableAlpha = 255;
public NotificationBackgroundView(Context context, AttributeSet attrs) {
super(context, attrs);
+ mDontModifyCorners = getResources().getBoolean(
+ R.bool.config_clipNotificationsToOutline);
}
@Override
protected void onDraw(Canvas canvas) {
- draw(canvas, mBackground);
+ if (mClipTopAmount + mClipBottomAmount < mActualHeight - mBackgroundTop
+ || mExpandAnimationRunning) {
+ canvas.save();
+ if (!mExpandAnimationRunning) {
+ canvas.clipRect(0, mClipTopAmount, getWidth(), mActualHeight - mClipBottomAmount);
+ }
+ draw(canvas, mBackground);
+ canvas.restore();
+ }
}
private void draw(Canvas canvas, Drawable drawable) {
- int bottom = mActualHeight - mClipBottomAmount;
- if (drawable != null && bottom > mClipTopAmount) {
- drawable.setBounds(0, mClipTopAmount, getWidth(), bottom);
+ if (drawable != null) {
+ int bottom = mActualHeight;
+ if (mBottomIsRounded && mBottomAmountClips && !mExpandAnimationRunning) {
+ bottom -= mClipBottomAmount;
+ }
+ int left = 0;
+ int right = getWidth();
+ if (mExpandAnimationRunning) {
+ left = (int) ((getWidth() - mActualWidth) / 2.0f);
+ right = (int) (left + mActualWidth);
+ }
+ drawable.setBounds(left, mBackgroundTop, right, bottom);
drawable.draw(canvas);
}
}
@@ -87,6 +120,7 @@ public class NotificationBackgroundView extends View {
unscheduleDrawable(mBackground);
}
mBackground = background;
+ mBackground.mutate();
if (mBackground != null) {
mBackground.setCallback(this);
setTint(mTintColor);
@@ -94,6 +128,7 @@ public class NotificationBackgroundView extends View {
if (mBackground instanceof RippleDrawable) {
((RippleDrawable) mBackground).setForceSoftware(true);
}
+ updateBackgroundRadii();
invalidate();
}
@@ -113,6 +148,9 @@ public class NotificationBackgroundView extends View {
}
public void setActualHeight(int actualHeight) {
+ if (mExpandAnimationRunning) {
+ return;
+ }
mActualHeight = actualHeight;
invalidate();
}
@@ -150,6 +188,74 @@ public class NotificationBackgroundView extends View {
}
public void setDrawableAlpha(int drawableAlpha) {
+ mDrawableAlpha = drawableAlpha;
+ if (mExpandAnimationRunning) {
+ return;
+ }
mBackground.setAlpha(drawableAlpha);
}
+
+ public void setRoundness(float topRoundness, float bottomRoundNess) {
+ mBottomIsRounded = bottomRoundNess != 0.0f;
+ mCornerRadii[0] = topRoundness;
+ mCornerRadii[1] = topRoundness;
+ mCornerRadii[2] = topRoundness;
+ mCornerRadii[3] = topRoundness;
+ mCornerRadii[4] = bottomRoundNess;
+ mCornerRadii[5] = bottomRoundNess;
+ mCornerRadii[6] = bottomRoundNess;
+ mCornerRadii[7] = bottomRoundNess;
+ updateBackgroundRadii();
+ }
+
+ public void setBottomAmountClips(boolean clips) {
+ if (clips != mBottomAmountClips) {
+ mBottomAmountClips = clips;
+ invalidate();
+ }
+ }
+
+ private void updateBackgroundRadii() {
+ if (mDontModifyCorners) {
+ return;
+ }
+ if (mBackground instanceof LayerDrawable) {
+ GradientDrawable gradientDrawable =
+ (GradientDrawable) ((LayerDrawable) mBackground).getDrawable(0);
+ gradientDrawable.setCornerRadii(mCornerRadii);
+ }
+ }
+
+ public void setBackgroundTop(int backgroundTop) {
+ mBackgroundTop = backgroundTop;
+ invalidate();
+ }
+
+ public void setExpandAnimationParams(ActivityLaunchAnimator.ExpandAnimationParameters params) {
+ mActualHeight = params.getHeight();
+ mActualWidth = params.getWidth();
+ float alphaProgress = Interpolators.ALPHA_IN.getInterpolation(
+ params.getProgress(
+ ActivityLaunchAnimator.ANIMATION_DURATION_FADE_CONTENT /* delay */,
+ ActivityLaunchAnimator.ANIMATION_DURATION_FADE_APP /* duration */));
+ mBackground.setAlpha((int) (mDrawableAlpha * (1.0f - alphaProgress)));
+ invalidate();
+ }
+
+ public void setExpandAnimationRunning(boolean running) {
+ mExpandAnimationRunning = running;
+ if (mBackground instanceof LayerDrawable) {
+ GradientDrawable gradientDrawable =
+ (GradientDrawable) ((LayerDrawable) mBackground).getDrawable(0);
+ gradientDrawable.setXfermode(
+ running ? new PorterDuffXfermode(PorterDuff.Mode.SRC) : null);
+ // Speed optimization: disable AA if transfer mode is not SRC_OVER. AA is not easy to
+ // spot during animation anyways.
+ gradientDrawable.setAntiAlias(!running);
+ }
+ if (!mExpandAnimationRunning) {
+ setDrawableAlpha(mDrawableAlpha);
+ }
+ invalidate();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index 9e059c89ffe1..c4d0b79a69c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -24,23 +24,28 @@ import android.graphics.Rect;
import android.os.Build;
import android.service.notification.StatusBarNotification;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.NotificationHeaderView;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import android.widget.ImageView;
+import android.widget.LinearLayout;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.NotificationColorUtil;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
-import com.android.systemui.statusbar.notification.HybridNotificationView;
import com.android.systemui.statusbar.notification.HybridGroupManager;
+import com.android.systemui.statusbar.notification.HybridNotificationView;
import com.android.systemui.statusbar.notification.NotificationCustomViewWrapper;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.NotificationViewWrapper;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.policy.RemoteInputView;
+import com.android.systemui.statusbar.policy.SmartReplyConstants;
+import com.android.systemui.statusbar.policy.SmartReplyView;
/**
* A frame layout containing the actual payload of the notification, including the contracted,
@@ -49,6 +54,7 @@ import com.android.systemui.statusbar.policy.RemoteInputView;
*/
public class NotificationContentView extends FrameLayout {
+ private static final String TAG = "NotificationContentView";
public static final int VISIBLE_TYPE_CONTRACTED = 0;
public static final int VISIBLE_TYPE_EXPANDED = 1;
public static final int VISIBLE_TYPE_HEADSUP = 2;
@@ -58,9 +64,9 @@ public class NotificationContentView extends FrameLayout {
public static final int UNDEFINED = -1;
private final Rect mClipBounds = new Rect();
- private final int mMinContractedHeight;
- private final int mNotificationContentMarginEnd;
+ private int mMinContractedHeight;
+ private int mNotificationContentMarginEnd;
private View mContractedChild;
private View mExpandedChild;
private View mHeadsUpChild;
@@ -71,6 +77,9 @@ public class NotificationContentView extends FrameLayout {
private RemoteInputView mExpandedRemoteInput;
private RemoteInputView mHeadsUpRemoteInput;
+ private SmartReplyConstants mSmartReplyConstants;
+ private SmartReplyView mExpandedSmartReplyView;
+
private NotificationViewWrapper mContractedWrapper;
private NotificationViewWrapper mExpandedWrapper;
private NotificationViewWrapper mHeadsUpWrapper;
@@ -139,6 +148,11 @@ public class NotificationContentView extends FrameLayout {
public NotificationContentView(Context context, AttributeSet attrs) {
super(context, attrs);
mHybridGroupManager = new HybridGroupManager(getContext(), this);
+ mSmartReplyConstants = Dependency.get(SmartReplyConstants.class);
+ initView();
+ }
+
+ public void initView() {
mMinContractedHeight = getResources().getDimensionPixelSize(
R.dimen.min_notification_layout_height);
mNotificationContentMarginEnd = getResources().getDimensionPixelSize(
@@ -178,7 +192,7 @@ public class NotificationContentView extends FrameLayout {
: MeasureSpec.makeMeasureSpec(size, useExactly
? MeasureSpec.EXACTLY
: MeasureSpec.AT_MOST);
- mExpandedChild.measure(widthMeasureSpec, spec);
+ measureChildWithMargins(mExpandedChild, widthMeasureSpec, 0, spec, 0);
maxChildHeight = Math.max(maxChildHeight, mExpandedChild.getMeasuredHeight());
}
if (mContractedChild != null) {
@@ -196,22 +210,22 @@ public class NotificationContentView extends FrameLayout {
} else {
heightSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST);
}
- mContractedChild.measure(widthMeasureSpec, heightSpec);
+ measureChildWithMargins(mContractedChild, widthMeasureSpec, 0, heightSpec, 0);
int measuredHeight = mContractedChild.getMeasuredHeight();
if (measuredHeight < mMinContractedHeight) {
heightSpec = MeasureSpec.makeMeasureSpec(mMinContractedHeight, MeasureSpec.EXACTLY);
- mContractedChild.measure(widthMeasureSpec, heightSpec);
+ measureChildWithMargins(mContractedChild, widthMeasureSpec, 0, heightSpec, 0);
}
maxChildHeight = Math.max(maxChildHeight, measuredHeight);
if (updateContractedHeaderWidth()) {
- mContractedChild.measure(widthMeasureSpec, heightSpec);
+ measureChildWithMargins(mContractedChild, widthMeasureSpec, 0, heightSpec, 0);
}
if (mExpandedChild != null
&& mContractedChild.getMeasuredHeight() > mExpandedChild.getMeasuredHeight()) {
// the Expanded child is smaller then the collapsed. Let's remeasure it.
heightSpec = MeasureSpec.makeMeasureSpec(mContractedChild.getMeasuredHeight(),
MeasureSpec.EXACTLY);
- mExpandedChild.measure(widthMeasureSpec, heightSpec);
+ measureChildWithMargins(mExpandedChild, widthMeasureSpec, 0, heightSpec, 0);
}
}
if (mHeadsUpChild != null) {
@@ -223,9 +237,9 @@ public class NotificationContentView extends FrameLayout {
size = Math.min(size, layoutParams.height);
useExactly = true;
}
- mHeadsUpChild.measure(widthMeasureSpec,
+ measureChildWithMargins(mHeadsUpChild, widthMeasureSpec, 0,
MeasureSpec.makeMeasureSpec(size, useExactly ? MeasureSpec.EXACTLY
- : MeasureSpec.AT_MOST));
+ : MeasureSpec.AT_MOST), 0);
maxChildHeight = Math.max(maxChildHeight, mHeadsUpChild.getMeasuredHeight());
}
if (mSingleLineView != null) {
@@ -384,6 +398,22 @@ public class NotificationContentView extends FrameLayout {
mContractedWrapper.setDark(mDark, false /* animate */, 0 /* delay */);
}
+ private NotificationViewWrapper getWrapperForView(View child) {
+ if (child == mContractedChild) {
+ return mContractedWrapper;
+ }
+ if (child == mExpandedChild) {
+ return mExpandedWrapper;
+ }
+ if (child == mHeadsUpChild) {
+ return mHeadsUpWrapper;
+ }
+ if (child == mAmbientChild) {
+ return mAmbientWrapper;
+ }
+ return null;
+ }
+
public void setExpandedChild(View child) {
if (mExpandedChild != null) {
mPreviousExpandedRemoteInputIntent = null;
@@ -643,6 +673,13 @@ public class NotificationContentView extends FrameLayout {
int endHeight = getViewForVisibleType(mVisibleType).getHeight();
int progress = Math.abs(mContentHeight - startHeight);
int totalDistance = Math.abs(endHeight - startHeight);
+ if (totalDistance == 0) {
+ Log.wtf(TAG, "the total transformation distance is 0"
+ + "\n StartType: " + mTransformationStartVisibleType + " height: " + startHeight
+ + "\n VisibleType: " + mVisibleType + " height: " + endHeight
+ + "\n mContentHeight: " + mContentHeight);
+ return 1.0f;
+ }
float amount = (float) progress / (float) totalDistance;
return Math.min(1.0f, amount);
}
@@ -1096,7 +1133,7 @@ public class NotificationContentView extends FrameLayout {
if (mAmbientChild != null) {
mAmbientWrapper.onContentUpdated(entry.row);
}
- applyRemoteInput(entry);
+ applyRemoteInputAndSmartReply(entry);
updateLegacy();
mForceSelectNextLayout = true;
setDark(mDark, false /* animate */, 0 /* delay */);
@@ -1128,20 +1165,33 @@ public class NotificationContentView extends FrameLayout {
}
}
- private void applyRemoteInput(final NotificationData.Entry entry) {
+ private void applyRemoteInputAndSmartReply(final NotificationData.Entry entry) {
if (mRemoteInputController == null) {
return;
}
+ boolean enableSmartReplies = mSmartReplyConstants.isEnabled();
+
boolean hasRemoteInput = false;
+ RemoteInput remoteInputWithChoices = null;
+ PendingIntent pendingIntentWithChoices = null;
Notification.Action[] actions = entry.notification.getNotification().actions;
if (actions != null) {
for (Notification.Action a : actions) {
if (a.getRemoteInputs() != null) {
for (RemoteInput ri : a.getRemoteInputs()) {
- if (ri.getAllowFreeFormInput()) {
+ boolean showRemoteInputView = ri.getAllowFreeFormInput();
+ boolean showSmartReplyView = enableSmartReplies && ri.getChoices() != null
+ && ri.getChoices().length > 0;
+ if (showRemoteInputView) {
hasRemoteInput = true;
+ }
+ if (showSmartReplyView) {
+ remoteInputWithChoices = ri;
+ pendingIntentWithChoices = a.actionIntent;
+ }
+ if (showRemoteInputView || showSmartReplyView) {
break;
}
}
@@ -1149,6 +1199,11 @@ public class NotificationContentView extends FrameLayout {
}
}
+ applyRemoteInput(entry, hasRemoteInput);
+ applySmartReplyView(remoteInputWithChoices, pendingIntentWithChoices);
+ }
+
+ private void applyRemoteInput(NotificationData.Entry entry, boolean hasRemoteInput) {
View bigContentView = mExpandedChild;
if (bigContentView != null) {
mExpandedRemoteInput = applyRemoteInput(bigContentView, entry, hasRemoteInput,
@@ -1245,6 +1300,40 @@ public class NotificationContentView extends FrameLayout {
return null;
}
+ private void applySmartReplyView(RemoteInput remoteInput, PendingIntent pendingIntent) {
+ mExpandedSmartReplyView = mExpandedChild == null ?
+ null : applySmartReplyView(mExpandedChild, remoteInput, pendingIntent);
+ }
+
+ private SmartReplyView applySmartReplyView(
+ View view, RemoteInput remoteInput, PendingIntent pendingIntent) {
+ View smartReplyContainerCandidate = view.findViewById(
+ com.android.internal.R.id.smart_reply_container);
+ if (!(smartReplyContainerCandidate instanceof LinearLayout)) {
+ return null;
+ }
+ LinearLayout smartReplyContainer = (LinearLayout) smartReplyContainerCandidate;
+ if (remoteInput == null || pendingIntent == null) {
+ smartReplyContainer.setVisibility(View.GONE);
+ return null;
+ }
+ SmartReplyView smartReplyView = null;
+ if (smartReplyContainer.getChildCount() == 0) {
+ smartReplyView = SmartReplyView.inflate(mContext, smartReplyContainer);
+ smartReplyContainer.addView(smartReplyView);
+ } else if (smartReplyContainer.getChildCount() == 1) {
+ View child = smartReplyContainer.getChildAt(0);
+ if (child instanceof SmartReplyView) {
+ smartReplyView = (SmartReplyView) child;
+ }
+ }
+ if (smartReplyView != null) {
+ smartReplyView.setRepliesFromRemoteInput(remoteInput, pendingIntent);
+ smartReplyContainer.setVisibility(View.VISIBLE);
+ }
+ return smartReplyView;
+ }
+
public void closeRemoteInput() {
if (mHeadsUpRemoteInput != null) {
mHeadsUpRemoteInput.close();
@@ -1459,4 +1548,32 @@ public class NotificationContentView extends FrameLayout {
}
return false;
}
+
+ public boolean shouldClipToRounding(boolean topRounded, boolean bottomRounded) {
+ boolean needsPaddings = shouldClipToRounding(getVisibleType(), topRounded, bottomRounded);
+ if (mUserExpanding) {
+ needsPaddings |= shouldClipToRounding(mTransformationStartVisibleType, topRounded,
+ bottomRounded);
+ }
+ return needsPaddings;
+ }
+
+ private boolean shouldClipToRounding(int visibleType, boolean topRounded,
+ boolean bottomRounded) {
+ NotificationViewWrapper visibleWrapper = getVisibleWrapper(visibleType);
+ if (visibleWrapper == null) {
+ return false;
+ }
+ return visibleWrapper.shouldClipToRounding(topRounded, bottomRounded);
+ }
+
+ public CharSequence getActiveRemoteInputText() {
+ if (mExpandedRemoteInput != null && mExpandedRemoteInput.isActive()) {
+ return mExpandedRemoteInput.getText();
+ }
+ if (mHeadsUpRemoteInput != null && mHeadsUpRemoteInput.isActive()) {
+ return mHeadsUpRemoteInput.getText();
+ }
+ return null;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index d0417b59448d..127f3f918fba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -67,6 +67,7 @@ public class NotificationData {
public static final class Entry {
private static final long LAUNCH_COOLDOWN = 2000;
+ private static final long REMOTE_INPUT_COOLDOWN = 500;
private static final long NOT_LAUNCHED_YET = -LAUNCH_COOLDOWN;
private static final int COLOR_INVALID = 1;
public String key;
@@ -86,10 +87,14 @@ public class NotificationData {
public RemoteViews cachedAmbientContentView;
public CharSequence remoteInputText;
public List<SnoozeCriterion> snoozeCriteria;
+ public int userSentiment = Ranking.USER_SENTIMENT_NEUTRAL;
+
private int mCachedContrastColor = COLOR_INVALID;
private int mCachedContrastColorIsFor = COLOR_INVALID;
private InflationTask mRunningTask = null;
private Throwable mDebugThrowable;
+ public CharSequence remoteInputTextWhenReset;
+ public long lastRemoteInputSent = NOT_LAUNCHED_YET;
public Entry(StatusBarNotification n) {
this.key = n.getKey();
@@ -130,6 +135,10 @@ public class NotificationData {
return SystemClock.elapsedRealtime() < lastFullScreenIntentLaunchTime + LAUNCH_COOLDOWN;
}
+ public boolean hasJustSentRemoteInput() {
+ return SystemClock.elapsedRealtime() < lastRemoteInputSent + REMOTE_INPUT_COOLDOWN;
+ }
+
/**
* Create the icons for a notification
* @param context the context to create the icons with
@@ -263,6 +272,11 @@ public class NotificationData {
public Throwable getDebugThrowable() {
return mDebugThrowable;
}
+
+ public void onRemoteInputInserted() {
+ lastRemoteInputSent = NOT_LAUNCHED_YET;
+ remoteInputTextWhenReset = null;
+ }
}
private final ArrayMap<String, Entry> mEntries = new ArrayMap<>();
@@ -463,6 +477,7 @@ public class NotificationData {
}
entry.channel = getChannel(entry.key);
entry.snoozeCriteria = getSnoozeCriteria(entry.key);
+ entry.userSentiment = mTmpRanking.getUserSentiment();
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java
new file mode 100644
index 000000000000..7360486ac7e9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java
@@ -0,0 +1,979 @@
+/*
+ * 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.statusbar;
+
+import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
+import static com.android.systemui.statusbar.NotificationRemoteInputManager
+ .FORCE_REMOTE_INPUT_HISTORY;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.database.ContentObserver;
+import android.os.Build;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.NotificationStats;
+import android.service.notification.StatusBarNotification;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.EventLog;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.util.NotificationMessagingUtil;
+import com.android.systemui.DejankUtils;
+import com.android.systemui.Dependency;
+import com.android.systemui.Dumpable;
+import com.android.systemui.EventLogTags;
+import com.android.systemui.ForegroundServiceController;
+import com.android.systemui.R;
+import com.android.systemui.UiOffloadThread;
+import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.statusbar.notification.InflationException;
+import com.android.systemui.statusbar.notification.NotificationInflater;
+import com.android.systemui.statusbar.notification.RowInflaterTask;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.util.leak.LeakDetector;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * NotificationEntryManager is responsible for the adding, removing, and updating of notifications.
+ * It also handles tasks such as their inflation and their interaction with other
+ * Notification.*Manager objects.
+ */
+public class NotificationEntryManager implements Dumpable, NotificationInflater.InflationCallback,
+ ExpandableNotificationRow.ExpansionLogger, NotificationUpdateHandler,
+ VisualStabilityManager.Callback {
+ private static final String TAG = "NotificationEntryManager";
+ protected static final boolean DEBUG = false;
+ protected static final boolean ENABLE_HEADS_UP = true;
+ protected static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up";
+
+ protected final NotificationMessagingUtil mMessagingUtil;
+ protected final Context mContext;
+ protected final HashMap<String, NotificationData.Entry> mPendingNotifications = new HashMap<>();
+ protected final NotificationClicker mNotificationClicker = new NotificationClicker();
+ protected final ArraySet<NotificationData.Entry> mHeadsUpEntriesToRemoveOnSwitch =
+ new ArraySet<>();
+
+ // Dependencies:
+ protected final NotificationLockscreenUserManager mLockscreenUserManager =
+ Dependency.get(NotificationLockscreenUserManager.class);
+ protected final NotificationGroupManager mGroupManager =
+ Dependency.get(NotificationGroupManager.class);
+ protected final NotificationGutsManager mGutsManager =
+ Dependency.get(NotificationGutsManager.class);
+ protected final NotificationRemoteInputManager mRemoteInputManager =
+ Dependency.get(NotificationRemoteInputManager.class);
+ protected final NotificationMediaManager mMediaManager =
+ Dependency.get(NotificationMediaManager.class);
+ protected final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
+ protected final DeviceProvisionedController mDeviceProvisionedController =
+ Dependency.get(DeviceProvisionedController.class);
+ protected final VisualStabilityManager mVisualStabilityManager =
+ Dependency.get(VisualStabilityManager.class);
+ protected final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
+ protected final ForegroundServiceController mForegroundServiceController =
+ Dependency.get(ForegroundServiceController.class);
+ protected final NotificationListener mNotificationListener =
+ Dependency.get(NotificationListener.class);
+
+ protected IStatusBarService mBarService;
+ protected NotificationPresenter mPresenter;
+ protected Callback mCallback;
+ protected PowerManager mPowerManager;
+ protected SystemServicesProxy mSystemServicesProxy;
+ protected NotificationListenerService.RankingMap mLatestRankingMap;
+ protected HeadsUpManager mHeadsUpManager;
+ protected NotificationData mNotificationData;
+ protected ContentObserver mHeadsUpObserver;
+ protected boolean mUseHeadsUp = false;
+ protected boolean mDisableNotificationAlerts;
+ protected NotificationListContainer mListContainer;
+
+ private final class NotificationClicker implements View.OnClickListener {
+
+ @Override
+ public void onClick(final View v) {
+ if (!(v instanceof ExpandableNotificationRow)) {
+ Log.e(TAG, "NotificationClicker called on a view that is not a notification row.");
+ return;
+ }
+
+ mPresenter.wakeUpIfDozing(SystemClock.uptimeMillis(), v);
+
+ final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+ final StatusBarNotification sbn = row.getStatusBarNotification();
+ if (sbn == null) {
+ Log.e(TAG, "NotificationClicker called on an unclickable notification,");
+ return;
+ }
+
+ // Check if the notification is displaying the menu, if so slide notification back
+ if (row.getProvider() != null && row.getProvider().isMenuVisible()) {
+ row.animateTranslateNotification(0);
+ return;
+ }
+
+ // Mark notification for one frame.
+ row.setJustClicked(true);
+ DejankUtils.postAfterTraversal(() -> row.setJustClicked(false));
+
+ mCallback.onNotificationClicked(sbn, row);
+ }
+
+ public void register(ExpandableNotificationRow row, StatusBarNotification sbn) {
+ Notification notification = sbn.getNotification();
+ if (notification.contentIntent != null || notification.fullScreenIntent != null) {
+ row.setOnClickListener(this);
+ } else {
+ row.setOnClickListener(null);
+ }
+ }
+ }
+
+ private final DeviceProvisionedController.DeviceProvisionedListener
+ mDeviceProvisionedListener =
+ new DeviceProvisionedController.DeviceProvisionedListener() {
+ @Override
+ public void onDeviceProvisionedChanged() {
+ updateNotifications();
+ }
+ };
+
+ public NotificationListenerService.RankingMap getLatestRankingMap() {
+ return mLatestRankingMap;
+ }
+
+ public void setLatestRankingMap(NotificationListenerService.RankingMap latestRankingMap) {
+ mLatestRankingMap = latestRankingMap;
+ }
+
+ public void setDisableNotificationAlerts(boolean disableNotificationAlerts) {
+ mDisableNotificationAlerts = disableNotificationAlerts;
+ mHeadsUpObserver.onChange(true);
+ }
+
+ public void destroy() {
+ mDeviceProvisionedController.removeCallback(mDeviceProvisionedListener);
+ }
+
+ public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) {
+ if (!isHeadsUp && mHeadsUpEntriesToRemoveOnSwitch.contains(entry)) {
+ removeNotification(entry.key, getLatestRankingMap());
+ mHeadsUpEntriesToRemoveOnSwitch.remove(entry);
+ if (mHeadsUpEntriesToRemoveOnSwitch.isEmpty()) {
+ setLatestRankingMap(null);
+ }
+ } else {
+ updateNotificationRanking(null);
+ }
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("NotificationEntryManager state:");
+ pw.print(" mPendingNotifications=");
+ if (mPendingNotifications.size() == 0) {
+ pw.println("null");
+ } else {
+ for (NotificationData.Entry entry : mPendingNotifications.values()) {
+ pw.println(entry.notification);
+ }
+ }
+ pw.print(" mUseHeadsUp=");
+ pw.println(mUseHeadsUp);
+ }
+
+ public NotificationEntryManager(Context context) {
+ mContext = context;
+ mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ mBarService = IStatusBarService.Stub.asInterface(
+ ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+ mMessagingUtil = new NotificationMessagingUtil(context);
+ mSystemServicesProxy = SystemServicesProxy.getInstance(mContext);
+ }
+
+ public void setUpWithPresenter(NotificationPresenter presenter,
+ NotificationListContainer listContainer, Callback callback,
+ HeadsUpManager headsUpManager) {
+ mPresenter = presenter;
+ mCallback = callback;
+ mNotificationData = new NotificationData(presenter);
+ mHeadsUpManager = headsUpManager;
+ mNotificationData.setHeadsUpManager(mHeadsUpManager);
+ mListContainer = listContainer;
+
+ mHeadsUpObserver = new ContentObserver(mPresenter.getHandler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ boolean wasUsing = mUseHeadsUp;
+ mUseHeadsUp = ENABLE_HEADS_UP && !mDisableNotificationAlerts
+ && Settings.Global.HEADS_UP_OFF != Settings.Global.getInt(
+ mContext.getContentResolver(),
+ Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
+ Settings.Global.HEADS_UP_OFF);
+ Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled"));
+ if (wasUsing != mUseHeadsUp) {
+ if (!mUseHeadsUp) {
+ Log.d(TAG,
+ "dismissing any existing heads up notification on disable event");
+ mHeadsUpManager.releaseAllImmediately();
+ }
+ }
+ }
+ };
+
+ if (ENABLE_HEADS_UP) {
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED),
+ true,
+ mHeadsUpObserver);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(SETTING_HEADS_UP_TICKER), true,
+ mHeadsUpObserver);
+ }
+
+ mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
+
+ mHeadsUpObserver.onChange(true); // set up
+ }
+
+ public NotificationData getNotificationData() {
+ return mNotificationData;
+ }
+
+ public ExpandableNotificationRow.LongPressListener getNotificationLongClicker() {
+ return mGutsManager::openGuts;
+ }
+
+ @Override
+ public void logNotificationExpansion(String key, boolean userAction, boolean expanded) {
+ mUiOffloadThread.submit(() -> {
+ try {
+ mBarService.onNotificationExpansionChanged(key, userAction, expanded);
+ } catch (RemoteException e) {
+ // Ignore.
+ }
+ });
+ }
+
+ @Override
+ public void onReorderingAllowed() {
+ updateNotifications();
+ }
+
+ private boolean shouldSuppressFullScreenIntent(String key) {
+ if (mPresenter.isDeviceInVrMode()) {
+ return true;
+ }
+
+ if (mPowerManager.isInteractive()) {
+ return mNotificationData.shouldSuppressScreenOn(key);
+ } else {
+ return mNotificationData.shouldSuppressScreenOff(key);
+ }
+ }
+
+ private void inflateViews(NotificationData.Entry entry, ViewGroup parent) {
+ PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext,
+ entry.notification.getUser().getIdentifier());
+
+ final StatusBarNotification sbn = entry.notification;
+ if (entry.row != null) {
+ entry.reset();
+ updateNotification(entry, pmUser, sbn, entry.row);
+ } else {
+ new RowInflaterTask().inflate(mContext, parent, entry,
+ row -> {
+ bindRow(entry, pmUser, sbn, row);
+ updateNotification(entry, pmUser, sbn, row);
+ });
+ }
+ }
+
+ private void bindRow(NotificationData.Entry entry, PackageManager pmUser,
+ StatusBarNotification sbn, ExpandableNotificationRow row) {
+ row.setExpansionLogger(this, entry.notification.getKey());
+ row.setGroupManager(mGroupManager);
+ row.setHeadsUpManager(mHeadsUpManager);
+ row.setOnExpandClickListener(mPresenter);
+ row.setInflationCallback(this);
+ row.setLongPressListener(getNotificationLongClicker());
+ mRemoteInputManager.bindRow(row);
+
+ // Get the app name.
+ // Note that Notification.Builder#bindHeaderAppName has similar logic
+ // but since this field is used in the guts, it must be accurate.
+ // Therefore we will only show the application label, or, failing that, the
+ // package name. No substitutions.
+ final String pkg = sbn.getPackageName();
+ String appname = pkg;
+ try {
+ final ApplicationInfo info = pmUser.getApplicationInfo(pkg,
+ PackageManager.MATCH_UNINSTALLED_PACKAGES
+ | PackageManager.MATCH_DISABLED_COMPONENTS);
+ if (info != null) {
+ appname = String.valueOf(pmUser.getApplicationLabel(info));
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // Do nothing
+ }
+ row.setAppName(appname);
+ row.setOnDismissRunnable(() ->
+ performRemoveNotification(row.getStatusBarNotification()));
+ row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
+ if (ENABLE_REMOTE_INPUT) {
+ row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
+ }
+
+ mCallback.onBindRow(entry, pmUser, sbn, row);
+ }
+
+ public void performRemoveNotification(StatusBarNotification n) {
+ NotificationData.Entry entry = mNotificationData.get(n.getKey());
+ mRemoteInputManager.onPerformRemoveNotification(n, entry);
+ final String pkg = n.getPackageName();
+ final String tag = n.getTag();
+ final int id = n.getId();
+ final int userId = n.getUserId();
+ try {
+ int dismissalSurface = NotificationStats.DISMISSAL_SHADE;
+ if (isHeadsUp(n.getKey())) {
+ dismissalSurface = NotificationStats.DISMISSAL_PEEK;
+ } else if (mListContainer.hasPulsingNotifications()) {
+ dismissalSurface = NotificationStats.DISMISSAL_AOD;
+ }
+ mBarService.onNotificationClear(pkg, tag, id, userId, n.getKey(), dismissalSurface);
+ removeNotification(n.getKey(), null);
+
+ } catch (RemoteException ex) {
+ // system process is dead if we're here.
+ }
+
+ mCallback.onPerformRemoveNotification(n);
+ }
+
+ /**
+ * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService
+ * about the failure.
+ *
+ * WARNING: this will call back into us. Don't hold any locks.
+ */
+ void handleNotificationError(StatusBarNotification n, String message) {
+ removeNotification(n.getKey(), null);
+ try {
+ mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(),
+ n.getInitialPid(), message, n.getUserId());
+ } catch (RemoteException ex) {
+ // The end is nigh.
+ }
+ }
+
+ private void abortExistingInflation(String key) {
+ if (mPendingNotifications.containsKey(key)) {
+ NotificationData.Entry entry = mPendingNotifications.get(key);
+ entry.abortTask();
+ mPendingNotifications.remove(key);
+ }
+ NotificationData.Entry addedEntry = mNotificationData.get(key);
+ if (addedEntry != null) {
+ addedEntry.abortTask();
+ }
+ }
+
+ @Override
+ public void handleInflationException(StatusBarNotification notification, Exception e) {
+ handleNotificationError(notification, e.getMessage());
+ }
+
+ private void addEntry(NotificationData.Entry shadeEntry) {
+ boolean isHeadsUped = shouldPeek(shadeEntry);
+ if (isHeadsUped) {
+ mHeadsUpManager.showNotification(shadeEntry);
+ // Mark as seen immediately
+ setNotificationShown(shadeEntry.notification);
+ }
+ addNotificationViews(shadeEntry);
+ mCallback.onNotificationAdded(shadeEntry);
+ }
+
+ @Override
+ public void onAsyncInflationFinished(NotificationData.Entry entry) {
+ mPendingNotifications.remove(entry.key);
+ // If there was an async task started after the removal, we don't want to add it back to
+ // the list, otherwise we might get leaks.
+ boolean isNew = mNotificationData.get(entry.key) == null;
+ if (isNew && !entry.row.isRemoved()) {
+ addEntry(entry);
+ } else if (!isNew && entry.row.hasLowPriorityStateUpdated()) {
+ mVisualStabilityManager.onLowPriorityUpdated(entry);
+ mPresenter.updateNotificationViews();
+ }
+ entry.row.setLowPriorityStateUpdated(false);
+ }
+
+ @Override
+ public void removeNotification(String key, NotificationListenerService.RankingMap ranking) {
+ boolean deferRemoval = false;
+ abortExistingInflation(key);
+ if (mHeadsUpManager.isHeadsUp(key)) {
+ // A cancel() in response to a remote input shouldn't be delayed, as it makes the
+ // sending look longer than it takes.
+ // Also we should not defer the removal if reordering isn't allowed since otherwise
+ // some notifications can't disappear before the panel is closed.
+ boolean ignoreEarliestRemovalTime = mRemoteInputManager.getController().isSpinning(key)
+ && !FORCE_REMOTE_INPUT_HISTORY
+ || !mVisualStabilityManager.isReorderingAllowed();
+ deferRemoval = !mHeadsUpManager.removeNotification(key, ignoreEarliestRemovalTime);
+ }
+ mMediaManager.onNotificationRemoved(key);
+
+ NotificationData.Entry entry = mNotificationData.get(key);
+ if (FORCE_REMOTE_INPUT_HISTORY
+ && shouldKeepForRemoteInput(entry)
+ && entry.row != null && !entry.row.isDismissed()) {
+ StatusBarNotification sbn = entry.notification;
+
+ Notification.Builder b = Notification.Builder
+ .recoverBuilder(mContext, sbn.getNotification().clone());
+ CharSequence[] oldHistory = sbn.getNotification().extras
+ .getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY);
+ CharSequence[] newHistory;
+ if (oldHistory == null) {
+ newHistory = new CharSequence[1];
+ } else {
+ newHistory = new CharSequence[oldHistory.length + 1];
+ System.arraycopy(oldHistory, 0, newHistory, 1, oldHistory.length);
+ }
+ CharSequence remoteInputText = entry.remoteInputText;
+ if (TextUtils.isEmpty(remoteInputText)) {
+ remoteInputText = entry.remoteInputTextWhenReset;
+ }
+ newHistory[0] = String.valueOf(remoteInputText);
+ b.setRemoteInputHistory(newHistory);
+
+ Notification newNotification = b.build();
+
+ // Undo any compatibility view inflation
+ newNotification.contentView = sbn.getNotification().contentView;
+ newNotification.bigContentView = sbn.getNotification().bigContentView;
+ newNotification.headsUpContentView = sbn.getNotification().headsUpContentView;
+
+ StatusBarNotification newSbn = new StatusBarNotification(sbn.getPackageName(),
+ sbn.getOpPkg(),
+ sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(),
+ newNotification, sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime());
+ boolean updated = false;
+ entry.onRemoteInputInserted();
+ try {
+ updateNotificationInternal(newSbn, null);
+ updated = true;
+ } catch (InflationException e) {
+ deferRemoval = false;
+ }
+ if (updated) {
+ Log.w(TAG, "Keeping notification around after sending remote input "+ entry.key);
+ mRemoteInputManager.getKeysKeptForRemoteInput().add(entry.key);
+ return;
+ }
+ }
+ if (deferRemoval) {
+ mLatestRankingMap = ranking;
+ mHeadsUpEntriesToRemoveOnSwitch.add(mHeadsUpManager.getEntry(key));
+ return;
+ }
+
+ if (mRemoteInputManager.onRemoveNotification(entry)) {
+ mLatestRankingMap = ranking;
+ return;
+ }
+
+ if (entry != null && mGutsManager.getExposedGuts() != null
+ && mGutsManager.getExposedGuts() == entry.row.getGuts()
+ && entry.row.getGuts() != null && !entry.row.getGuts().isLeavebehind()) {
+ Log.w(TAG, "Keeping notification because it's showing guts. " + key);
+ mLatestRankingMap = ranking;
+ mGutsManager.setKeyToRemoveOnGutsClosed(key);
+ return;
+ }
+
+ if (entry != null) {
+ mForegroundServiceController.removeNotification(entry.notification);
+ }
+
+ if (entry != null && entry.row != null) {
+ entry.row.setRemoved();
+ mListContainer.cleanUpViewState(entry.row);
+ }
+ // Let's remove the children if this was a summary
+ handleGroupSummaryRemoved(key);
+ StatusBarNotification old = removeNotificationViews(key, ranking);
+
+ mCallback.onNotificationRemoved(key, old);
+ }
+
+ private boolean shouldKeepForRemoteInput(NotificationData.Entry entry) {
+ if (entry == null) {
+ return false;
+ }
+ if (mRemoteInputManager.getController().isSpinning(entry.key)) {
+ return true;
+ }
+ if (entry.hasJustSentRemoteInput()) {
+ return true;
+ }
+ return false;
+ }
+
+ private StatusBarNotification removeNotificationViews(String key,
+ NotificationListenerService.RankingMap ranking) {
+ NotificationData.Entry entry = mNotificationData.remove(key, ranking);
+ if (entry == null) {
+ Log.w(TAG, "removeNotification for unknown key: " + key);
+ return null;
+ }
+ updateNotifications();
+ Dependency.get(LeakDetector.class).trackGarbage(entry);
+ return entry.notification;
+ }
+
+ /**
+ * Ensures that the group children are cancelled immediately when the group summary is cancelled
+ * instead of waiting for the notification manager to send all cancels. Otherwise this could
+ * lead to flickers.
+ *
+ * This also ensures that the animation looks nice and only consists of a single disappear
+ * animation instead of multiple.
+ * @param key the key of the notification was removed
+ *
+ */
+ private void handleGroupSummaryRemoved(String key) {
+ NotificationData.Entry entry = mNotificationData.get(key);
+ if (entry != null && entry.row != null
+ && entry.row.isSummaryWithChildren()) {
+ if (entry.notification.getOverrideGroupKey() != null && !entry.row.isDismissed()) {
+ // We don't want to remove children for autobundled notifications as they are not
+ // always cancelled. We only remove them if they were dismissed by the user.
+ return;
+ }
+ List<ExpandableNotificationRow> notificationChildren =
+ entry.row.getNotificationChildren();
+ for (int i = 0; i < notificationChildren.size(); i++) {
+ ExpandableNotificationRow row = notificationChildren.get(i);
+ if ((row.getStatusBarNotification().getNotification().flags
+ & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
+ // the child is a foreground service notification which we can't remove!
+ continue;
+ }
+ row.setKeepInParent(true);
+ // we need to set this state earlier as otherwise we might generate some weird
+ // animations
+ row.setRemoved();
+ }
+ }
+ }
+
+ public void updateNotificationsOnDensityOrFontScaleChanged() {
+ ArrayList<NotificationData.Entry> activeNotifications =
+ mNotificationData.getActiveNotifications();
+ for (int i = 0; i < activeNotifications.size(); i++) {
+ NotificationData.Entry entry = activeNotifications.get(i);
+ boolean exposedGuts = mGutsManager.getExposedGuts() != null
+ && entry.row.getGuts() == mGutsManager.getExposedGuts();
+ entry.row.onDensityOrFontScaleChanged();
+ if (exposedGuts) {
+ mGutsManager.onDensityOrFontScaleChanged(entry.row);
+ }
+ }
+ }
+
+ private void updateNotification(NotificationData.Entry entry, PackageManager pmUser,
+ StatusBarNotification sbn, ExpandableNotificationRow row) {
+ row.setNeedsRedaction(mLockscreenUserManager.needsRedaction(entry));
+ boolean isLowPriority = mNotificationData.isAmbient(sbn.getKey());
+ boolean isUpdate = mNotificationData.get(entry.key) != null;
+ boolean wasLowPriority = row.isLowPriority();
+ row.setIsLowPriority(isLowPriority);
+ row.setLowPriorityStateUpdated(isUpdate && (wasLowPriority != isLowPriority));
+ // bind the click event to the content area
+ mNotificationClicker.register(row, sbn);
+
+ // Extract target SDK version.
+ try {
+ ApplicationInfo info = pmUser.getApplicationInfo(sbn.getPackageName(), 0);
+ entry.targetSdk = info.targetSdkVersion;
+ } catch (PackageManager.NameNotFoundException ex) {
+ Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex);
+ }
+ row.setLegacy(entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD
+ && entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
+ entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
+ entry.autoRedacted = entry.notification.getNotification().publicVersion == null;
+
+ entry.row = row;
+ entry.row.setOnActivatedListener(mPresenter);
+
+ boolean useIncreasedCollapsedHeight = mMessagingUtil.isImportantMessaging(sbn,
+ mNotificationData.getImportance(sbn.getKey()));
+ boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight
+ && !mPresenter.isPresenterFullyCollapsed();
+ row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
+ row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp);
+ row.updateNotification(entry);
+ }
+
+
+ protected void addNotificationViews(NotificationData.Entry entry) {
+ if (entry == null) {
+ return;
+ }
+ // Add the expanded view and icon.
+ mNotificationData.add(entry);
+ updateNotifications();
+ }
+
+ protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn)
+ throws InflationException {
+ if (DEBUG) {
+ Log.d(TAG, "createNotificationViews(notification=" + sbn);
+ }
+ NotificationData.Entry entry = new NotificationData.Entry(sbn);
+ Dependency.get(LeakDetector.class).trackInstance(entry);
+ entry.createIcons(mContext, sbn);
+ // Construct the expanded view.
+ inflateViews(entry, mListContainer.getViewParentForNotification(entry));
+ return entry;
+ }
+
+ private void addNotificationInternal(StatusBarNotification notification,
+ NotificationListenerService.RankingMap ranking) throws InflationException {
+ String key = notification.getKey();
+ if (DEBUG) Log.d(TAG, "addNotification key=" + key);
+
+ mNotificationData.updateRanking(ranking);
+ NotificationData.Entry shadeEntry = createNotificationViews(notification);
+ boolean isHeadsUped = shouldPeek(shadeEntry);
+ if (!isHeadsUped && notification.getNotification().fullScreenIntent != null) {
+ if (shouldSuppressFullScreenIntent(key)) {
+ if (DEBUG) {
+ Log.d(TAG, "No Fullscreen intent: suppressed by DND: " + key);
+ }
+ } else if (mNotificationData.getImportance(key)
+ < NotificationManager.IMPORTANCE_HIGH) {
+ if (DEBUG) {
+ Log.d(TAG, "No Fullscreen intent: not important enough: "
+ + key);
+ }
+ } else {
+ // Stop screensaver if the notification has a fullscreen intent.
+ // (like an incoming phone call)
+ SystemServicesProxy.getInstance(mContext).awakenDreamsAsync();
+
+ // not immersive & a fullscreen alert should be shown
+ if (DEBUG)
+ Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent");
+ try {
+ EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION,
+ key);
+ notification.getNotification().fullScreenIntent.send();
+ shadeEntry.notifyFullScreenIntentLaunched();
+ mMetricsLogger.count("note_fullscreen", 1);
+ } catch (PendingIntent.CanceledException e) {
+ }
+ }
+ }
+ abortExistingInflation(key);
+
+ mForegroundServiceController.addNotification(notification,
+ mNotificationData.getImportance(key));
+
+ mPendingNotifications.put(key, shadeEntry);
+ }
+
+ @Override
+ public void addNotification(StatusBarNotification notification,
+ NotificationListenerService.RankingMap ranking) {
+ try {
+ addNotificationInternal(notification, ranking);
+ } catch (InflationException e) {
+ handleInflationException(notification, e);
+ }
+ }
+
+ private boolean alertAgain(NotificationData.Entry oldEntry, Notification newNotification) {
+ return oldEntry == null || !oldEntry.hasInterrupted()
+ || (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0;
+ }
+
+ private void updateNotificationInternal(StatusBarNotification notification,
+ NotificationListenerService.RankingMap ranking) throws InflationException {
+ if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")");
+
+ final String key = notification.getKey();
+ abortExistingInflation(key);
+ NotificationData.Entry entry = mNotificationData.get(key);
+ if (entry == null) {
+ return;
+ }
+ mHeadsUpEntriesToRemoveOnSwitch.remove(entry);
+ mRemoteInputManager.onUpdateNotification(entry);
+
+ if (key.equals(mGutsManager.getKeyToRemoveOnGutsClosed())) {
+ mGutsManager.setKeyToRemoveOnGutsClosed(null);
+ Log.w(TAG, "Notification that was kept for guts was updated. " + key);
+ }
+
+ Notification n = notification.getNotification();
+ mNotificationData.updateRanking(ranking);
+
+ final StatusBarNotification oldNotification = entry.notification;
+ entry.notification = notification;
+ mGroupManager.onEntryUpdated(entry, oldNotification);
+
+ entry.updateIcons(mContext, notification);
+ inflateViews(entry, mListContainer.getViewParentForNotification(entry));
+
+ mForegroundServiceController.updateNotification(notification,
+ mNotificationData.getImportance(key));
+
+ boolean shouldPeek = shouldPeek(entry, notification);
+ boolean alertAgain = alertAgain(entry, n);
+
+ updateHeadsUp(key, entry, shouldPeek, alertAgain);
+ updateNotifications();
+
+ if (!notification.isClearable()) {
+ // The user may have performed a dismiss action on the notification, since it's
+ // not clearable we should snap it back.
+ mListContainer.snapViewIfNeeded(entry.row);
+ }
+
+ if (DEBUG) {
+ // Is this for you?
+ boolean isForCurrentUser = mPresenter.isNotificationForCurrentProfiles(notification);
+ Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
+ }
+
+ mCallback.onNotificationUpdated(notification);
+ }
+
+ @Override
+ public void updateNotification(StatusBarNotification notification,
+ NotificationListenerService.RankingMap ranking) {
+ try {
+ updateNotificationInternal(notification, ranking);
+ } catch (InflationException e) {
+ handleInflationException(notification, e);
+ }
+ }
+
+ public void updateNotifications() {
+ mNotificationData.filterAndSort();
+
+ mPresenter.updateNotificationViews();
+ }
+
+ public void updateNotificationRanking(NotificationListenerService.RankingMap ranking) {
+ mNotificationData.updateRanking(ranking);
+ updateNotifications();
+ }
+
+ protected boolean shouldPeek(NotificationData.Entry entry) {
+ return shouldPeek(entry, entry.notification);
+ }
+
+ public boolean shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn) {
+ if (!mUseHeadsUp || mPresenter.isDeviceInVrMode()) {
+ if (DEBUG) Log.d(TAG, "No peeking: no huns or vr mode");
+ return false;
+ }
+
+ if (mNotificationData.shouldFilterOut(sbn)) {
+ if (DEBUG) Log.d(TAG, "No peeking: filtered notification: " + sbn.getKey());
+ return false;
+ }
+
+ boolean inUse = mPowerManager.isScreenOn() && !mSystemServicesProxy.isDreaming();
+
+ if (!inUse && !mPresenter.isDozing()) {
+ if (DEBUG) {
+ Log.d(TAG, "No peeking: not in use: " + sbn.getKey());
+ }
+ return false;
+ }
+
+ if (!mPresenter.isDozing() && mNotificationData.shouldSuppressScreenOn(sbn.getKey())) {
+ if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey());
+ return false;
+ }
+
+ if (mPresenter.isDozing() && mNotificationData.shouldSuppressScreenOff(sbn.getKey())) {
+ if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey());
+ return false;
+ }
+
+ if (entry.hasJustLaunchedFullScreenIntent()) {
+ if (DEBUG) Log.d(TAG, "No peeking: recent fullscreen: " + sbn.getKey());
+ return false;
+ }
+
+ if (isSnoozedPackage(sbn)) {
+ if (DEBUG) Log.d(TAG, "No peeking: snoozed package: " + sbn.getKey());
+ return false;
+ }
+
+ // Allow peeking for DEFAULT notifications only if we're on Ambient Display.
+ int importanceLevel = mPresenter.isDozing() ? NotificationManager.IMPORTANCE_DEFAULT
+ : NotificationManager.IMPORTANCE_HIGH;
+ if (mNotificationData.getImportance(sbn.getKey()) < importanceLevel) {
+ if (DEBUG) Log.d(TAG, "No peeking: unimportant notification: " + sbn.getKey());
+ return false;
+ }
+
+ // Don't peek notifications that are suppressed due to group alert behavior
+ if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) {
+ if (DEBUG) Log.d(TAG, "No peeking: suppressed due to group alert behavior");
+ return false;
+ }
+
+ if (!mCallback.shouldPeek(entry, sbn)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ protected void setNotificationShown(StatusBarNotification n) {
+ setNotificationsShown(new String[]{n.getKey()});
+ }
+
+ protected void setNotificationsShown(String[] keys) {
+ try {
+ mNotificationListener.setNotificationsShown(keys);
+ } catch (RuntimeException e) {
+ Log.d(TAG, "failed setNotificationsShown: ", e);
+ }
+ }
+
+ protected boolean isSnoozedPackage(StatusBarNotification sbn) {
+ return mHeadsUpManager.isSnoozed(sbn.getPackageName());
+ }
+
+ protected void updateHeadsUp(String key, NotificationData.Entry entry, boolean shouldPeek,
+ boolean alertAgain) {
+ final boolean wasHeadsUp = isHeadsUp(key);
+ if (wasHeadsUp) {
+ if (!shouldPeek) {
+ // We don't want this to be interrupting anymore, lets remove it
+ mHeadsUpManager.removeNotification(key, false /* ignoreEarliestRemovalTime */);
+ } else {
+ mHeadsUpManager.updateNotification(entry, alertAgain);
+ }
+ } else if (shouldPeek && alertAgain) {
+ // This notification was updated to be a heads-up, show it!
+ mHeadsUpManager.showNotification(entry);
+ }
+ }
+
+ protected boolean isHeadsUp(String key) {
+ return mHeadsUpManager.isHeadsUp(key);
+ }
+
+ /**
+ * Callback for NotificationEntryManager.
+ */
+ public interface Callback {
+
+ /**
+ * Called when a new entry is created.
+ *
+ * @param shadeEntry entry that was created
+ */
+ void onNotificationAdded(NotificationData.Entry shadeEntry);
+
+ /**
+ * Called when a notification was updated.
+ *
+ * @param notification notification that was updated
+ */
+ void onNotificationUpdated(StatusBarNotification notification);
+
+ /**
+ * Called when a notification was removed.
+ *
+ * @param key key of notification that was removed
+ * @param old StatusBarNotification of the notification before it was removed
+ */
+ void onNotificationRemoved(String key, StatusBarNotification old);
+
+
+ /**
+ * Called when a notification is clicked.
+ *
+ * @param sbn notification that was clicked
+ * @param row row for that notification
+ */
+ void onNotificationClicked(StatusBarNotification sbn, ExpandableNotificationRow row);
+
+ /**
+ * Called when a new notification and row is created.
+ *
+ * @param entry entry for the notification
+ * @param pmUser package manager for user
+ * @param sbn notification
+ * @param row row for the notification
+ */
+ void onBindRow(NotificationData.Entry entry, PackageManager pmUser,
+ StatusBarNotification sbn, ExpandableNotificationRow row);
+
+ /**
+ * Removes a notification immediately.
+ *
+ * @param statusBarNotification notification that is being removed
+ */
+ void onPerformRemoveNotification(StatusBarNotification statusBarNotification);
+
+ /**
+ * Returns true if NotificationEntryManager should peek this notification.
+ *
+ * @param entry entry of the notification that might be peeked
+ * @param sbn notification that might be peeked
+ * @return true if the notification should be peeked
+ */
+ boolean shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
index 54d622b341f1..52776d7e8430 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
@@ -18,46 +18,22 @@ package com.android.systemui.statusbar;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.app.INotificationManager;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.os.Handler;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.service.notification.NotificationListenerService;
-import android.service.notification.StatusBarNotification;
+import android.support.annotation.Nullable;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.View;
import android.view.ViewAnimationUtils;
-import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.SeekBar;
-import android.widget.Switch;
-import android.widget.TextView;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.settingslib.Utils;
+
import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
import com.android.systemui.statusbar.stack.StackStateAnimator;
-import java.util.Set;
-
/**
* The guts of a notification revealed when performing a long press.
*/
@@ -212,6 +188,12 @@ public class NotificationGuts extends FrameLayout {
}
}
+ public void openControls(
+ int x, int y, boolean needsFalsingProtection, @Nullable Runnable onAnimationEnd) {
+ animateOpen(x, y, onAnimationEnd);
+ setExposed(true /* exposed */, needsFalsingProtection);
+ }
+
public void closeControls(boolean leavebehinds, boolean controls, int x, int y, boolean force) {
if (mGutsContent != null) {
if (mGutsContent.isLeavebehind() && leavebehinds) {
@@ -239,6 +221,27 @@ public class NotificationGuts extends FrameLayout {
}
}
+ private void animateOpen(int x, int y, @Nullable Runnable onAnimationEnd) {
+ final double horz = Math.max(getWidth() - x, x);
+ final double vert = Math.max(getHeight() - y, y);
+ final float r = (float) Math.hypot(horz, vert);
+
+ final Animator a
+ = ViewAnimationUtils.createCircularReveal(this, x, y, 0, r);
+ a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+ a.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+ a.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ if (onAnimationEnd != null) {
+ onAnimationEnd.run();
+ }
+ }
+ });
+ a.start();
+ }
+
private void animateClose(int x, int y) {
if (x == -1 || y == -1) {
x = (getLeft() + getRight()) / 2;
@@ -304,7 +307,7 @@ public class NotificationGuts extends FrameLayout {
}
}
- public void setExposed(boolean exposed, boolean needsFalsingProtection) {
+ private void setExposed(boolean exposed, boolean needsFalsingProtection) {
final boolean wasExposed = mExposed;
mExposed = exposed;
mNeedsFalsingProtection = needsFalsingProtection;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java
new file mode 100644
index 000000000000..1aaa3b2593d2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java
@@ -0,0 +1,360 @@
+/*
+ * 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.statusbar;
+
+import static android.service.notification.NotificationListenerService.Ranking
+ .USER_SENTIMENT_NEGATIVE;
+
+import android.app.INotificationManager;
+import android.app.NotificationChannel;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.service.notification.StatusBarNotification;
+import android.util.ArraySet;
+import android.util.Log;
+import android.view.HapticFeedbackConstants;
+import android.view.View;
+import android.view.accessibility.AccessibilityManager;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.systemui.Dependency;
+import com.android.systemui.Dumpable;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
+import com.android.systemui.statusbar.phone.StatusBar;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Handles various NotificationGuts related tasks, such as binding guts to a row, opening and
+ * closing guts, and keeping track of the currently exposed notification guts.
+ */
+public class NotificationGutsManager implements Dumpable {
+ private static final String TAG = "NotificationGutsManager";
+
+ // Must match constant in Settings. Used to highlight preferences when linking to Settings.
+ private static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
+
+ private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
+ private final Set<String> mNonBlockablePkgs;
+ private final Context mContext;
+ private final AccessibilityManager mAccessibilityManager;
+
+ // Dependencies:
+ private final NotificationLockscreenUserManager mLockscreenUserManager =
+ Dependency.get(NotificationLockscreenUserManager.class);
+
+ // which notification is currently being longpress-examined by the user
+ private NotificationGuts mNotificationGutsExposed;
+ private NotificationMenuRowPlugin.MenuItem mGutsMenuItem;
+ protected NotificationPresenter mPresenter;
+ protected NotificationEntryManager mEntryManager;
+ private NotificationListContainer mListContainer;
+ private NotificationInfo.CheckSaveListener mCheckSaveListener;
+ private OnSettingsClickListener mOnSettingsClickListener;
+ private String mKeyToRemoveOnGutsClosed;
+
+ public NotificationGutsManager(Context context) {
+ mContext = context;
+ Resources res = context.getResources();
+
+ mNonBlockablePkgs = new HashSet<>();
+ Collections.addAll(mNonBlockablePkgs, res.getStringArray(
+ com.android.internal.R.array.config_nonBlockableNotificationPackages));
+
+ mAccessibilityManager = (AccessibilityManager)
+ mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
+ }
+
+ public void setUpWithPresenter(NotificationPresenter presenter,
+ NotificationEntryManager entryManager, NotificationListContainer listContainer,
+ NotificationInfo.CheckSaveListener checkSaveListener,
+ OnSettingsClickListener onSettingsClickListener) {
+ mPresenter = presenter;
+ mEntryManager = entryManager;
+ mListContainer = listContainer;
+ mCheckSaveListener = checkSaveListener;
+ mOnSettingsClickListener = onSettingsClickListener;
+ }
+
+ public String getKeyToRemoveOnGutsClosed() {
+ return mKeyToRemoveOnGutsClosed;
+ }
+
+ public void setKeyToRemoveOnGutsClosed(String keyToRemoveOnGutsClosed) {
+ mKeyToRemoveOnGutsClosed = keyToRemoveOnGutsClosed;
+ }
+
+ public void onDensityOrFontScaleChanged(ExpandableNotificationRow row) {
+ setExposedGuts(row.getGuts());
+ bindGuts(row);
+ }
+
+ private void saveAndCloseNotificationMenu(
+ ExpandableNotificationRow row, NotificationGuts guts, View done) {
+ guts.resetFalsingCheck();
+ int[] rowLocation = new int[2];
+ int[] doneLocation = new int[2];
+ row.getLocationOnScreen(rowLocation);
+ done.getLocationOnScreen(doneLocation);
+
+ final int centerX = done.getWidth() / 2;
+ final int centerY = done.getHeight() / 2;
+ final int x = doneLocation[0] - rowLocation[0] + centerX;
+ final int y = doneLocation[1] - rowLocation[1] + centerY;
+ closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
+ true /* removeControls */, x, y, true /* resetMenu */);
+ }
+
+ /**
+ * Sends an intent to open the notification settings for a particular package and optional
+ * channel.
+ */
+ private void startAppNotificationSettingsActivity(String packageName, final int appUid,
+ final NotificationChannel channel, ExpandableNotificationRow row) {
+ final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
+ intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
+ intent.putExtra(Settings.EXTRA_APP_UID, appUid);
+ if (channel != null) {
+ intent.putExtra(EXTRA_FRAGMENT_ARG_KEY, channel.getId());
+ }
+ mPresenter.startNotificationGutsIntent(intent, appUid, row);
+ }
+
+ public void bindGuts(final ExpandableNotificationRow row) {
+ bindGuts(row, mGutsMenuItem);
+ }
+
+ private void bindGuts(final ExpandableNotificationRow row,
+ NotificationMenuRowPlugin.MenuItem item) {
+ row.inflateGuts();
+ row.setGutsView(item);
+ final StatusBarNotification sbn = row.getStatusBarNotification();
+ row.setTag(sbn.getPackageName());
+ final NotificationGuts guts = row.getGuts();
+ guts.setClosedListener((NotificationGuts g) -> {
+ if (!g.willBeRemoved() && !row.isRemoved()) {
+ mListContainer.onHeightChanged(
+ row, !mPresenter.isPresenterFullyCollapsed() /* needsAnimation */);
+ }
+ if (mNotificationGutsExposed == g) {
+ mNotificationGutsExposed = null;
+ mGutsMenuItem = null;
+ }
+ String key = sbn.getKey();
+ if (key.equals(mKeyToRemoveOnGutsClosed)) {
+ mKeyToRemoveOnGutsClosed = null;
+ mEntryManager.removeNotification(key, mEntryManager.getLatestRankingMap());
+ }
+ });
+
+ View gutsView = item.getGutsView();
+ if (gutsView instanceof NotificationSnooze) {
+ NotificationSnooze snoozeGuts = (NotificationSnooze) gutsView;
+ snoozeGuts.setSnoozeListener(mListContainer.getSwipeActionHelper());
+ snoozeGuts.setStatusBarNotification(sbn);
+ snoozeGuts.setSnoozeOptions(row.getEntry().snoozeCriteria);
+ guts.setHeightChangedListener((NotificationGuts g) -> {
+ mListContainer.onHeightChanged(row, row.isShown() /* needsAnimation */);
+ });
+ }
+
+ if (gutsView instanceof NotificationInfo) {
+ final UserHandle userHandle = sbn.getUser();
+ PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext,
+ userHandle.getIdentifier());
+ final INotificationManager iNotificationManager = INotificationManager.Stub.asInterface(
+ ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+ final String pkg = sbn.getPackageName();
+ NotificationInfo info = (NotificationInfo) gutsView;
+ // Settings link is only valid for notifications that specify a user, unless this is the
+ // system user.
+ NotificationInfo.OnSettingsClickListener onSettingsClick = null;
+ if (!userHandle.equals(UserHandle.ALL)
+ || mLockscreenUserManager.getCurrentUserId() == UserHandle.USER_SYSTEM) {
+ onSettingsClick = (View v, NotificationChannel channel, int appUid) -> {
+ mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_INFO);
+ guts.resetFalsingCheck();
+ mOnSettingsClickListener.onClick(sbn.getKey());
+ startAppNotificationSettingsActivity(pkg, appUid, channel, row);
+ };
+ }
+ final NotificationInfo.OnAppSettingsClickListener onAppSettingsClick = (View v,
+ Intent intent) -> {
+ mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_APP_NOTE_SETTINGS);
+ guts.resetFalsingCheck();
+ mPresenter.startNotificationGutsIntent(intent, sbn.getUid(), row);
+ };
+ final View.OnClickListener onDoneClick = (View v) -> {
+ saveAndCloseNotificationMenu(row, guts, v);
+ };
+
+ ArraySet<NotificationChannel> channels = new ArraySet<>();
+ channels.add(row.getEntry().channel);
+ if (row.isSummaryWithChildren()) {
+ // If this is a summary, then add in the children notification channels for the
+ // same user and pkg.
+ final List<ExpandableNotificationRow> childrenRows = row.getNotificationChildren();
+ final int numChildren = childrenRows.size();
+ for (int i = 0; i < numChildren; i++) {
+ final ExpandableNotificationRow childRow = childrenRows.get(i);
+ final NotificationChannel childChannel = childRow.getEntry().channel;
+ final StatusBarNotification childSbn = childRow.getStatusBarNotification();
+ if (childSbn.getUser().equals(userHandle) &&
+ childSbn.getPackageName().equals(pkg)) {
+ channels.add(childChannel);
+ }
+ }
+ }
+ try {
+ info.bindNotification(pmUser, iNotificationManager, pkg, row.getEntry().channel,
+ channels.size(), sbn, mCheckSaveListener, onSettingsClick,
+ onAppSettingsClick, mNonBlockablePkgs,
+ row.getEntry().userSentiment == USER_SENTIMENT_NEGATIVE);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.toString());
+ }
+ }
+ }
+
+ /**
+ * Closes guts or notification menus that might be visible and saves any changes.
+ *
+ * @param removeLeavebehinds true if leavebehinds (e.g. snooze) should be closed.
+ * @param force true if guts should be closed regardless of state (used for snooze only).
+ * @param removeControls true if controls (e.g. info) should be closed.
+ * @param x if closed based on touch location, this is the x touch location.
+ * @param y if closed based on touch location, this is the y touch location.
+ * @param resetMenu if any notification menus that might be revealed should be closed.
+ */
+ public void closeAndSaveGuts(boolean removeLeavebehinds, boolean force, boolean removeControls,
+ int x, int y, boolean resetMenu) {
+ if (mNotificationGutsExposed != null) {
+ mNotificationGutsExposed.closeControls(removeLeavebehinds, removeControls, x, y, force);
+ }
+ if (resetMenu) {
+ mListContainer.resetExposedMenuView(false /* animate */, true /* force */);
+ }
+ }
+
+ /**
+ * Returns the exposed NotificationGuts or null if none are exposed.
+ */
+ public NotificationGuts getExposedGuts() {
+ return mNotificationGutsExposed;
+ }
+
+ public void setExposedGuts(NotificationGuts guts) {
+ mNotificationGutsExposed = guts;
+ }
+
+ /**
+ * Opens guts on the given ExpandableNotificationRow |v|.
+ *
+ * @param v ExpandableNotificationRow to open guts on
+ * @param x x coordinate of origin of circular reveal
+ * @param y y coordinate of origin of circular reveal
+ * @param item MenuItem the guts should display
+ * @return true if guts was opened
+ */
+ public boolean openGuts(View v, int x, int y,
+ NotificationMenuRowPlugin.MenuItem item) {
+ if (!(v instanceof ExpandableNotificationRow)) {
+ return false;
+ }
+
+ if (v.getWindowToken() == null) {
+ Log.e(TAG, "Trying to show notification guts, but not attached to window");
+ return false;
+ }
+
+ final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+ if (row.isDark()) {
+ return false;
+ }
+ v.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+ if (row.areGutsExposed()) {
+ closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
+ true /* removeControls */, -1 /* x */, -1 /* y */,
+ true /* resetMenu */);
+ return false;
+ }
+ bindGuts(row, item);
+ NotificationGuts guts = row.getGuts();
+
+ // Assume we are a status_bar_notification_row
+ if (guts == null) {
+ // This view has no guts. Examples are the more card or the dismiss all view
+ return false;
+ }
+
+ mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_CONTROLS);
+
+ // ensure that it's laid but not visible until actually laid out
+ guts.setVisibility(View.INVISIBLE);
+ // Post to ensure the the guts are properly laid out.
+ guts.post(new Runnable() {
+ @Override
+ public void run() {
+ if (row.getWindowToken() == null) {
+ Log.e(TAG, "Trying to show notification guts, but not attached to "
+ + "window");
+ return;
+ }
+ closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
+ true /* removeControls */, -1 /* x */, -1 /* y */,
+ false /* resetMenu */);
+ guts.setVisibility(View.VISIBLE);
+
+ final boolean needsFalsingProtection =
+ (mPresenter.isPresenterLocked() &&
+ !mAccessibilityManager.isTouchExplorationEnabled());
+ guts.openControls(x, y, needsFalsingProtection, () -> {
+ // Move the notification view back over the menu
+ row.resetTranslation();
+ });
+
+ row.closeRemoteInput();
+ mListContainer.onHeightChanged(row, true /* needsAnimation */);
+ mNotificationGutsExposed = guts;
+ mGutsMenuItem = item;
+ }
+ });
+ return true;
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("NotificationGutsManager state:");
+ pw.print(" mKeyToRemoveOnGutsClosed: ");
+ pw.println(mKeyToRemoveOnGutsClosed);
+ }
+
+ public interface OnSettingsClickListener {
+ void onClick(String key);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
index 43018174de44..112707552e6e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
@@ -128,6 +128,7 @@ public class NotificationHeaderUtil {
mComparators.add(HeaderProcessor.forTextView(mRow,
com.android.internal.R.id.header_text));
mDividers.add(com.android.internal.R.id.header_text_divider);
+ mDividers.add(com.android.internal.R.id.header_text_secondary_divider);
mDividers.add(com.android.internal.R.id.time_divider);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
index 3b23a0c0db89..afe906c28ea5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
@@ -18,6 +18,10 @@ package com.android.systemui.statusbar;
import static android.app.NotificationManager.IMPORTANCE_NONE;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
import android.app.INotificationManager;
import android.app.Notification;
import android.app.NotificationChannel;
@@ -38,15 +42,14 @@ import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.widget.ImageView;
import android.widget.LinearLayout;
-import android.widget.Switch;
import android.widget.TextView;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.Utils;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import java.lang.IllegalArgumentException;
import java.util.List;
import java.util.Set;
@@ -57,24 +60,37 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
private static final String TAG = "InfoGuts";
private INotificationManager mINotificationManager;
+ private PackageManager mPm;
+
private String mPkg;
private String mAppName;
private int mAppUid;
- private List<NotificationChannel> mNotificationChannels;
+ private int mNumNotificationChannels;
private NotificationChannel mSingleNotificationChannel;
+ private int mStartingUserImportance;
+ private int mChosenImportance;
private boolean mIsSingleDefaultChannel;
+ private boolean mNonblockable;
private StatusBarNotification mSbn;
- private int mStartingUserImportance;
+ private AnimatorSet mExpandAnimation;
- private TextView mNumChannelsView;
- private View mChannelDisabledView;
- private TextView mSettingsLinkView;
- private Switch mChannelEnabledSwitch;
private CheckSaveListener mCheckSaveListener;
+ private OnSettingsClickListener mOnSettingsClickListener;
private OnAppSettingsClickListener mAppSettingsClickListener;
- private PackageManager mPm;
-
private NotificationGuts mGutsContainer;
+ private boolean mNegativeUserSentiment;
+
+ private OnClickListener mOnKeepShowing = v -> {
+ closeControls(v);
+ };
+
+ private OnClickListener mOnStopNotifications = v -> {
+ swapContent(false);
+ };
+
+ private OnClickListener mOnUndo = v -> {
+ swapContent(true);
+ };
public NotificationInfo(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -84,7 +100,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
public interface CheckSaveListener {
// Invoked when importance has changed and the NotificationInfo wants to try to save it.
// Listener should run saveImportance unless the change should be canceled.
- void checkSave(Runnable saveImportance);
+ void checkSave(Runnable saveImportance, StatusBarNotification sbn);
}
public interface OnSettingsClickListener {
@@ -98,141 +114,110 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
public void bindNotification(final PackageManager pm,
final INotificationManager iNotificationManager,
final String pkg,
- final List<NotificationChannel> notificationChannels,
- int startingUserImportance,
+ final NotificationChannel notificationChannel,
+ final int numChannels,
final StatusBarNotification sbn,
- OnSettingsClickListener onSettingsClick,
- OnAppSettingsClickListener onAppSettingsClick,
- OnClickListener onDoneClick,
- CheckSaveListener checkSaveListener,
+ final CheckSaveListener checkSaveListener,
+ final OnSettingsClickListener onSettingsClick,
+ final OnAppSettingsClickListener onAppSettingsClick,
final Set<String> nonBlockablePkgs)
throws RemoteException {
+ bindNotification(pm, iNotificationManager, pkg, notificationChannel, numChannels, sbn,
+ checkSaveListener, onSettingsClick, onAppSettingsClick, nonBlockablePkgs,
+ false /* negative sentiment */);
+ }
+
+ public void bindNotification(final PackageManager pm,
+ final INotificationManager iNotificationManager,
+ final String pkg,
+ final NotificationChannel notificationChannel,
+ final int numChannels,
+ final StatusBarNotification sbn,
+ final CheckSaveListener checkSaveListener,
+ final OnSettingsClickListener onSettingsClick,
+ final OnAppSettingsClickListener onAppSettingsClick,
+ final Set<String> nonBlockablePkgs,
+ boolean negativeUserSentiment) throws RemoteException {
mINotificationManager = iNotificationManager;
mPkg = pkg;
- mNotificationChannels = notificationChannels;
- mCheckSaveListener = checkSaveListener;
+ mNumNotificationChannels = numChannels;
mSbn = sbn;
mPm = pm;
mAppSettingsClickListener = onAppSettingsClick;
- mStartingUserImportance = startingUserImportance;
mAppName = mPkg;
- Drawable pkgicon = null;
- CharSequence channelNameText = "";
- ApplicationInfo info = null;
- try {
- info = pm.getApplicationInfo(mPkg,
- PackageManager.MATCH_UNINSTALLED_PACKAGES
- | PackageManager.MATCH_DISABLED_COMPONENTS
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
- | PackageManager.MATCH_DIRECT_BOOT_AWARE);
- if (info != null) {
- mAppUid = sbn.getUid();
- mAppName = String.valueOf(pm.getApplicationLabel(info));
- pkgicon = pm.getApplicationIcon(info);
- }
- } catch (PackageManager.NameNotFoundException e) {
- // app is gone, just show package name and generic icon
- pkgicon = pm.getDefaultActivityIcon();
- }
- ((ImageView) findViewById(R.id.pkgicon)).setImageDrawable(pkgicon);
+ mCheckSaveListener = checkSaveListener;
+ mOnSettingsClickListener = onSettingsClick;
+ mSingleNotificationChannel = notificationChannel;
+ mStartingUserImportance = mChosenImportance = mSingleNotificationChannel.getImportance();
+ mNegativeUserSentiment = negativeUserSentiment;
- int numTotalChannels = iNotificationManager.getNumNotificationChannelsForPackage(
+ int numTotalChannels = mINotificationManager.getNumNotificationChannelsForPackage(
pkg, mAppUid, false /* includeDeleted */);
- if (mNotificationChannels.isEmpty()) {
+ if (mNumNotificationChannels == 0) {
throw new IllegalArgumentException("bindNotification requires at least one channel");
} else {
- if (mNotificationChannels.size() == 1) {
- mSingleNotificationChannel = mNotificationChannels.get(0);
- // Special behavior for the Default channel if no other channels have been defined.
- mIsSingleDefaultChannel =
- (mSingleNotificationChannel.getId()
- .equals(NotificationChannel.DEFAULT_CHANNEL_ID) &&
- numTotalChannels <= 1);
- } else {
- mSingleNotificationChannel = null;
- mIsSingleDefaultChannel = false;
- }
+ // Special behavior for the Default channel if no other channels have been defined.
+ mIsSingleDefaultChannel = mNumNotificationChannels == 1
+ && mSingleNotificationChannel.getId()
+ .equals(NotificationChannel.DEFAULT_CHANNEL_ID)
+ && numTotalChannels <= 1;
}
- boolean nonBlockable = false;
try {
final PackageInfo pkgInfo = pm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES);
if (Utils.isSystemPackage(getResources(), pm, pkgInfo)) {
- final int numChannels = mNotificationChannels.size();
- for (int i = 0; i < numChannels; i++) {
- final NotificationChannel notificationChannel = mNotificationChannels.get(i);
- // If any of the system channels is not blockable, the bundle is nonblockable
- if (!notificationChannel.isBlockableSystem()) {
- nonBlockable = true;
- break;
- }
+ if (mSingleNotificationChannel != null
+ && !mSingleNotificationChannel.isBlockableSystem()) {
+ mNonblockable = true;
}
}
} catch (PackageManager.NameNotFoundException e) {
// unlikely.
}
if (nonBlockablePkgs != null) {
- nonBlockable |= nonBlockablePkgs.contains(pkg);
+ mNonblockable |= nonBlockablePkgs.contains(pkg);
}
+ mNonblockable |= (mNumNotificationChannels > 1);
- String channelsDescText;
- mNumChannelsView = findViewById(R.id.num_channels_desc);
- if (nonBlockable) {
- channelsDescText = mContext.getString(R.string.notification_unblockable_desc);
- } else if (mIsSingleDefaultChannel) {
- channelsDescText = mContext.getString(R.string.notification_default_channel_desc);
- } else {
- switch (mNotificationChannels.size()) {
- case 1:
- channelsDescText = String.format(mContext.getResources().getQuantityString(
- R.plurals.notification_num_channels_desc, numTotalChannels),
- numTotalChannels);
- break;
- case 2:
- channelsDescText = mContext.getString(
- R.string.notification_channels_list_desc_2,
- mNotificationChannels.get(0).getName(),
- mNotificationChannels.get(1).getName());
- break;
- default:
- final int numOthers = mNotificationChannels.size() - 2;
- channelsDescText = String.format(
- mContext.getResources().getQuantityString(
- R.plurals.notification_channels_list_desc_2_and_others,
- numOthers),
- mNotificationChannels.get(0).getName(),
- mNotificationChannels.get(1).getName(),
- numOthers);
+ bindHeader();
+ bindPrompt();
+ bindButtons();
+ }
+
+ private void bindHeader() throws RemoteException {
+ // Package name
+ Drawable pkgicon = null;
+ ApplicationInfo info;
+ try {
+ info = mPm.getApplicationInfo(mPkg,
+ PackageManager.MATCH_UNINSTALLED_PACKAGES
+ | PackageManager.MATCH_DISABLED_COMPONENTS
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE);
+ if (info != null) {
+ mAppUid = mSbn.getUid();
+ mAppName = String.valueOf(mPm.getApplicationLabel(info));
+ pkgicon = mPm.getApplicationIcon(info);
}
+ } catch (PackageManager.NameNotFoundException e) {
+ // app is gone, just show package name and generic icon
+ pkgicon = mPm.getDefaultActivityIcon();
}
- mNumChannelsView.setText(channelsDescText);
-
- if (mSingleNotificationChannel == null) {
- // Multiple channels don't use a channel name for the title.
- channelNameText = mContext.getString(R.string.notification_num_channels,
- mNotificationChannels.size());
- } else if (mIsSingleDefaultChannel || nonBlockable) {
- // If this is the default channel or the app is unblockable,
- // don't use our channel-specific text.
- channelNameText = mContext.getString(R.string.notification_header_default_channel);
- } else {
- channelNameText = mSingleNotificationChannel.getName();
- }
+ ((ImageView) findViewById(R.id.pkgicon)).setImageDrawable(pkgicon);
((TextView) findViewById(R.id.pkgname)).setText(mAppName);
- ((TextView) findViewById(R.id.channel_name)).setText(channelNameText);
// Set group information if this channel has an associated group.
CharSequence groupName = null;
if (mSingleNotificationChannel != null && mSingleNotificationChannel.getGroup() != null) {
final NotificationChannelGroup notificationChannelGroup =
- iNotificationManager.getNotificationChannelGroupForPackage(
- mSingleNotificationChannel.getGroup(), pkg, mAppUid);
+ mINotificationManager.getNotificationChannelGroupForPackage(
+ mSingleNotificationChannel.getGroup(), mPkg, mAppUid);
if (notificationChannelGroup != null) {
groupName = notificationChannelGroup.getName();
}
}
- TextView groupNameView = ((TextView) findViewById(R.id.group_name));
- TextView groupDividerView = ((TextView) findViewById(R.id.pkg_group_divider));
+ TextView groupNameView = findViewById(R.id.group_name);
+ TextView groupDividerView = findViewById(R.id.pkg_group_divider);
if (groupName != null) {
groupNameView.setText(groupName);
groupNameView.setVisibility(View.VISIBLE);
@@ -242,53 +227,58 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
groupDividerView.setVisibility(View.GONE);
}
- bindButtons(nonBlockable);
-
- // Top-level importance group
- mChannelDisabledView = findViewById(R.id.channel_disabled);
- updateSecondaryText();
-
// Settings button.
- final TextView settingsButton = (TextView) findViewById(R.id.more_settings);
- if (mAppUid >= 0 && onSettingsClick != null) {
+ final View settingsButton = findViewById(R.id.info);
+ if (mAppUid >= 0 && mOnSettingsClickListener != null) {
settingsButton.setVisibility(View.VISIBLE);
final int appUidF = mAppUid;
settingsButton.setOnClickListener(
(View view) -> {
- onSettingsClick.onClick(view, mSingleNotificationChannel, appUidF);
+ mOnSettingsClickListener.onClick(view,
+ mNumNotificationChannels > 1 ? null : mSingleNotificationChannel,
+ appUidF);
});
- if (numTotalChannels <= 1 || nonBlockable) {
- settingsButton.setText(R.string.notification_more_settings);
- } else {
- settingsButton.setText(R.string.notification_all_categories);
- }
} else {
settingsButton.setVisibility(View.GONE);
}
+ }
- // Done button.
- final TextView doneButton = (TextView) findViewById(R.id.done);
- doneButton.setText(R.string.notification_done);
- doneButton.setOnClickListener(onDoneClick);
+ private void bindPrompt() {
+ final TextView blockPrompt = findViewById(R.id.block_prompt);
+ bindName();
+ if (mNonblockable) {
+ blockPrompt.setText(R.string.notification_unblockable_desc);
+ } else {
+ if (mNegativeUserSentiment) {
+ blockPrompt.setText(R.string.inline_blocking_helper);
+ } else if (mIsSingleDefaultChannel || mNumNotificationChannels > 1) {
+ blockPrompt.setText(R.string.inline_keep_showing_app);
+ } else {
+ blockPrompt.setText(R.string.inline_keep_showing);
+ }
+ }
+ }
- // Optional settings link
- updateAppSettingsLink();
+ private void bindName() {
+ final TextView channelName = findViewById(R.id.channel_name);
+ if (mIsSingleDefaultChannel || mNumNotificationChannels > 1) {
+ channelName.setVisibility(View.GONE);
+ } else {
+ channelName.setText(mSingleNotificationChannel.getName());
+ }
}
private boolean hasImportanceChanged() {
- return mSingleNotificationChannel != null &&
- mChannelEnabledSwitch != null &&
- mStartingUserImportance != getSelectedImportance();
+ return mSingleNotificationChannel != null && mStartingUserImportance != mChosenImportance;
}
private void saveImportance() {
- if (!hasImportanceChanged()) {
+ if (mNonblockable) {
return;
}
- final int selectedImportance = getSelectedImportance();
MetricsLogger.action(mContext, MetricsEvent.ACTION_SAVE_IMPORTANCE,
- selectedImportance - mStartingUserImportance);
- mSingleNotificationChannel.setImportance(selectedImportance);
+ mChosenImportance - mStartingUserImportance);
+ mSingleNotificationChannel.setImportance(mChosenImportance);
mSingleNotificationChannel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
try {
mINotificationManager.updateNotificationChannelForPackage(
@@ -298,30 +288,78 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
}
}
- private int getSelectedImportance() {
- if (!mChannelEnabledSwitch.isChecked()) {
- return IMPORTANCE_NONE;
+ private void bindButtons() {
+ View block = findViewById(R.id.block);
+ block.setOnClickListener(mOnStopNotifications);
+ TextView keep = findViewById(R.id.keep);
+ keep.setOnClickListener(mOnKeepShowing);
+ findViewById(R.id.undo).setOnClickListener(mOnUndo);
+
+ if (mNonblockable) {
+ keep.setText(R.string.notification_done);
+ block.setVisibility(GONE);
+ }
+
+ // app settings link
+ TextView settingsLinkView = findViewById(R.id.app_settings);
+ Intent settingsIntent = getAppSettingsIntent(mPm, mPkg, mSingleNotificationChannel,
+ mSbn.getId(), mSbn.getTag());
+ if (settingsIntent != null
+ && !TextUtils.isEmpty(mSbn.getNotification().getSettingsText())) {
+ settingsLinkView.setVisibility(View.VISIBLE);
+ settingsLinkView.setText(mContext.getString(R.string.notification_app_settings,
+ mSbn.getNotification().getSettingsText()));
+ settingsLinkView.setOnClickListener((View view) -> {
+ mAppSettingsClickListener.onClick(view, settingsIntent);
+ });
} else {
- return mStartingUserImportance;
+ settingsLinkView.setVisibility(View.GONE);
}
}
- private void bindButtons(final boolean nonBlockable) {
- // Enabled Switch
- mChannelEnabledSwitch = (Switch) findViewById(R.id.channel_enabled_switch);
- mChannelEnabledSwitch.setChecked(
- mStartingUserImportance != IMPORTANCE_NONE);
- final boolean visible = !nonBlockable && mSingleNotificationChannel != null;
- mChannelEnabledSwitch.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
-
- // Callback when checked.
- mChannelEnabledSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
- if (mGutsContainer != null) {
- mGutsContainer.resetFalsingCheck();
+ private void swapContent(boolean showPrompt) {
+ if (mExpandAnimation != null) {
+ mExpandAnimation.cancel();
+ }
+
+ if (showPrompt) {
+ mChosenImportance = mStartingUserImportance;
+ } else {
+ mChosenImportance = IMPORTANCE_NONE;
+ }
+
+ View prompt = findViewById(R.id.prompt);
+ View confirmation = findViewById(R.id.confirmation);
+ ObjectAnimator promptAnim = ObjectAnimator.ofFloat(prompt, View.ALPHA,
+ prompt.getAlpha(), showPrompt ? 1f : 0f);
+ promptAnim.setInterpolator(showPrompt ? Interpolators.ALPHA_IN : Interpolators.ALPHA_OUT);
+ ObjectAnimator confirmAnim = ObjectAnimator.ofFloat(confirmation, View.ALPHA,
+ confirmation.getAlpha(), showPrompt ? 0f : 1f);
+ confirmAnim.setInterpolator(showPrompt ? Interpolators.ALPHA_OUT : Interpolators.ALPHA_IN);
+
+ prompt.setVisibility(showPrompt ? VISIBLE : GONE);
+ confirmation.setVisibility(showPrompt ? GONE : VISIBLE);
+
+ mExpandAnimation = new AnimatorSet();
+ mExpandAnimation.playTogether(promptAnim, confirmAnim);
+ mExpandAnimation.setDuration(150);
+ mExpandAnimation.addListener(new AnimatorListenerAdapter() {
+ boolean cancelled = false;
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ cancelled = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (!cancelled) {
+ prompt.setVisibility(showPrompt ? VISIBLE : GONE);
+ confirmation.setVisibility(showPrompt ? GONE : VISIBLE);
+ }
}
- updateSecondaryText();
- updateAppSettingsLink();
});
+ mExpandAnimation.start();
}
@Override
@@ -339,35 +377,6 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
}
}
- private void updateSecondaryText() {
- final boolean disabled = mSingleNotificationChannel != null &&
- getSelectedImportance() == IMPORTANCE_NONE;
- if (disabled) {
- mChannelDisabledView.setVisibility(View.VISIBLE);
- mNumChannelsView.setVisibility(View.GONE);
- } else {
- mChannelDisabledView.setVisibility(View.GONE);
- mNumChannelsView.setVisibility(mIsSingleDefaultChannel ? View.INVISIBLE : View.VISIBLE);
- }
- }
-
- private void updateAppSettingsLink() {
- mSettingsLinkView = findViewById(R.id.app_settings);
- Intent settingsIntent = getAppSettingsIntent(mPm, mPkg, mSingleNotificationChannel,
- mSbn.getId(), mSbn.getTag());
- if (settingsIntent != null && getSelectedImportance() != IMPORTANCE_NONE
- && !TextUtils.isEmpty(mSbn.getNotification().getSettingsText())) {
- mSettingsLinkView.setVisibility(View.VISIBLE);
- mSettingsLinkView.setText(mContext.getString(R.string.notification_app_settings,
- mSbn.getNotification().getSettingsText()));
- mSettingsLinkView.setOnClickListener((View view) -> {
- mAppSettingsClickListener.onClick(view, settingsIntent);
- });
- } else {
- mSettingsLinkView.setVisibility(View.GONE);
- }
- }
-
private Intent getAppSettingsIntent(PackageManager pm, String packageName,
NotificationChannel channel, int id, String tag) {
Intent intent = new Intent(Intent.ACTION_MAIN)
@@ -390,6 +399,18 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
return intent;
}
+ private void closeControls(View v) {
+ int[] parentLoc = new int[2];
+ int[] targetLoc = new int[2];
+ mGutsContainer.getLocationOnScreen(parentLoc);
+ v.getLocationOnScreen(targetLoc);
+ final int centerX = v.getWidth() / 2;
+ final int centerY = v.getHeight() / 2;
+ final int x = targetLoc[0] - parentLoc[0] + centerX;
+ final int y = targetLoc[1] - parentLoc[1] + centerY;
+ mGutsContainer.closeControls(x, y, true /* save */, false /* force */);
+ }
+
@Override
public void setGutsParent(NotificationGuts guts) {
mGutsContainer = guts;
@@ -397,7 +418,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
@Override
public boolean willBeRemoved() {
- return mChannelEnabledSwitch != null && !mChannelEnabledSwitch.isChecked();
+ return hasImportanceChanged();
}
@Override
@@ -407,9 +428,11 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
@Override
public boolean handleCloseControls(boolean save, boolean force) {
- if (save && hasImportanceChanged()) {
+ // Save regardless of the importance so we can lock the importance field if the user wants
+ // to keep getting notifications
+ if (save) {
if (mCheckSaveListener != null) {
- mCheckSaveListener.checkSave(() -> { saveImportance(); });
+ mCheckSaveListener.checkSave(this::saveImportance, mSbn);
} else {
saveImportance();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListContainer.java
new file mode 100644
index 000000000000..0c19ec091ca6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListContainer.java
@@ -0,0 +1,191 @@
+/*
+ * 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.statusbar;
+
+import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
+
+/**
+ * Interface representing the entity that contains notifications. It can have
+ * notification views added and removed from it, and will manage displaying them to the user.
+ */
+public interface NotificationListContainer {
+
+ /**
+ * Called when a child is being transferred.
+ *
+ * @param childTransferInProgress whether child transfer is in progress
+ */
+ void setChildTransferInProgress(boolean childTransferInProgress);
+
+ /**
+ * Change the position of child to a new location
+ *
+ * @param child the view to change the position for
+ * @param newIndex the new index
+ */
+ void changeViewPosition(View child, int newIndex);
+
+ /**
+ * Called when a child was added to a group.
+ *
+ * @param row row of the group child that was added
+ */
+ void notifyGroupChildAdded(View row);
+
+ /**
+ * Called when a child was removed from a group.
+ *
+ * @param row row of the child that was removed
+ * @param childrenContainer ViewGroup of the group that the child was removed from
+ */
+ void notifyGroupChildRemoved(View row, ViewGroup childrenContainer);
+
+ /**
+ * Generate an animation for an added child view.
+ *
+ * @param child The view to be added.
+ * @param fromMoreCard Whether this add is coming from the "more" card on lockscreen.
+ */
+ void generateAddAnimation(View child, boolean fromMoreCard);
+
+ /**
+ * Generate a child order changed event.
+ */
+ void generateChildOrderChangedEvent();
+
+ /**
+ * Returns the number of children in the NotificationListContainer.
+ *
+ * @return the number of children in the NotificationListContainer
+ */
+ int getContainerChildCount();
+
+ /**
+ * Gets the ith child in the NotificationListContainer.
+ *
+ * @param i ith child to get
+ * @return the ith child in the list container
+ */
+ View getContainerChildAt(int i);
+
+ /**
+ * Remove a view from the container
+ *
+ * @param v view to remove
+ */
+ void removeContainerView(View v);
+
+ /**
+ * Add a view to the container
+ *
+ * @param v view to add
+ */
+ void addContainerView(View v);
+
+ /**
+ * Sets the maximum number of notifications to display.
+ *
+ * @param maxNotifications max number of notifications to display
+ */
+ void setMaxDisplayedNotifications(int maxNotifications);
+
+ /**
+ * Handle snapping a non-dismissable row back if the user tried to dismiss it.
+ *
+ * @param row row to snap back
+ */
+ void snapViewIfNeeded(ExpandableNotificationRow row);
+
+ /**
+ * Get the view parent for a notification entry. For example, NotificationStackScrollLayout.
+ *
+ * @param entry entry to get the view parent for
+ * @return the view parent for entry
+ */
+ ViewGroup getViewParentForNotification(NotificationData.Entry entry);
+
+ /**
+ * Called when the height of an expandable view changes.
+ *
+ * @param view view whose height changed
+ * @param animate whether this change should be animated
+ */
+ void onHeightChanged(ExpandableView view, boolean animate);
+
+ /**
+ * Resets the currently exposed menu view.
+ *
+ * @param animate whether to animate the closing/change of menu view
+ * @param force reset the menu view even if it looks like it is already reset
+ */
+ void resetExposedMenuView(boolean animate, boolean force);
+
+ /**
+ * Returns the NotificationSwipeActionHelper for the NotificationListContainer.
+ *
+ * @return swipe action helper for the list container
+ */
+ NotificationSwipeActionHelper getSwipeActionHelper();
+
+ /**
+ * Called when a notification is removed from the shade. This cleans up the state for a
+ * given view.
+ *
+ * @param view view to clean up view state for
+ */
+ void cleanUpViewState(View view);
+
+ /**
+ * Returns whether an ExpandableNotificationRow is in a visible location or not.
+ *
+ * @param row
+ * @return true if row is in a visible location
+ */
+ boolean isInVisibleLocation(ExpandableNotificationRow row);
+
+ /**
+ * Sets a listener to listen for changes in notification locations.
+ *
+ * @param listener listener to set
+ */
+ void setChildLocationsChangedListener(
+ NotificationLogger.OnChildLocationsChangedListener listener);
+
+ /**
+ * Called when an update to the notification view hierarchy is completed.
+ */
+ default void onNotificationViewUpdateFinished() {}
+
+ /**
+ * Returns true if there are pulsing notifications.
+ *
+ * @return true if has pulsing notifications
+ */
+ boolean hasPulsingNotifications();
+
+ /**
+ * Apply parameters of the expand animation to the layout
+ */
+ default void applyExpandAnimationParams(ExpandAnimationParameters params) {}
+
+ default void setExpandingNotification(ExpandableNotificationRow row) {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
new file mode 100644
index 000000000000..0144f4244a8f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -0,0 +1,145 @@
+/*
+ * 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.statusbar;
+
+import static com.android.systemui.statusbar.RemoteInputController.processForRemoteInput;
+import static com.android.systemui.statusbar.phone.StatusBar.DEBUG;
+import static com.android.systemui.statusbar.phone.StatusBar.ENABLE_CHILD_NOTIFICATIONS;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+import android.util.Log;
+
+import com.android.systemui.Dependency;
+import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins;
+
+/**
+ * This class handles listening to notification updates and passing them along to
+ * NotificationPresenter to be displayed to the user.
+ */
+public class NotificationListener extends NotificationListenerWithPlugins {
+ private static final String TAG = "NotificationListener";
+
+ // Dependencies:
+ private final NotificationRemoteInputManager mRemoteInputManager =
+ Dependency.get(NotificationRemoteInputManager.class);
+
+ private final Context mContext;
+
+ protected NotificationPresenter mPresenter;
+ protected NotificationEntryManager mEntryManager;
+
+ public NotificationListener(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public void onListenerConnected() {
+ if (DEBUG) Log.d(TAG, "onListenerConnected");
+ onPluginConnected();
+ final StatusBarNotification[] notifications = getActiveNotifications();
+ if (notifications == null) {
+ Log.w(TAG, "onListenerConnected unable to get active notifications.");
+ return;
+ }
+ final RankingMap currentRanking = getCurrentRanking();
+ mPresenter.getHandler().post(() -> {
+ for (StatusBarNotification sbn : notifications) {
+ mEntryManager.addNotification(sbn, currentRanking);
+ }
+ });
+ }
+
+ @Override
+ public void onNotificationPosted(final StatusBarNotification sbn,
+ final RankingMap rankingMap) {
+ if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn);
+ if (sbn != null && !onPluginNotificationPosted(sbn, rankingMap)) {
+ mPresenter.getHandler().post(() -> {
+ processForRemoteInput(sbn.getNotification(), mContext);
+ String key = sbn.getKey();
+ mRemoteInputManager.getKeysKeptForRemoteInput().remove(key);
+ boolean isUpdate =
+ mEntryManager.getNotificationData().get(key) != null;
+ // In case we don't allow child notifications, we ignore children of
+ // notifications that have a summary, since` we're not going to show them
+ // anyway. This is true also when the summary is canceled,
+ // because children are automatically canceled by NoMan in that case.
+ if (!ENABLE_CHILD_NOTIFICATIONS
+ && mPresenter.getGroupManager().isChildInGroupWithSummary(sbn)) {
+ if (DEBUG) {
+ Log.d(TAG, "Ignoring group child due to existing summary: " + sbn);
+ }
+
+ // Remove existing notification to avoid stale data.
+ if (isUpdate) {
+ mEntryManager.removeNotification(key, rankingMap);
+ } else {
+ mEntryManager.getNotificationData()
+ .updateRanking(rankingMap);
+ }
+ return;
+ }
+ if (isUpdate) {
+ mEntryManager.updateNotification(sbn, rankingMap);
+ } else {
+ mEntryManager.addNotification(sbn, rankingMap);
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onNotificationRemoved(StatusBarNotification sbn,
+ final RankingMap rankingMap) {
+ if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn);
+ if (sbn != null && !onPluginNotificationRemoved(sbn, rankingMap)) {
+ final String key = sbn.getKey();
+ mPresenter.getHandler().post(() -> {
+ mEntryManager.removeNotification(key, rankingMap);
+ });
+ }
+ }
+
+ @Override
+ public void onNotificationRankingUpdate(final RankingMap rankingMap) {
+ if (DEBUG) Log.d(TAG, "onRankingUpdate");
+ if (rankingMap != null) {
+ RankingMap r = onPluginRankingUpdate(rankingMap);
+ mPresenter.getHandler().post(() -> {
+ mEntryManager.updateNotificationRanking(r);
+ });
+ }
+ }
+
+ public void setUpWithPresenter(NotificationPresenter presenter,
+ NotificationEntryManager entryManager) {
+ mPresenter = presenter;
+ mEntryManager = entryManager;
+
+ try {
+ registerAsSystemService(mContext,
+ new ComponentName(mContext.getPackageName(), getClass().getCanonicalName()),
+ UserHandle.USER_ALL);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to register notification listener", e);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
new file mode 100644
index 000000000000..47fa1d2167f0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
@@ -0,0 +1,469 @@
+/*
+ * 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.statusbar;
+
+import android.app.ActivityManager;
+import android.app.Notification;
+import android.app.admin.DevicePolicyManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.content.pm.UserInfo;
+import android.database.ContentObserver;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.service.notification.StatusBarNotification;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.Dependency;
+import com.android.systemui.Dumpable;
+import com.android.systemui.OverviewProxyService;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.List;
+
+/**
+ * Handles keeping track of the current user, profiles, and various things related to hiding
+ * contents, redacting notifications, and the lockscreen.
+ */
+public class NotificationLockscreenUserManager implements Dumpable {
+ private static final String TAG = "LockscreenUserManager";
+ private static final boolean ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT = false;
+ public static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
+ public static final String NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION
+ = "com.android.systemui.statusbar.work_challenge_unlocked_notification_action";
+
+ private final DevicePolicyManager mDevicePolicyManager;
+ private final SparseBooleanArray mLockscreenPublicMode = new SparseBooleanArray();
+ private final SparseBooleanArray mUsersAllowingPrivateNotifications = new SparseBooleanArray();
+ private final SparseBooleanArray mUsersAllowingNotifications = new SparseBooleanArray();
+ private final DeviceProvisionedController mDeviceProvisionedController =
+ Dependency.get(DeviceProvisionedController.class);
+ private final UserManager mUserManager;
+ private final IStatusBarService mBarService;
+
+ private boolean mShowLockscreenNotifications;
+ private boolean mAllowLockscreenRemoteInput;
+
+ protected final BroadcastReceiver mAllUsersReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+
+ if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(action) &&
+ isCurrentProfile(getSendingUserId())) {
+ mUsersAllowingPrivateNotifications.clear();
+ updateLockscreenNotificationSetting();
+ mEntryManager.updateNotifications();
+ } else if (Intent.ACTION_DEVICE_LOCKED_CHANGED.equals(action)) {
+ if (userId != mCurrentUserId && isCurrentProfile(userId)) {
+ mPresenter.onWorkChallengeChanged();
+ }
+ }
+ }
+ };
+
+ protected final BroadcastReceiver mBaseBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (Intent.ACTION_USER_SWITCHED.equals(action)) {
+ mCurrentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+ updateCurrentProfilesCache();
+ Log.v(TAG, "userId " + mCurrentUserId + " is in the house");
+
+ updateLockscreenNotificationSetting();
+
+ mPresenter.onUserSwitched(mCurrentUserId);
+ } else if (Intent.ACTION_USER_ADDED.equals(action)) {
+ updateCurrentProfilesCache();
+ } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
+ // Start the overview connection to the launcher service
+ Dependency.get(OverviewProxyService.class).startConnectionToCurrentUser();
+ } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
+ try {
+ final int lastResumedActivityUserId =
+ ActivityManager.getService().getLastResumedActivityUserId();
+ if (mUserManager.isManagedProfile(lastResumedActivityUserId)) {
+ showForegroundManagedProfileActivityToast();
+ }
+ } catch (RemoteException e) {
+ // Abandon hope activity manager not running.
+ }
+ } else if (NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION.equals(action)) {
+ final IntentSender intentSender = intent.getParcelableExtra(Intent.EXTRA_INTENT);
+ final String notificationKey = intent.getStringExtra(Intent.EXTRA_INDEX);
+ if (intentSender != null) {
+ try {
+ mContext.startIntentSender(intentSender, null, 0, 0, 0);
+ } catch (IntentSender.SendIntentException e) {
+ /* ignore */
+ }
+ }
+ if (notificationKey != null) {
+ try {
+ mBarService.onNotificationClick(notificationKey);
+ } catch (RemoteException e) {
+ /* ignore */
+ }
+ }
+ }
+ }
+ };
+
+ protected final Context mContext;
+ protected final SparseArray<UserInfo> mCurrentProfiles = new SparseArray<>();
+
+ protected int mCurrentUserId = 0;
+ protected NotificationPresenter mPresenter;
+ protected NotificationEntryManager mEntryManager;
+ protected ContentObserver mLockscreenSettingsObserver;
+ protected ContentObserver mSettingsObserver;
+
+ public NotificationLockscreenUserManager(Context context) {
+ mContext = context;
+ mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService(
+ Context.DEVICE_POLICY_SERVICE);
+ mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ mCurrentUserId = ActivityManager.getCurrentUser();
+ mBarService = IStatusBarService.Stub.asInterface(
+ ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+ }
+
+ public void setUpWithPresenter(NotificationPresenter presenter,
+ NotificationEntryManager entryManager) {
+ mPresenter = presenter;
+ mEntryManager = entryManager;
+
+ mLockscreenSettingsObserver = new ContentObserver(mPresenter.getHandler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ // We don't know which user changed LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS or
+ // LOCK_SCREEN_SHOW_NOTIFICATIONS, so we just dump our cache ...
+ mUsersAllowingPrivateNotifications.clear();
+ mUsersAllowingNotifications.clear();
+ // ... and refresh all the notifications
+ updateLockscreenNotificationSetting();
+ mEntryManager.updateNotifications();
+ }
+ };
+
+ mSettingsObserver = new ContentObserver(mPresenter.getHandler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ updateLockscreenNotificationSetting();
+ if (mDeviceProvisionedController.isDeviceProvisioned()) {
+ mEntryManager.updateNotifications();
+ }
+ }
+ };
+
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS), false,
+ mLockscreenSettingsObserver,
+ UserHandle.USER_ALL);
+
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS),
+ true,
+ mLockscreenSettingsObserver,
+ UserHandle.USER_ALL);
+
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false,
+ mSettingsObserver);
+
+ if (ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT) {
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT),
+ false,
+ mSettingsObserver,
+ UserHandle.USER_ALL);
+ }
+
+ IntentFilter allUsersFilter = new IntentFilter();
+ allUsersFilter.addAction(
+ DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
+ allUsersFilter.addAction(Intent.ACTION_DEVICE_LOCKED_CHANGED);
+ mContext.registerReceiverAsUser(mAllUsersReceiver, UserHandle.ALL, allUsersFilter,
+ null, null);
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_USER_SWITCHED);
+ filter.addAction(Intent.ACTION_USER_ADDED);
+ filter.addAction(Intent.ACTION_USER_PRESENT);
+ filter.addAction(Intent.ACTION_USER_UNLOCKED);
+ mContext.registerReceiver(mBaseBroadcastReceiver, filter);
+
+ IntentFilter internalFilter = new IntentFilter();
+ internalFilter.addAction(NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION);
+ mContext.registerReceiver(mBaseBroadcastReceiver, internalFilter, PERMISSION_SELF, null);
+
+ updateCurrentProfilesCache();
+
+ mSettingsObserver.onChange(false); // set up
+ }
+
+ private void showForegroundManagedProfileActivityToast() {
+ Toast toast = Toast.makeText(mContext,
+ R.string.managed_profile_foreground_toast,
+ Toast.LENGTH_SHORT);
+ TextView text = toast.getView().findViewById(android.R.id.message);
+ text.setCompoundDrawablesRelativeWithIntrinsicBounds(
+ R.drawable.stat_sys_managed_profile_status, 0, 0, 0);
+ int paddingPx = mContext.getResources().getDimensionPixelSize(
+ R.dimen.managed_profile_toast_padding);
+ text.setCompoundDrawablePadding(paddingPx);
+ toast.show();
+ }
+
+ public boolean shouldShowLockscreenNotifications() {
+ return mShowLockscreenNotifications;
+ }
+
+ public boolean shouldAllowLockscreenRemoteInput() {
+ return mAllowLockscreenRemoteInput;
+ }
+
+ public boolean isCurrentProfile(int userId) {
+ synchronized (mCurrentProfiles) {
+ return userId == UserHandle.USER_ALL || mCurrentProfiles.get(userId) != null;
+ }
+ }
+
+ /**
+ * Returns true if we're on a secure lockscreen and the user wants to hide notification data.
+ * If so, notifications should be hidden.
+ */
+ public boolean shouldHideNotifications(int userId) {
+ return isLockscreenPublicMode(userId) && !userAllowsNotificationsInPublic(userId)
+ || (userId != mCurrentUserId && shouldHideNotifications(mCurrentUserId));
+ }
+
+ /**
+ * Returns true if we're on a secure lockscreen and the user wants to hide notifications via
+ * package-specific override.
+ */
+ public boolean shouldHideNotifications(String key) {
+ if (mEntryManager == null) {
+ Log.wtf(TAG, "mEntryManager was null!", new Throwable());
+ return true;
+ }
+ return isLockscreenPublicMode(mCurrentUserId)
+ && mEntryManager.getNotificationData().getVisibilityOverride(key) ==
+ Notification.VISIBILITY_SECRET;
+ }
+
+ public boolean shouldShowOnKeyguard(StatusBarNotification sbn) {
+ if (mEntryManager == null) {
+ Log.wtf(TAG, "mEntryManager was null!", new Throwable());
+ return false;
+ }
+ return mShowLockscreenNotifications
+ && !mEntryManager.getNotificationData().isAmbient(sbn.getKey());
+ }
+
+ private void setShowLockscreenNotifications(boolean show) {
+ mShowLockscreenNotifications = show;
+ }
+
+ private void setLockscreenAllowRemoteInput(boolean allowLockscreenRemoteInput) {
+ mAllowLockscreenRemoteInput = allowLockscreenRemoteInput;
+ }
+
+ protected void updateLockscreenNotificationSetting() {
+ final boolean show = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
+ 1,
+ mCurrentUserId) != 0;
+ final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures(
+ null /* admin */, mCurrentUserId);
+ final boolean allowedByDpm = (dpmFlags
+ & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0;
+
+ setShowLockscreenNotifications(show && allowedByDpm);
+
+ if (ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT) {
+ final boolean remoteInput = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT,
+ 0,
+ mCurrentUserId) != 0;
+ final boolean remoteInputDpm =
+ (dpmFlags & DevicePolicyManager.KEYGUARD_DISABLE_REMOTE_INPUT) == 0;
+
+ setLockscreenAllowRemoteInput(remoteInput && remoteInputDpm);
+ } else {
+ setLockscreenAllowRemoteInput(false);
+ }
+ }
+
+ /**
+ * Has the given user chosen to allow their private (full) notifications to be shown even
+ * when the lockscreen is in "public" (secure & locked) mode?
+ */
+ public boolean userAllowsPrivateNotificationsInPublic(int userHandle) {
+ if (userHandle == UserHandle.USER_ALL) {
+ return true;
+ }
+
+ if (mUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
+ final boolean allowedByUser = 0 != Settings.Secure.getIntForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, userHandle);
+ final boolean allowedByDpm = adminAllowsUnredactedNotifications(userHandle);
+ final boolean allowed = allowedByUser && allowedByDpm;
+ mUsersAllowingPrivateNotifications.append(userHandle, allowed);
+ return allowed;
+ }
+
+ return mUsersAllowingPrivateNotifications.get(userHandle);
+ }
+
+ private boolean adminAllowsUnredactedNotifications(int userHandle) {
+ if (userHandle == UserHandle.USER_ALL) {
+ return true;
+ }
+ final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures(null /* admin */,
+ userHandle);
+ return (dpmFlags & DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS) == 0;
+ }
+
+ /**
+ * Save the current "public" (locked and secure) state of the lockscreen.
+ */
+ public void setLockscreenPublicMode(boolean publicMode, int userId) {
+ mLockscreenPublicMode.put(userId, publicMode);
+ }
+
+ public boolean isLockscreenPublicMode(int userId) {
+ if (userId == UserHandle.USER_ALL) {
+ return mLockscreenPublicMode.get(mCurrentUserId, false);
+ }
+ return mLockscreenPublicMode.get(userId, false);
+ }
+
+ /**
+ * Has the given user chosen to allow notifications to be shown even when the lockscreen is in
+ * "public" (secure & locked) mode?
+ */
+ private boolean userAllowsNotificationsInPublic(int userHandle) {
+ if (userHandle == UserHandle.USER_ALL) {
+ return true;
+ }
+
+ if (mUsersAllowingNotifications.indexOfKey(userHandle) < 0) {
+ final boolean allowed = 0 != Settings.Secure.getIntForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, userHandle);
+ mUsersAllowingNotifications.append(userHandle, allowed);
+ return allowed;
+ }
+
+ return mUsersAllowingNotifications.get(userHandle);
+ }
+
+ /** @return true if the entry needs redaction when on the lockscreen. */
+ public boolean needsRedaction(NotificationData.Entry ent) {
+ int userId = ent.notification.getUserId();
+
+ boolean currentUserWantsRedaction = !userAllowsPrivateNotificationsInPublic(mCurrentUserId);
+ boolean notiUserWantsRedaction = !userAllowsPrivateNotificationsInPublic(userId);
+ boolean redactedLockscreen = currentUserWantsRedaction || notiUserWantsRedaction;
+
+ boolean notificationRequestsRedaction =
+ ent.notification.getNotification().visibility == Notification.VISIBILITY_PRIVATE;
+ boolean userForcesRedaction = packageHasVisibilityOverride(ent.notification.getKey());
+
+ return userForcesRedaction || notificationRequestsRedaction && redactedLockscreen;
+ }
+
+ private boolean packageHasVisibilityOverride(String key) {
+ if (mEntryManager == null) {
+ Log.wtf(TAG, "mEntryManager was null!", new Throwable());
+ return true;
+ }
+ return mEntryManager.getNotificationData().getVisibilityOverride(key) ==
+ Notification.VISIBILITY_PRIVATE;
+ }
+
+
+ private void updateCurrentProfilesCache() {
+ synchronized (mCurrentProfiles) {
+ mCurrentProfiles.clear();
+ if (mUserManager != null) {
+ for (UserInfo user : mUserManager.getProfiles(mCurrentUserId)) {
+ mCurrentProfiles.put(user.id, user);
+ }
+ }
+ }
+ }
+
+ public boolean isAnyProfilePublicMode() {
+ for (int i = mCurrentProfiles.size() - 1; i >= 0; i--) {
+ if (isLockscreenPublicMode(mCurrentProfiles.valueAt(i).id)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns the current user id. This can change if the user is switched.
+ */
+ public int getCurrentUserId() {
+ return mCurrentUserId;
+ }
+
+ public SparseArray<UserInfo> getCurrentProfiles() {
+ return mCurrentProfiles;
+ }
+
+ public void destroy() {
+ mContext.unregisterReceiver(mBaseBroadcastReceiver);
+ mContext.unregisterReceiver(mAllUsersReceiver);
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("NotificationLockscreenUserManager state:");
+ pw.print(" mCurrentUserId=");
+ pw.println(mCurrentUserId);
+ pw.print(" mShowLockscreenNotifications=");
+ pw.println(mShowLockscreenNotifications);
+ pw.print(" mAllowLockscreenRemoteInput=");
+ pw.println(mAllowLockscreenRemoteInput);
+ pw.print(" mCurrentProfiles=");
+ for (int i = mCurrentProfiles.size() - 1; i >= 0; i--) {
+ final int userId = mCurrentProfiles.valueAt(i).id;
+ pw.print("" + userId + " ");
+ }
+ pw.println();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLogger.java
new file mode 100644
index 000000000000..4225f83c5b11
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLogger.java
@@ -0,0 +1,227 @@
+/*
+ * 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.statusbar;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.service.notification.NotificationListenerService;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.Dependency;
+import com.android.systemui.UiOffloadThread;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ * Handles notification logging, in particular, logging which notifications are visible and which
+ * are not.
+ */
+public class NotificationLogger {
+ private static final String TAG = "NotificationLogger";
+
+ /** The minimum delay in ms between reports of notification visibility. */
+ private static final int VISIBILITY_REPORT_MIN_DELAY_MS = 500;
+
+ /** Keys of notifications currently visible to the user. */
+ private final ArraySet<NotificationVisibility> mCurrentlyVisibleNotifications =
+ new ArraySet<>();
+
+ // Dependencies:
+ private final NotificationListenerService mNotificationListener =
+ Dependency.get(NotificationListener.class);
+ private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
+
+ protected NotificationEntryManager mEntryManager;
+ protected Handler mHandler = new Handler();
+ protected IStatusBarService mBarService;
+ private long mLastVisibilityReportUptimeMs;
+ private NotificationListContainer mListContainer;
+
+ protected final OnChildLocationsChangedListener mNotificationLocationsChangedListener =
+ new OnChildLocationsChangedListener() {
+ @Override
+ public void onChildLocationsChanged() {
+ if (mHandler.hasCallbacks(mVisibilityReporter)) {
+ // Visibilities will be reported when the existing
+ // callback is executed.
+ return;
+ }
+ // Calculate when we're allowed to run the visibility
+ // reporter. Note that this timestamp might already have
+ // passed. That's OK, the callback will just be executed
+ // ASAP.
+ long nextReportUptimeMs =
+ mLastVisibilityReportUptimeMs + VISIBILITY_REPORT_MIN_DELAY_MS;
+ mHandler.postAtTime(mVisibilityReporter, nextReportUptimeMs);
+ }
+ };
+
+ // Tracks notifications currently visible in mNotificationStackScroller and
+ // emits visibility events via NoMan on changes.
+ protected final Runnable mVisibilityReporter = new Runnable() {
+ private final ArraySet<NotificationVisibility> mTmpNewlyVisibleNotifications =
+ new ArraySet<>();
+ private final ArraySet<NotificationVisibility> mTmpCurrentlyVisibleNotifications =
+ new ArraySet<>();
+ private final ArraySet<NotificationVisibility> mTmpNoLongerVisibleNotifications =
+ new ArraySet<>();
+
+ @Override
+ public void run() {
+ mLastVisibilityReportUptimeMs = SystemClock.uptimeMillis();
+
+ // 1. Loop over mNotificationData entries:
+ // A. Keep list of visible notifications.
+ // B. Keep list of previously hidden, now visible notifications.
+ // 2. Compute no-longer visible notifications by removing currently
+ // visible notifications from the set of previously visible
+ // notifications.
+ // 3. Report newly visible and no-longer visible notifications.
+ // 4. Keep currently visible notifications for next report.
+ ArrayList<NotificationData.Entry> activeNotifications = mEntryManager
+ .getNotificationData().getActiveNotifications();
+ int N = activeNotifications.size();
+ for (int i = 0; i < N; i++) {
+ NotificationData.Entry entry = activeNotifications.get(i);
+ String key = entry.notification.getKey();
+ boolean isVisible = mListContainer.isInVisibleLocation(entry.row);
+ NotificationVisibility visObj = NotificationVisibility.obtain(key, i, isVisible);
+ boolean previouslyVisible = mCurrentlyVisibleNotifications.contains(visObj);
+ if (isVisible) {
+ // Build new set of visible notifications.
+ mTmpCurrentlyVisibleNotifications.add(visObj);
+ if (!previouslyVisible) {
+ mTmpNewlyVisibleNotifications.add(visObj);
+ }
+ } else {
+ // release object
+ visObj.recycle();
+ }
+ }
+ mTmpNoLongerVisibleNotifications.addAll(mCurrentlyVisibleNotifications);
+ mTmpNoLongerVisibleNotifications.removeAll(mTmpCurrentlyVisibleNotifications);
+
+ logNotificationVisibilityChanges(
+ mTmpNewlyVisibleNotifications, mTmpNoLongerVisibleNotifications);
+
+ recycleAllVisibilityObjects(mCurrentlyVisibleNotifications);
+ mCurrentlyVisibleNotifications.addAll(mTmpCurrentlyVisibleNotifications);
+
+ recycleAllVisibilityObjects(mTmpNoLongerVisibleNotifications);
+ mTmpCurrentlyVisibleNotifications.clear();
+ mTmpNewlyVisibleNotifications.clear();
+ mTmpNoLongerVisibleNotifications.clear();
+ }
+ };
+
+ public NotificationLogger() {
+ mBarService = IStatusBarService.Stub.asInterface(
+ ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+ }
+
+ public void setUpWithEntryManager(NotificationEntryManager entryManager,
+ NotificationListContainer listContainer) {
+ mEntryManager = entryManager;
+ mListContainer = listContainer;
+ }
+
+ public void stopNotificationLogging() {
+ // Report all notifications as invisible and turn down the
+ // reporter.
+ if (!mCurrentlyVisibleNotifications.isEmpty()) {
+ logNotificationVisibilityChanges(
+ Collections.emptyList(), mCurrentlyVisibleNotifications);
+ recycleAllVisibilityObjects(mCurrentlyVisibleNotifications);
+ }
+ mHandler.removeCallbacks(mVisibilityReporter);
+ mListContainer.setChildLocationsChangedListener(null);
+ }
+
+ public void startNotificationLogging() {
+ mListContainer.setChildLocationsChangedListener(mNotificationLocationsChangedListener);
+ // Some transitions like mVisibleToUser=false -> mVisibleToUser=true don't
+ // cause the scroller to emit child location events. Hence generate
+ // one ourselves to guarantee that we're reporting visible
+ // notifications.
+ // (Note that in cases where the scroller does emit events, this
+ // additional event doesn't break anything.)
+ mNotificationLocationsChangedListener.onChildLocationsChanged();
+ }
+
+ private void logNotificationVisibilityChanges(
+ Collection<NotificationVisibility> newlyVisible,
+ Collection<NotificationVisibility> noLongerVisible) {
+ if (newlyVisible.isEmpty() && noLongerVisible.isEmpty()) {
+ return;
+ }
+ NotificationVisibility[] newlyVisibleAr =
+ newlyVisible.toArray(new NotificationVisibility[newlyVisible.size()]);
+ NotificationVisibility[] noLongerVisibleAr =
+ noLongerVisible.toArray(new NotificationVisibility[noLongerVisible.size()]);
+ mUiOffloadThread.submit(() -> {
+ try {
+ mBarService.onNotificationVisibilityChanged(newlyVisibleAr, noLongerVisibleAr);
+ } catch (RemoteException e) {
+ // Ignore.
+ }
+
+ final int N = newlyVisible.size();
+ if (N > 0) {
+ String[] newlyVisibleKeyAr = new String[N];
+ for (int i = 0; i < N; i++) {
+ newlyVisibleKeyAr[i] = newlyVisibleAr[i].key;
+ }
+
+ // TODO: Call NotificationEntryManager to do this, once it exists.
+ // TODO: Consider not catching all runtime exceptions here.
+ try {
+ mNotificationListener.setNotificationsShown(newlyVisibleKeyAr);
+ } catch (RuntimeException e) {
+ Log.d(TAG, "failed setNotificationsShown: ", e);
+ }
+ }
+ });
+ }
+
+ private void recycleAllVisibilityObjects(ArraySet<NotificationVisibility> array) {
+ final int N = array.size();
+ for (int i = 0 ; i < N; i++) {
+ array.valueAt(i).recycle();
+ }
+ array.clear();
+ }
+
+ @VisibleForTesting
+ public Runnable getVisibilityReporter() {
+ return mVisibilityReporter;
+ }
+
+ /**
+ * A listener that is notified when some child locations might have changed.
+ */
+ public interface OnChildLocationsChangedListener {
+ void onChildLocationsChanged();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
new file mode 100644
index 000000000000..852239a2143b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -0,0 +1,267 @@
+/*
+ * 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.statusbar;
+
+import android.app.Notification;
+import android.content.Context;
+import android.media.MediaMetadata;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.media.session.MediaSessionManager;
+import android.media.session.PlaybackState;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.systemui.Dumpable;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Handles tasks and state related to media notifications. For example, there is a 'current' media
+ * notification, which this class keeps track of.
+ */
+public class NotificationMediaManager implements Dumpable {
+ private static final String TAG = "NotificationMediaManager";
+ public static final boolean DEBUG_MEDIA = false;
+
+ private final Context mContext;
+ private final MediaSessionManager mMediaSessionManager;
+
+ protected NotificationPresenter mPresenter;
+ protected NotificationEntryManager mEntryManager;
+ private MediaController mMediaController;
+ private String mMediaNotificationKey;
+ private MediaMetadata mMediaMetadata;
+
+ private final MediaController.Callback mMediaListener = new MediaController.Callback() {
+ @Override
+ public void onPlaybackStateChanged(PlaybackState state) {
+ super.onPlaybackStateChanged(state);
+ if (DEBUG_MEDIA) {
+ Log.v(TAG, "DEBUG_MEDIA: onPlaybackStateChanged: " + state);
+ }
+ if (state != null) {
+ if (!isPlaybackActive(state.getState())) {
+ clearCurrentMediaNotification();
+ mPresenter.updateMediaMetaData(true, true);
+ }
+ }
+ }
+
+ @Override
+ public void onMetadataChanged(MediaMetadata metadata) {
+ super.onMetadataChanged(metadata);
+ if (DEBUG_MEDIA) {
+ Log.v(TAG, "DEBUG_MEDIA: onMetadataChanged: " + metadata);
+ }
+ mMediaMetadata = metadata;
+ mPresenter.updateMediaMetaData(true, true);
+ }
+ };
+
+ public NotificationMediaManager(Context context) {
+ mContext = context;
+ mMediaSessionManager
+ = (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
+ // TODO: use MediaSessionManager.SessionListener to hook us up to future updates
+ // in session state
+ }
+
+ public void setUpWithPresenter(NotificationPresenter presenter,
+ NotificationEntryManager entryManager) {
+ mPresenter = presenter;
+ mEntryManager = entryManager;
+ }
+
+ public void onNotificationRemoved(String key) {
+ if (key.equals(mMediaNotificationKey)) {
+ clearCurrentMediaNotification();
+ mPresenter.updateMediaMetaData(true, true);
+ }
+ }
+
+ public String getMediaNotificationKey() {
+ return mMediaNotificationKey;
+ }
+
+ public MediaMetadata getMediaMetadata() {
+ return mMediaMetadata;
+ }
+
+ public void findAndUpdateMediaNotifications() {
+ boolean metaDataChanged = false;
+
+ synchronized (mEntryManager.getNotificationData()) {
+ ArrayList<NotificationData.Entry> activeNotifications = mEntryManager
+ .getNotificationData().getActiveNotifications();
+ final int N = activeNotifications.size();
+
+ // Promote the media notification with a controller in 'playing' state, if any.
+ NotificationData.Entry mediaNotification = null;
+ MediaController controller = null;
+ for (int i = 0; i < N; i++) {
+ final NotificationData.Entry entry = activeNotifications.get(i);
+
+ if (isMediaNotification(entry)) {
+ final MediaSession.Token token =
+ entry.notification.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.notification.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?
+ final List<MediaController> sessions
+ = mMediaSessionManager.getActiveSessionsForUser(
+ null,
+ UserHandle.USER_ALL);
+
+ for (MediaController aController : sessions) {
+ if (PlaybackState.STATE_PLAYING ==
+ getMediaControllerPlaybackState(aController)) {
+ // now to see if we have one like this
+ final String pkg = aController.getPackageName();
+
+ for (int i = 0; i < N; i++) {
+ final NotificationData.Entry entry = activeNotifications.get(i);
+ if (entry.notification.getPackageName().equals(pkg)) {
+ if (DEBUG_MEDIA) {
+ Log.v(TAG, "DEBUG_MEDIA: found controller matching "
+ + entry.notification.getKey());
+ }
+ controller = aController;
+ mediaNotification = entry;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (controller != null && !sameSessions(mMediaController, controller)) {
+ // We have a new media session
+ clearCurrentMediaNotification();
+ mMediaController = controller;
+ mMediaController.registerCallback(mMediaListener);
+ mMediaMetadata = mMediaController.getMetadata();
+ if (DEBUG_MEDIA) {
+ Log.v(TAG, "DEBUG_MEDIA: insert listener, receive metadata: "
+ + mMediaMetadata);
+ }
+
+ if (mediaNotification != null) {
+ mMediaNotificationKey = mediaNotification.notification.getKey();
+ if (DEBUG_MEDIA) {
+ Log.v(TAG, "DEBUG_MEDIA: Found new media notification: key="
+ + mMediaNotificationKey + " controller=" + mMediaController);
+ }
+ }
+ metaDataChanged = true;
+ }
+ }
+
+ if (metaDataChanged) {
+ mEntryManager.updateNotifications();
+ }
+ mPresenter.updateMediaMetaData(metaDataChanged, true);
+ }
+
+ public void clearCurrentMediaNotification() {
+ mMediaNotificationKey = null;
+ mMediaMetadata = null;
+ if (mMediaController != null) {
+ if (DEBUG_MEDIA) {
+ Log.v(TAG, "DEBUG_MEDIA: Disconnecting from old controller: "
+ + mMediaController.getPackageName());
+ }
+ mMediaController.unregisterCallback(mMediaListener);
+ }
+ mMediaController = null;
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.print(" mMediaSessionManager=");
+ pw.println(mMediaSessionManager);
+ pw.print(" mMediaNotificationKey=");
+ pw.println(mMediaNotificationKey);
+ pw.print(" mMediaController=");
+ pw.print(mMediaController);
+ if (mMediaController != null) {
+ pw.print(" state=" + mMediaController.getPlaybackState());
+ }
+ pw.println();
+ pw.print(" mMediaMetadata=");
+ pw.print(mMediaMetadata);
+ if (mMediaMetadata != null) {
+ pw.print(" title=" + mMediaMetadata.getText(MediaMetadata.METADATA_KEY_TITLE));
+ }
+ pw.println();
+ }
+
+ private boolean isPlaybackActive(int state) {
+ return state != PlaybackState.STATE_STOPPED && state != PlaybackState.STATE_ERROR
+ && state != PlaybackState.STATE_NONE;
+ }
+
+ private boolean sameSessions(MediaController a, MediaController b) {
+ if (a == b) {
+ return true;
+ }
+ if (a == null) {
+ return false;
+ }
+ return a.controlsSameSession(b);
+ }
+
+ private int getMediaControllerPlaybackState(MediaController controller) {
+ if (controller != null) {
+ final PlaybackState playbackState = controller.getPlaybackState();
+ if (playbackState != null) {
+ return playbackState.getState();
+ }
+ }
+ return PlaybackState.STATE_NONE;
+ }
+
+ private boolean isMediaNotification(NotificationData.Entry entry) {
+ // TODO: confirm that there's a valid media key
+ return entry.getExpandedContentView() != null &&
+ entry.getExpandedContentView()
+ .findViewById(com.android.internal.R.id.media_actions) != null;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
index 99b4b0799cba..037eeb2d298b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
@@ -88,6 +88,7 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
private float mHorizSpaceForIcon = -1;
private int mVertSpaceForIcons = -1;
private int mIconPadding = -1;
+ private int mSidePadding;
private float mAlpha = 0f;
private float mPrevX;
@@ -175,7 +176,6 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
final Resources res = mContext.getResources();
mHorizSpaceForIcon = res.getDimensionPixelSize(R.dimen.notification_menu_icon_size);
mVertSpaceForIcons = res.getDimensionPixelSize(R.dimen.notification_min_height);
- mIconPadding = res.getDimensionPixelSize(R.dimen.notification_menu_icon_padding);
mMenuItems.clear();
// Construct the menu items based on the notification
if (mParent != null && mParent.getStatusBarNotification() != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
new file mode 100644
index 000000000000..5263bb4173a3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
@@ -0,0 +1,120 @@
+/*
+ * 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.statusbar;
+
+import android.content.Intent;
+import android.os.Handler;
+import android.view.View;
+
+/**
+ * An abstraction of something that presents notifications, e.g. StatusBar. Contains methods
+ * for both querying the state of the system (some modularised piece of functionality may
+ * want to act differently based on e.g. whether the presenter is visible to the user or not) and
+ * for affecting the state of the system (e.g. starting an intent, given that the presenter may
+ * want to perform some action before doing so).
+ */
+public interface NotificationPresenter extends NotificationData.Environment,
+ NotificationRemoteInputManager.Callback,
+ ExpandableNotificationRow.OnExpandClickListener,
+ ActivatableNotificationView.OnActivatedListener,
+ NotificationEntryManager.Callback {
+ /**
+ * Returns true if the presenter is not visible. For example, it may not be necessary to do
+ * animations if this returns true.
+ */
+ boolean isPresenterFullyCollapsed();
+
+ /**
+ * Returns true if the presenter is locked. For example, if the keyguard is active.
+ */
+ boolean isPresenterLocked();
+
+ /**
+ * Runs the given intent. The presenter may want to run some animations or close itself when
+ * this happens.
+ */
+ void startNotificationGutsIntent(Intent intent, int appUid, ExpandableNotificationRow row);
+
+ /**
+ * Returns the Handler for NotificationPresenter.
+ */
+ Handler getHandler();
+
+ /**
+ * Refresh or remove lockscreen artwork from media metadata or the lockscreen wallpaper.
+ */
+ void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation);
+
+ /**
+ * Called when the locked status of the device is changed for a work profile.
+ */
+ void onWorkChallengeChanged();
+
+ /**
+ * Called when the current user changes.
+ * @param newUserId new user id
+ */
+ void onUserSwitched(int newUserId);
+
+ /**
+ * Gets the NotificationLockscreenUserManager for this Presenter.
+ */
+ NotificationLockscreenUserManager getNotificationLockscreenUserManager();
+
+ /**
+ * Wakes the device up if dozing.
+ *
+ * @param time the time when the request to wake up was issued
+ * @param where which view caused this wake up request
+ */
+ void wakeUpIfDozing(long time, View where);
+
+ /**
+ * True if the device currently requires a PIN, pattern, or password to unlock.
+ *
+ * @param userId user id to query about
+ * @return true iff the device is locked
+ */
+ boolean isDeviceLocked(int userId);
+
+ /**
+ * @return true iff the device is in vr mode
+ */
+ boolean isDeviceInVrMode();
+
+ /**
+ * Updates the visual representation of the notifications.
+ */
+ void updateNotificationViews();
+
+ /**
+ * @return true iff the device is dozing
+ */
+ boolean isDozing();
+
+ /**
+ * Returns the maximum number of notifications to show while locked.
+ *
+ * @param recompute whether something has changed that means we should recompute this value
+ * @return the maximum number of notifications to show while locked
+ */
+ int getMaxNotificationsWhileLocked(boolean recompute);
+
+ /**
+ * Called when the row states are updated by NotificationViewHierarchyManager.
+ */
+ void onUpdateRowStates();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
new file mode 100644
index 000000000000..3c480d80dea8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -0,0 +1,451 @@
+/*
+ * 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.statusbar;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
+
+import android.app.ActivityManager;
+import android.app.PendingIntent;
+import android.app.RemoteInput;
+import android.content.Context;
+import android.content.Intent;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.UserManager;
+import android.service.notification.StatusBarNotification;
+import android.util.ArraySet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.widget.RemoteViews;
+import android.widget.TextView;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.Dependency;
+import com.android.systemui.Dumpable;
+import com.android.systemui.statusbar.policy.RemoteInputView;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Set;
+
+/**
+ * Class for handling remote input state over a set of notifications. This class handles things
+ * like keeping notifications temporarily that were cancelled as a response to a remote input
+ * interaction, keeping track of notifications to remove when NotificationPresenter is collapsed,
+ * and handling clicks on remote views.
+ */
+public class NotificationRemoteInputManager implements Dumpable {
+ public static final boolean ENABLE_REMOTE_INPUT =
+ SystemProperties.getBoolean("debug.enable_remote_input", true);
+ public static final boolean FORCE_REMOTE_INPUT_HISTORY =
+ SystemProperties.getBoolean("debug.force_remoteinput_history", true);
+ private static final boolean DEBUG = false;
+ private static final String TAG = "NotificationRemoteInputManager";
+
+ /**
+ * How long to wait before auto-dismissing a notification that was kept for remote input, and
+ * has now sent a remote input. We auto-dismiss, because the app may not see a reason to cancel
+ * these given that they technically don't exist anymore. We wait a bit in case the app issues
+ * an update.
+ */
+ private static final int REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY = 200;
+
+ protected final ArraySet<NotificationData.Entry> mRemoteInputEntriesToRemoveOnCollapse =
+ new ArraySet<>();
+
+ // Dependencies:
+ protected final NotificationLockscreenUserManager mLockscreenUserManager =
+ Dependency.get(NotificationLockscreenUserManager.class);
+
+ /**
+ * Notifications with keys in this set are not actually around anymore. We kept them around
+ * when they were canceled in response to a remote input interaction. This allows us to show
+ * what you replied and allows you to continue typing into it.
+ */
+ protected final ArraySet<String> mKeysKeptForRemoteInput = new ArraySet<>();
+ protected final Context mContext;
+ private final UserManager mUserManager;
+
+ protected RemoteInputController mRemoteInputController;
+ protected NotificationPresenter mPresenter;
+ protected NotificationEntryManager mEntryManager;
+ protected IStatusBarService mBarService;
+ protected Callback mCallback;
+
+ private final RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() {
+
+ @Override
+ public boolean onClickHandler(
+ final View view, final PendingIntent pendingIntent, final Intent fillInIntent) {
+ mPresenter.wakeUpIfDozing(SystemClock.uptimeMillis(), view);
+
+ if (handleRemoteInput(view, pendingIntent)) {
+ return true;
+ }
+
+ if (DEBUG) {
+ Log.v(TAG, "Notification click handler invoked for intent: " + pendingIntent);
+ }
+ logActionClick(view);
+ // The intent we are sending is for the application, which
+ // won't have permission to immediately start an activity after
+ // the user switches to home. We know it is safe to do at this
+ // point, so make sure new activity switches are now allowed.
+ try {
+ ActivityManager.getService().resumeAppSwitches();
+ } catch (RemoteException e) {
+ }
+ return mCallback.handleRemoteViewClick(view, pendingIntent, fillInIntent,
+ () -> superOnClickHandler(view, pendingIntent, fillInIntent));
+ }
+
+ private void logActionClick(View view) {
+ ViewParent parent = view.getParent();
+ String key = getNotificationKeyForParent(parent);
+ if (key == null) {
+ Log.w(TAG, "Couldn't determine notification for click.");
+ return;
+ }
+ int index = -1;
+ // If this is a default template, determine the index of the button.
+ if (view.getId() == com.android.internal.R.id.action0 &&
+ parent != null && parent instanceof ViewGroup) {
+ ViewGroup actionGroup = (ViewGroup) parent;
+ index = actionGroup.indexOfChild(view);
+ }
+ try {
+ mBarService.onNotificationActionClick(key, index);
+ } catch (RemoteException e) {
+ // Ignore
+ }
+ }
+
+ private String getNotificationKeyForParent(ViewParent parent) {
+ while (parent != null) {
+ if (parent instanceof ExpandableNotificationRow) {
+ return ((ExpandableNotificationRow) parent)
+ .getStatusBarNotification().getKey();
+ }
+ parent = parent.getParent();
+ }
+ return null;
+ }
+
+ private boolean superOnClickHandler(View view, PendingIntent pendingIntent,
+ Intent fillInIntent) {
+ return super.onClickHandler(view, pendingIntent, fillInIntent,
+ WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
+ }
+
+ private boolean handleRemoteInput(View view, PendingIntent pendingIntent) {
+ if (mCallback.shouldHandleRemoteInput(view, pendingIntent)) {
+ return true;
+ }
+
+ Object tag = view.getTag(com.android.internal.R.id.remote_input_tag);
+ RemoteInput[] inputs = null;
+ if (tag instanceof RemoteInput[]) {
+ inputs = (RemoteInput[]) tag;
+ }
+
+ if (inputs == null) {
+ return false;
+ }
+
+ RemoteInput input = null;
+
+ for (RemoteInput i : inputs) {
+ if (i.getAllowFreeFormInput()) {
+ input = i;
+ }
+ }
+
+ if (input == null) {
+ return false;
+ }
+
+ ViewParent p = view.getParent();
+ RemoteInputView riv = null;
+ while (p != null) {
+ if (p instanceof View) {
+ View pv = (View) p;
+ if (pv.isRootNamespace()) {
+ riv = findRemoteInputView(pv);
+ break;
+ }
+ }
+ p = p.getParent();
+ }
+ ExpandableNotificationRow row = null;
+ while (p != null) {
+ if (p instanceof ExpandableNotificationRow) {
+ row = (ExpandableNotificationRow) p;
+ break;
+ }
+ p = p.getParent();
+ }
+
+ if (row == null) {
+ return false;
+ }
+
+ row.setUserExpanded(true);
+
+ if (!mLockscreenUserManager.shouldAllowLockscreenRemoteInput()) {
+ final int userId = pendingIntent.getCreatorUserHandle().getIdentifier();
+ if (mLockscreenUserManager.isLockscreenPublicMode(userId)) {
+ mCallback.onLockedRemoteInput(row, view);
+ return true;
+ }
+ if (mUserManager.getUserInfo(userId).isManagedProfile()
+ && mPresenter.isDeviceLocked(userId)) {
+ mCallback.onLockedWorkRemoteInput(userId, row, view);
+ return true;
+ }
+ }
+
+ if (riv == null) {
+ riv = findRemoteInputView(row.getPrivateLayout().getExpandedChild());
+ if (riv == null) {
+ return false;
+ }
+ if (!row.getPrivateLayout().getExpandedChild().isShown()) {
+ mCallback.onMakeExpandedVisibleForRemoteInput(row, view);
+ return true;
+ }
+ }
+
+ int width = view.getWidth();
+ if (view instanceof TextView) {
+ // Center the reveal on the text which might be off-center from the TextView
+ TextView tv = (TextView) view;
+ if (tv.getLayout() != null) {
+ int innerWidth = (int) tv.getLayout().getLineWidth(0);
+ innerWidth += tv.getCompoundPaddingLeft() + tv.getCompoundPaddingRight();
+ width = Math.min(width, innerWidth);
+ }
+ }
+ int cx = view.getLeft() + width / 2;
+ int cy = view.getTop() + view.getHeight() / 2;
+ int w = riv.getWidth();
+ int h = riv.getHeight();
+ int r = Math.max(
+ Math.max(cx + cy, cx + (h - cy)),
+ Math.max((w - cx) + cy, (w - cx) + (h - cy)));
+
+ riv.setRevealParameters(cx, cy, r);
+ riv.setPendingIntent(pendingIntent);
+ riv.setRemoteInput(inputs, input);
+ riv.focusAnimated();
+
+ return true;
+ }
+
+ private RemoteInputView findRemoteInputView(View v) {
+ if (v == null) {
+ return null;
+ }
+ return (RemoteInputView) v.findViewWithTag(RemoteInputView.VIEW_TAG);
+ }
+ };
+
+ public NotificationRemoteInputManager(Context context) {
+ mContext = context;
+ mBarService = IStatusBarService.Stub.asInterface(
+ ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+ mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ }
+
+ public void setUpWithPresenter(NotificationPresenter presenter,
+ NotificationEntryManager entryManager,
+ Callback callback,
+ RemoteInputController.Delegate delegate) {
+ mPresenter = presenter;
+ mEntryManager = entryManager;
+ mCallback = callback;
+ mRemoteInputController = new RemoteInputController(delegate);
+ mRemoteInputController.addCallback(new RemoteInputController.Callback() {
+ @Override
+ public void onRemoteInputSent(NotificationData.Entry entry) {
+ if (FORCE_REMOTE_INPUT_HISTORY && mKeysKeptForRemoteInput.contains(entry.key)) {
+ mEntryManager.removeNotification(entry.key, null);
+ } else if (mRemoteInputEntriesToRemoveOnCollapse.contains(entry)) {
+ // We're currently holding onto this notification, but from the apps point of
+ // view it is already canceled, so we'll need to cancel it on the apps behalf
+ // after sending - unless the app posts an update in the mean time, so wait a
+ // bit.
+ mPresenter.getHandler().postDelayed(() -> {
+ if (mRemoteInputEntriesToRemoveOnCollapse.remove(entry)) {
+ mEntryManager.removeNotification(entry.key, null);
+ }
+ }, REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY);
+ }
+ try {
+ mBarService.onNotificationDirectReplied(entry.notification.getKey());
+ } catch (RemoteException e) {
+ // Nothing to do, system going down
+ }
+ }
+ });
+
+ }
+
+ public RemoteInputController getController() {
+ return mRemoteInputController;
+ }
+
+ public void onUpdateNotification(NotificationData.Entry entry) {
+ mRemoteInputEntriesToRemoveOnCollapse.remove(entry);
+ }
+
+ /**
+ * Returns true if NotificationRemoteInputManager wants to keep this notification around.
+ *
+ * @param entry notification being removed
+ */
+ public boolean onRemoveNotification(NotificationData.Entry entry) {
+ if (entry != null && mRemoteInputController.isRemoteInputActive(entry)
+ && (entry.row != null && !entry.row.isDismissed())) {
+ mRemoteInputEntriesToRemoveOnCollapse.add(entry);
+ return true;
+ }
+ return false;
+ }
+
+ public void onPerformRemoveNotification(StatusBarNotification n,
+ NotificationData.Entry entry) {
+ if (mRemoteInputController.isRemoteInputActive(entry)) {
+ mRemoteInputController.removeRemoteInput(entry, null);
+ }
+ if (FORCE_REMOTE_INPUT_HISTORY
+ && mKeysKeptForRemoteInput.contains(n.getKey())) {
+ mKeysKeptForRemoteInput.remove(n.getKey());
+ }
+ }
+
+ public void removeRemoteInputEntriesKeptUntilCollapsed() {
+ for (int i = 0; i < mRemoteInputEntriesToRemoveOnCollapse.size(); i++) {
+ NotificationData.Entry entry = mRemoteInputEntriesToRemoveOnCollapse.valueAt(i);
+ mRemoteInputController.removeRemoteInput(entry, null);
+ mEntryManager.removeNotification(entry.key, mEntryManager.getLatestRankingMap());
+ }
+ mRemoteInputEntriesToRemoveOnCollapse.clear();
+ }
+
+ public void checkRemoteInputOutside(MotionEvent event) {
+ if (event.getAction() == MotionEvent.ACTION_OUTSIDE // touch outside the source bar
+ && event.getX() == 0 && event.getY() == 0 // a touch outside both bars
+ && mRemoteInputController.isRemoteInputActive()) {
+ mRemoteInputController.closeRemoteInputs();
+ }
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("NotificationRemoteInputManager state:");
+ pw.print(" mRemoteInputEntriesToRemoveOnCollapse: ");
+ pw.println(mRemoteInputEntriesToRemoveOnCollapse);
+ pw.print(" mKeysKeptForRemoteInput: ");
+ pw.println(mKeysKeptForRemoteInput);
+ }
+
+ public void bindRow(ExpandableNotificationRow row) {
+ row.setRemoteInputController(mRemoteInputController);
+ row.setRemoteViewClickHandler(mOnClickHandler);
+ }
+
+ public Set<String> getKeysKeptForRemoteInput() {
+ return mKeysKeptForRemoteInput;
+ }
+
+ @VisibleForTesting
+ public Set<NotificationData.Entry> getRemoteInputEntriesToRemoveOnCollapse() {
+ return mRemoteInputEntriesToRemoveOnCollapse;
+ }
+
+ /**
+ * Callback for various remote input related events, or for providing information that
+ * NotificationRemoteInputManager needs to know to decide what to do.
+ */
+ public interface Callback {
+
+ /**
+ * Called when remote input was activated but the device is locked.
+ *
+ * @param row
+ * @param clicked
+ */
+ void onLockedRemoteInput(ExpandableNotificationRow row, View clicked);
+
+ /**
+ * Called when remote input was activated but the device is locked and in a managed profile.
+ *
+ * @param userId
+ * @param row
+ * @param clicked
+ */
+ void onLockedWorkRemoteInput(int userId, ExpandableNotificationRow row, View clicked);
+
+ /**
+ * Called when a row should be made expanded for the purposes of remote input.
+ *
+ * @param row
+ * @param clickedView
+ */
+ void onMakeExpandedVisibleForRemoteInput(ExpandableNotificationRow row, View clickedView);
+
+ /**
+ * Return whether or not remote input should be handled for this view.
+ *
+ * @param view
+ * @param pendingIntent
+ * @return true iff the remote input should be handled
+ */
+ boolean shouldHandleRemoteInput(View view, PendingIntent pendingIntent);
+
+ /**
+ * Performs any special handling for a remote view click. The default behaviour can be
+ * called through the defaultHandler parameter.
+ *
+ * @param view
+ * @param pendingIntent
+ * @param fillInIntent
+ * @param defaultHandler
+ * @return true iff the click was handled
+ */
+ boolean handleRemoteViewClick(View view, PendingIntent pendingIntent, Intent fillInIntent,
+ ClickHandler defaultHandler);
+ }
+
+ /**
+ * Helper interface meant for passing the default on click behaviour to NotificationPresenter,
+ * so it may do its own handling before invoking the default behaviour.
+ */
+ public interface ClickHandler {
+ /**
+ * Tries to handle a click on a remote view.
+ *
+ * @return true iff the click was handled
+ */
+ boolean handleClick();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 5557dde7a5d6..cad956cd602a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -25,6 +25,7 @@ import android.content.res.Resources;
import android.graphics.Rect;
import android.os.SystemProperties;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
@@ -56,6 +57,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
private static final boolean ICON_ANMATIONS_WHILE_SCROLLING
= SystemProperties.getBoolean("debug.icon_scroll_animations", true);
private static final int TAG_CONTINUOUS_CLIPPING = R.id.continuous_clipping_tag;
+ private static final String TAG = "NotificationShelf";
private ViewInvertHelper mViewInvertHelper;
private boolean mDark;
private NotificationIconContainer mShelfIcons;
@@ -82,9 +84,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
private boolean mNoAnimationsInThisFrame;
private boolean mAnimationsEnabled = true;
private boolean mShowNotificationShelf;
- private boolean mVibrationOnAnimation;
- private boolean mUserTouchingScreen;
- private boolean mTouchActive;
+ private float mFirstElementRoundness;
public NotificationShelf(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -100,25 +100,14 @@ public class NotificationShelf extends ActivatableNotificationView implements
setClipToActualHeight(false);
setClipChildren(false);
setClipToPadding(false);
- mShelfIcons.setShowAllIcons(false);
- mVibrationOnAnimation = mContext.getResources().getBoolean(
- R.bool.config_vibrateOnIconAnimation);
- updateVibrationOnAnimation();
+ mShelfIcons.setIsStaticLayout(false);
mViewInvertHelper = new ViewInvertHelper(mShelfIcons,
NotificationPanelView.DOZE_ANIMATION_DURATION);
mShelfState = new ShelfState();
+ setBottomRoundness(1.0f, false /* animate */);
initDimens();
}
- private void updateVibrationOnAnimation() {
- mShelfIcons.setVibrateOnAnimation(mVibrationOnAnimation && mTouchActive);
- }
-
- public void setTouchActive(boolean touchActive) {
- mTouchActive = touchActive;
- updateVibrationOnAnimation();
- }
-
public void bind(AmbientState ambientState, NotificationStackScrollLayout hostLayout) {
mAmbientState = ambientState;
mHostLayout = hostLayout;
@@ -252,6 +241,8 @@ public class NotificationShelf extends ActivatableNotificationView implements
boolean expandingAnimated = mAmbientState.isExpansionChanging()
&& !mAmbientState.isPanelTracking();
int baseZHeight = mAmbientState.getBaseZHeight();
+ int backgroundTop = 0;
+ float firstElementRoundness = 0.0f;
while (notificationIndex < mHostLayout.getChildCount()) {
ExpandableView child = (ExpandableView) mHostLayout.getChildAt(notificationIndex);
notificationIndex++;
@@ -302,9 +293,25 @@ public class NotificationShelf extends ActivatableNotificationView implements
if (notGoneIndex != 0 || !aboveShelf) {
row.setAboveShelf(false);
}
+ if (notGoneIndex == 0) {
+ StatusBarIconView icon = row.getEntry().expandedIcon;
+ NotificationIconContainer.IconState iconState = getIconState(icon);
+ if (iconState != null && iconState.clampedAppearAmount == 1.0f) {
+ // only if the first icon is fully in the shelf we want to clip to it!
+ backgroundTop = (int) (row.getTranslationY() - getTranslationY());
+ firstElementRoundness = row.getCurrentTopRoundness();
+ } else if (iconState == null) {
+ Log.wtf(TAG, "iconState is null. ExpandedIcon: " + row.getEntry().expandedIcon
+ + (row.getEntry().expandedIcon != null
+ ? "\n icon parent: " + row.getEntry().expandedIcon.getParent() : "")
+ + " \n number of notifications: " + mHostLayout.getChildCount() );
+ }
+ }
notGoneIndex++;
previousColor = ownColorUntinted;
}
+ setBackgroundTop(backgroundTop);
+ setFirstElementRoundness(firstElementRoundness);
mShelfIcons.setSpeedBumpIndex(mAmbientState.getSpeedBumpIndex());
mShelfIcons.calculateIconTranslations();
mShelfIcons.applyIconStates();
@@ -325,6 +332,13 @@ public class NotificationShelf extends ActivatableNotificationView implements
}
}
+ private void setFirstElementRoundness(float firstElementRoundness) {
+ if (mFirstElementRoundness != firstElementRoundness) {
+ mFirstElementRoundness = firstElementRoundness;
+ setTopRoundness(firstElementRoundness, false /* animate */);
+ }
+ }
+
private void updateIconClipAmount(ExpandableNotificationRow row) {
float maxTop = row.getTranslationY();
StatusBarIconView icon = row.getEntry().expandedIcon;
@@ -674,7 +688,8 @@ public class NotificationShelf extends ActivatableNotificationView implements
if (isLayoutRtl()) {
start = getWidth() - start - mCollapsedIcons.getWidth();
}
- int width = (int) NotificationUtils.interpolate(start + mCollapsedIcons.getWidth(),
+ int width = (int) NotificationUtils.interpolate(
+ start + mCollapsedIcons.getFinalTranslationX(),
mShelfIcons.getWidth(),
openedAmount);
mShelfIcons.setActualLayoutWidth(width);
@@ -684,6 +699,9 @@ public class NotificationShelf extends ActivatableNotificationView implements
// we have to ensure that adding the low priority notification won't lead to an
// overflow
collapsedPadding -= (1.0f + OVERFLOW_EARLY_AMOUNT) * mCollapsedIcons.getIconSize();
+ } else {
+ // Partial overflow padding will fill enough space to add extra dots
+ collapsedPadding -= mCollapsedIcons.getPartialOverflowExtraPadding();
}
float padding = NotificationUtils.interpolate(collapsedPadding,
mShelfIcons.getPaddingEnd(),
@@ -693,7 +711,6 @@ public class NotificationShelf extends ActivatableNotificationView implements
mShelfIcons.getPaddingStart(), openedAmount);
mShelfIcons.setActualPaddingStart(paddingStart);
mShelfIcons.setOpenedAmount(openedAmount);
- mShelfIcons.setVisualOverflowAdaption(mCollapsedIcons.getVisualOverflowAdaption());
}
public void setMaxLayoutHeight(int maxLayoutHeight) {
@@ -785,10 +802,6 @@ public class NotificationShelf extends ActivatableNotificationView implements
updateRelativeOffset();
}
- public void setDarkOffsetX(int offsetX) {
- mShelfIcons.setDarkOffsetX(offsetX);
- }
-
private class ShelfState extends ExpandableViewState {
private float openedAmount;
private boolean hasItemsInStableShelf;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java
index 492ab44d499b..aea0127764b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java
@@ -16,7 +16,6 @@ package com.android.systemui.statusbar;
*/
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
@@ -234,7 +233,7 @@ public class NotificationSnooze extends LinearLayout
final int defaultSnooze = mParser.getInt(KEY_DEFAULT_SNOOZE,
resources.getInteger(R.integer.config_notification_snooze_time_default));
- final int[] snoozeTimes = parseIntArray(KEY_OPTIONS,
+ final int[] snoozeTimes = mParser.getIntArray(KEY_OPTIONS,
resources.getIntArray(R.array.config_notification_snooze_times));
for (int i = 0; i < snoozeTimes.length && i < sAccessibilityActions.length; i++) {
@@ -248,21 +247,6 @@ public class NotificationSnooze extends LinearLayout
return options;
}
- @VisibleForTesting
- int[] parseIntArray(final String key, final int[] defaultArray) {
- final String value = mParser.getString(key, null);
- if (value != null) {
- try {
- return Arrays.stream(value.split(":")).map(String::trim).mapToInt(
- Integer::parseInt).toArray();
- } catch (NumberFormatException e) {
- return defaultArray;
- }
- } else {
- return defaultArray;
- }
- }
-
private SnoozeOption createOption(int minutes, int accessibilityActionId) {
Resources res = getResources();
boolean showInHours = minutes >= 60;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUpdateHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUpdateHandler.java
new file mode 100644
index 000000000000..0044194e886c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUpdateHandler.java
@@ -0,0 +1,58 @@
+/*
+ * 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.statusbar;
+
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+
+/**
+ * Interface for accepting notification updates from {@link NotificationListener}.
+ */
+public interface NotificationUpdateHandler {
+ /**
+ * Add a new notification and update the current notification ranking map.
+ *
+ * @param notification Notification to add
+ * @param ranking RankingMap to update with
+ */
+ void addNotification(StatusBarNotification notification,
+ NotificationListenerService.RankingMap ranking);
+
+ /**
+ * Remove a notification and update the current notification ranking map.
+ *
+ * @param key Key identifying the notification to remove
+ * @param ranking RankingMap to update with
+ */
+ void removeNotification(String key, NotificationListenerService.RankingMap ranking);
+
+ /**
+ * Update a given notification and the current notification ranking map.
+ *
+ * @param notification Updated notification
+ * @param ranking RankingMap to update with
+ */
+ void updateNotification(StatusBarNotification notification,
+ NotificationListenerService.RankingMap ranking);
+
+ /**
+ * Update with a new notification ranking map.
+ *
+ * @param ranking RankingMap to update with
+ */
+ void updateNotificationRanking(NotificationListenerService.RankingMap ranking);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
new file mode 100644
index 000000000000..cd4c7ae8d57e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -0,0 +1,349 @@
+/*
+ * 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.statusbar;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.service.notification.NotificationListenerService;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Stack;
+
+/**
+ * NotificationViewHierarchyManager manages updating the view hierarchy of notification views based
+ * on their group structure. For example, if a notification becomes bundled with another,
+ * NotificationViewHierarchyManager will update the view hierarchy to reflect that. It also will
+ * tell NotificationListContainer which notifications to display, and inform it of changes to those
+ * notifications that might affect their display.
+ */
+public class NotificationViewHierarchyManager {
+ private static final String TAG = "NotificationViewHierarchyManager";
+
+ private final HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>>
+ mTmpChildOrderMap = new HashMap<>();
+
+ // Dependencies:
+ protected final NotificationLockscreenUserManager mLockscreenUserManager =
+ Dependency.get(NotificationLockscreenUserManager.class);
+ protected final NotificationGroupManager mGroupManager =
+ Dependency.get(NotificationGroupManager.class);
+ protected final VisualStabilityManager mVisualStabilityManager =
+ Dependency.get(VisualStabilityManager.class);
+
+ /**
+ * {@code true} if notifications not part of a group should by default be rendered in their
+ * expanded state. If {@code false}, then only the first notification will be expanded if
+ * possible.
+ */
+ private final boolean mAlwaysExpandNonGroupedNotification;
+
+ private NotificationPresenter mPresenter;
+ private NotificationEntryManager mEntryManager;
+ private NotificationListContainer mListContainer;
+
+ public NotificationViewHierarchyManager(Context context) {
+ Resources res = context.getResources();
+ mAlwaysExpandNonGroupedNotification =
+ res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications);
+ }
+
+ public void setUpWithPresenter(NotificationPresenter presenter,
+ NotificationEntryManager entryManager, NotificationListContainer listContainer) {
+ mPresenter = presenter;
+ mEntryManager = entryManager;
+ mListContainer = listContainer;
+ }
+
+ /**
+ * Updates the visual representation of the notifications.
+ */
+ public void updateNotificationViews() {
+ ArrayList<NotificationData.Entry> activeNotifications = mEntryManager.getNotificationData()
+ .getActiveNotifications();
+ ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size());
+ final int N = activeNotifications.size();
+ for (int i = 0; i < N; i++) {
+ NotificationData.Entry ent = activeNotifications.get(i);
+ if (ent.row.isDismissed() || ent.row.isRemoved()) {
+ // we don't want to update removed notifications because they could
+ // temporarily become children if they were isolated before.
+ continue;
+ }
+ int userId = ent.notification.getUserId();
+
+ // Display public version of the notification if we need to redact.
+ // TODO: This area uses a lot of calls into NotificationLockscreenUserManager.
+ // We can probably move some of this code there.
+ boolean devicePublic = mLockscreenUserManager.isLockscreenPublicMode(
+ mLockscreenUserManager.getCurrentUserId());
+ boolean userPublic = devicePublic
+ || mLockscreenUserManager.isLockscreenPublicMode(userId);
+ boolean needsRedaction = mLockscreenUserManager.needsRedaction(ent);
+ boolean sensitive = userPublic && needsRedaction;
+ boolean deviceSensitive = devicePublic
+ && !mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(
+ mLockscreenUserManager.getCurrentUserId());
+ ent.row.setSensitive(sensitive, deviceSensitive);
+ ent.row.setNeedsRedaction(needsRedaction);
+ if (mGroupManager.isChildInGroupWithSummary(ent.row.getStatusBarNotification())) {
+ ExpandableNotificationRow summary = mGroupManager.getGroupSummary(
+ ent.row.getStatusBarNotification());
+ List<ExpandableNotificationRow> orderedChildren =
+ mTmpChildOrderMap.get(summary);
+ if (orderedChildren == null) {
+ orderedChildren = new ArrayList<>();
+ mTmpChildOrderMap.put(summary, orderedChildren);
+ }
+ orderedChildren.add(ent.row);
+ } else {
+ toShow.add(ent.row);
+ }
+
+ }
+
+ ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>();
+ for (int i=0; i< mListContainer.getContainerChildCount(); i++) {
+ View child = mListContainer.getContainerChildAt(i);
+ if (!toShow.contains(child) && child instanceof ExpandableNotificationRow) {
+ toRemove.add((ExpandableNotificationRow) child);
+ }
+ }
+
+ for (ExpandableNotificationRow remove : toRemove) {
+ if (mGroupManager.isChildInGroupWithSummary(remove.getStatusBarNotification())) {
+ // we are only transferring this notification to its parent, don't generate an
+ // animation
+ mListContainer.setChildTransferInProgress(true);
+ }
+ if (remove.isSummaryWithChildren()) {
+ remove.removeAllChildren();
+ }
+ mListContainer.removeContainerView(remove);
+ mListContainer.setChildTransferInProgress(false);
+ }
+
+ removeNotificationChildren();
+
+ for (int i = 0; i < toShow.size(); i++) {
+ View v = toShow.get(i);
+ if (v.getParent() == null) {
+ mVisualStabilityManager.notifyViewAddition(v);
+ mListContainer.addContainerView(v);
+ }
+ }
+
+ addNotificationChildrenAndSort();
+
+ // So after all this work notifications still aren't sorted correctly.
+ // Let's do that now by advancing through toShow and mListContainer in
+ // lock-step, making sure mListContainer matches what we see in toShow.
+ int j = 0;
+ for (int i = 0; i < mListContainer.getContainerChildCount(); i++) {
+ View child = mListContainer.getContainerChildAt(i);
+ if (!(child instanceof ExpandableNotificationRow)) {
+ // We don't care about non-notification views.
+ continue;
+ }
+
+ ExpandableNotificationRow targetChild = toShow.get(j);
+ if (child != targetChild) {
+ // Oops, wrong notification at this position. Put the right one
+ // here and advance both lists.
+ if (mVisualStabilityManager.canReorderNotification(targetChild)) {
+ mListContainer.changeViewPosition(targetChild, i);
+ } else {
+ mVisualStabilityManager.addReorderingAllowedCallback(mEntryManager);
+ }
+ }
+ j++;
+
+ }
+
+ mVisualStabilityManager.onReorderingFinished();
+ // clear the map again for the next usage
+ mTmpChildOrderMap.clear();
+
+ updateRowStates();
+
+ mListContainer.onNotificationViewUpdateFinished();
+ }
+
+ private void addNotificationChildrenAndSort() {
+ // Let's now add all notification children which are missing
+ boolean orderChanged = false;
+ for (int i = 0; i < mListContainer.getContainerChildCount(); i++) {
+ View view = mListContainer.getContainerChildAt(i);
+ if (!(view instanceof ExpandableNotificationRow)) {
+ // We don't care about non-notification views.
+ continue;
+ }
+
+ ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
+ List<ExpandableNotificationRow> children = parent.getNotificationChildren();
+ List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent);
+
+ for (int childIndex = 0; orderedChildren != null && childIndex < orderedChildren.size();
+ childIndex++) {
+ ExpandableNotificationRow childView = orderedChildren.get(childIndex);
+ if (children == null || !children.contains(childView)) {
+ if (childView.getParent() != null) {
+ Log.wtf(TAG, "trying to add a notification child that already has " +
+ "a parent. class:" + childView.getParent().getClass() +
+ "\n child: " + childView);
+ // This shouldn't happen. We can recover by removing it though.
+ ((ViewGroup) childView.getParent()).removeView(childView);
+ }
+ mVisualStabilityManager.notifyViewAddition(childView);
+ parent.addChildNotification(childView, childIndex);
+ mListContainer.notifyGroupChildAdded(childView);
+ }
+ }
+
+ // Finally after removing and adding has been performed we can apply the order.
+ orderChanged |= parent.applyChildOrder(orderedChildren, mVisualStabilityManager,
+ mEntryManager);
+ }
+ if (orderChanged) {
+ mListContainer.generateChildOrderChangedEvent();
+ }
+ }
+
+ private void removeNotificationChildren() {
+ // First let's remove all children which don't belong in the parents
+ ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>();
+ for (int i = 0; i < mListContainer.getContainerChildCount(); i++) {
+ View view = mListContainer.getContainerChildAt(i);
+ if (!(view instanceof ExpandableNotificationRow)) {
+ // We don't care about non-notification views.
+ continue;
+ }
+
+ ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
+ List<ExpandableNotificationRow> children = parent.getNotificationChildren();
+ List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent);
+
+ if (children != null) {
+ toRemove.clear();
+ for (ExpandableNotificationRow childRow : children) {
+ if ((orderedChildren == null
+ || !orderedChildren.contains(childRow))
+ && !childRow.keepInParent()) {
+ toRemove.add(childRow);
+ }
+ }
+ for (ExpandableNotificationRow remove : toRemove) {
+ parent.removeChildNotification(remove);
+ if (mEntryManager.getNotificationData().get(
+ remove.getStatusBarNotification().getKey()) == null) {
+ // We only want to add an animation if the view is completely removed
+ // otherwise it's just a transfer
+ mListContainer.notifyGroupChildRemoved(remove,
+ parent.getChildrenContainer());
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Updates expanded, dimmed and locked states of notification rows.
+ */
+ public void updateRowStates() {
+ final int N = mListContainer.getContainerChildCount();
+
+ int visibleNotifications = 0;
+ boolean isLocked = mPresenter.isPresenterLocked();
+ int maxNotifications = -1;
+ if (isLocked) {
+ maxNotifications = mPresenter.getMaxNotificationsWhileLocked(true /* recompute */);
+ }
+ mListContainer.setMaxDisplayedNotifications(maxNotifications);
+ Stack<ExpandableNotificationRow> stack = new Stack<>();
+ for (int i = N - 1; i >= 0; i--) {
+ View child = mListContainer.getContainerChildAt(i);
+ if (!(child instanceof ExpandableNotificationRow)) {
+ continue;
+ }
+ stack.push((ExpandableNotificationRow) child);
+ }
+ while(!stack.isEmpty()) {
+ ExpandableNotificationRow row = stack.pop();
+ NotificationData.Entry entry = row.getEntry();
+ boolean isChildNotification =
+ mGroupManager.isChildInGroupWithSummary(entry.notification);
+
+ row.setOnKeyguard(isLocked);
+
+ if (!isLocked) {
+ // If mAlwaysExpandNonGroupedNotification is false, then only expand the
+ // very first notification and if it's not a child of grouped notifications.
+ row.setSystemExpanded(mAlwaysExpandNonGroupedNotification
+ || (visibleNotifications == 0 && !isChildNotification
+ && !row.isLowPriority()));
+ }
+
+ entry.row.setShowAmbient(mPresenter.isDozing());
+ int userId = entry.notification.getUserId();
+ boolean suppressedSummary = mGroupManager.isSummaryOfSuppressedGroup(
+ entry.notification) && !entry.row.isRemoved();
+ boolean showOnKeyguard = mLockscreenUserManager.shouldShowOnKeyguard(entry
+ .notification);
+ if (suppressedSummary
+ || (mLockscreenUserManager.isLockscreenPublicMode(userId)
+ && !mLockscreenUserManager.shouldShowLockscreenNotifications())
+ || (isLocked && !showOnKeyguard)) {
+ entry.row.setVisibility(View.GONE);
+ } else {
+ boolean wasGone = entry.row.getVisibility() == View.GONE;
+ if (wasGone) {
+ entry.row.setVisibility(View.VISIBLE);
+ }
+ if (!isChildNotification && !entry.row.isRemoved()) {
+ if (wasGone) {
+ // notify the scroller of a child addition
+ mListContainer.generateAddAnimation(entry.row,
+ !showOnKeyguard /* fromMoreCard */);
+ }
+ visibleNotifications++;
+ }
+ }
+ if (row.isSummaryWithChildren()) {
+ List<ExpandableNotificationRow> notificationChildren =
+ row.getNotificationChildren();
+ int size = notificationChildren.size();
+ for (int i = size - 1; i >= 0; i--) {
+ stack.push(notificationChildren.get(i));
+ }
+ }
+
+ row.showBlockingHelper(entry.userSentiment ==
+ NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE);
+ }
+
+ mPresenter.onUpdateRowStates();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
new file mode 100644
index 000000000000..5090f74d4019
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
@@ -0,0 +1,155 @@
+/*
+ * 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.statusbar;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionInfo;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+import com.android.internal.telephony.IccCardConstants.State;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.settingslib.WirelessUtils;
+import com.android.systemui.DemoMode;
+import com.android.systemui.Dependency;
+import com.android.systemui.statusbar.policy.DarkIconDispatcher;
+import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.statusbar.policy.NetworkController.IconState;
+import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+import com.android.systemui.tuner.TunerService;
+import com.android.systemui.tuner.TunerService.Tunable;
+
+import java.util.List;
+
+public class OperatorNameView extends TextView implements DemoMode, DarkReceiver,
+ SignalCallback, Tunable {
+
+ private static final String KEY_SHOW_OPERATOR_NAME = "show_operator_name";
+
+ private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private boolean mDemoMode;
+
+ private final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
+ @Override
+ public void onRefreshCarrierInfo() {
+ updateText();
+ }
+ };
+
+ public OperatorNameView(Context context) {
+ this(context, null);
+ }
+
+ public OperatorNameView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public OperatorNameView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+ mKeyguardUpdateMonitor.registerCallback(mCallback);
+ Dependency.get(DarkIconDispatcher.class).addDarkReceiver(this);
+ Dependency.get(NetworkController.class).addCallback(this);
+ Dependency.get(TunerService.class).addTunable(this, KEY_SHOW_OPERATOR_NAME);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mKeyguardUpdateMonitor.removeCallback(mCallback);
+ Dependency.get(DarkIconDispatcher.class).removeDarkReceiver(this);
+ Dependency.get(NetworkController.class).removeCallback(this);
+ Dependency.get(TunerService.class).removeTunable(this);
+ }
+
+ @Override
+ public void onDarkChanged(Rect area, float darkIntensity, int tint) {
+ setTextColor(DarkIconDispatcher.getTint(area, this, tint));
+ }
+
+ @Override
+ public void setIsAirplaneMode(IconState icon) {
+ update();
+ }
+
+ @Override
+ public void onTuningChanged(String key, String newValue) {
+ update();
+ }
+
+ @Override
+ public void dispatchDemoCommand(String command, Bundle args) {
+ if (!mDemoMode && command.equals(COMMAND_ENTER)) {
+ mDemoMode = true;
+ } else if (mDemoMode && command.equals(COMMAND_EXIT)) {
+ mDemoMode = false;
+ update();
+ } else if (mDemoMode && command.equals(COMMAND_OPERATOR)) {
+ setText(args.getString("name"));
+ }
+ }
+
+ private void update() {
+ boolean showOperatorName = Dependency.get(TunerService.class)
+ .getValue(KEY_SHOW_OPERATOR_NAME, 1) != 0;
+ setVisibility(showOperatorName ? VISIBLE : GONE);
+
+ boolean hasMobile = ConnectivityManager.from(mContext)
+ .isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
+ boolean airplaneMode = WirelessUtils.isAirplaneModeOn(mContext);
+ if (!hasMobile || airplaneMode) {
+ setText(null);
+ setVisibility(GONE);
+ return;
+ }
+
+ if (!mDemoMode) {
+ updateText();
+ }
+ }
+
+ private void updateText() {
+ CharSequence displayText = null;
+ List<SubscriptionInfo> subs = mKeyguardUpdateMonitor.getSubscriptionInfo(false);
+ final int N = subs.size();
+ for (int i = 0; i < N; i++) {
+ int subId = subs.get(i).getSubscriptionId();
+ State simState = mKeyguardUpdateMonitor.getSimState(subId);
+ CharSequence carrierName = subs.get(i).getCarrierName();
+ if (!TextUtils.isEmpty(carrierName) && simState == State.READY) {
+ ServiceState ss = mKeyguardUpdateMonitor.getServiceState(subId);
+ if (ss != null && ss.getState() == ServiceState.STATE_IN_SERVICE) {
+ displayText = carrierName;
+ break;
+ }
+ }
+ }
+
+ setText(displayText);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
index 7f28c4c2e7a4..cfc69a8f67a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
@@ -19,30 +19,82 @@ package com.android.systemui.statusbar;
import com.android.internal.util.Preconditions;
import com.android.systemui.Dependency;
import com.android.systemui.statusbar.phone.StatusBarWindowManager;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.RemoteInputView;
+import android.app.Notification;
+import android.app.RemoteInput;
+import android.content.Context;
+import android.os.SystemProperties;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Pair;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.List;
/**
* Keeps track of the currently active {@link RemoteInputView}s.
*/
public class RemoteInputController {
+ private static final boolean ENABLE_REMOTE_INPUT =
+ SystemProperties.getBoolean("debug.enable_remote_input", true);
private final ArrayList<Pair<WeakReference<NotificationData.Entry>, Object>> mOpen
= new ArrayList<>();
private final ArrayMap<String, Object> mSpinning = new ArrayMap<>();
private final ArrayList<Callback> mCallbacks = new ArrayList<>(3);
- private final HeadsUpManager mHeadsUpManager;
+ private final Delegate mDelegate;
- public RemoteInputController(HeadsUpManager headsUpManager) {
- addCallback(Dependency.get(StatusBarWindowManager.class));
- mHeadsUpManager = headsUpManager;
+ public RemoteInputController(Delegate delegate) {
+ mDelegate = delegate;
+ }
+
+ /**
+ * Adds RemoteInput actions from the WearableExtender; to be removed once more apps support this
+ * via first-class API.
+ *
+ * TODO: Remove once enough apps specify remote inputs on their own.
+ */
+ public static void processForRemoteInput(Notification n, Context context) {
+ if (!ENABLE_REMOTE_INPUT) {
+ return;
+ }
+
+ if (n.extras != null && n.extras.containsKey("android.wearable.EXTENSIONS") &&
+ (n.actions == null || n.actions.length == 0)) {
+ Notification.Action viableAction = null;
+ Notification.WearableExtender we = new Notification.WearableExtender(n);
+
+ List<Notification.Action> actions = we.getActions();
+ final int numActions = actions.size();
+
+ for (int i = 0; i < numActions; i++) {
+ Notification.Action action = actions.get(i);
+ if (action == null) {
+ continue;
+ }
+ RemoteInput[] remoteInputs = action.getRemoteInputs();
+ if (remoteInputs == null) {
+ continue;
+ }
+ for (RemoteInput ri : remoteInputs) {
+ if (ri.getAllowFreeFormInput()) {
+ viableAction = action;
+ break;
+ }
+ }
+ if (viableAction != null) {
+ break;
+ }
+ }
+
+ if (viableAction != null) {
+ Notification.Builder rebuilder = Notification.Builder.recoverBuilder(context, n);
+ rebuilder.setActions(viableAction);
+ rebuilder.build(); // will rewrite n
+ }
+ }
}
/**
@@ -114,7 +166,7 @@ public class RemoteInputController {
}
private void apply(NotificationData.Entry entry) {
- mHeadsUpManager.setRemoteInputActive(entry, isRemoteInputActive(entry));
+ mDelegate.setRemoteInputActive(entry, isRemoteInputActive(entry));
boolean remoteInputActive = isRemoteInputActive();
int N = mCallbacks.size();
for (int i = 0; i < N; i++) {
@@ -204,9 +256,35 @@ public class RemoteInputController {
}
}
+ public void requestDisallowLongPressAndDismiss() {
+ mDelegate.requestDisallowLongPressAndDismiss();
+ }
+
+ public void lockScrollTo(NotificationData.Entry entry) {
+ mDelegate.lockScrollTo(entry);
+ }
+
public interface Callback {
default void onRemoteInputActive(boolean active) {}
default void onRemoteInputSent(NotificationData.Entry entry) {}
}
+
+ public interface Delegate {
+ /**
+ * Activate remote input if necessary.
+ */
+ void setRemoteInputActive(NotificationData.Entry entry, boolean remoteInputActive);
+
+ /**
+ * Request that the view does not dismiss nor perform long press for the current touch.
+ */
+ void requestDisallowLongPressAndDismiss();
+
+ /**
+ * Request that the view is made visible by scrolling to it, and keep the scroll locked until
+ * the user scrolls, or {@param v} loses focus or is detached.
+ */
+ void lockScrollTo(NotificationData.Entry entry);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
index a53e348fd16c..8830352296dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
@@ -41,6 +41,7 @@ import android.view.animation.Interpolator;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.colorextraction.ColorExtractor;
import com.android.internal.colorextraction.drawable.GradientDrawable;
+import com.android.settingslib.Utils;
import com.android.systemui.Dependency;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -50,6 +51,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController;
public class ScrimView extends View implements ConfigurationController.ConfigurationListener {
private static final String TAG = "ScrimView";
private final ColorExtractor.GradientColors mColors;
+ private int mDensity;
private boolean mDrawAsSrc;
private float mViewAlpha = 1.0f;
private ValueAnimator mAlphaAnimator;
@@ -72,6 +74,7 @@ public class ScrimView extends View implements ConfigurationController.Configura
}
};
private Runnable mChangeRunnable;
+ private int mCornerRadius;
public ScrimView(Context context) {
this(context, null);
@@ -93,6 +96,24 @@ public class ScrimView extends View implements ConfigurationController.Configura
mColors = new ColorExtractor.GradientColors();
updateScreenSize();
updateColorWithTint(false);
+ initView();
+ final Configuration currentConfig = mContext.getResources().getConfiguration();
+ mDensity = currentConfig.densityDpi;
+ }
+
+ private void initView() {
+ mCornerRadius = getResources().getDimensionPixelSize(
+ Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius));
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ int densityDpi = newConfig.densityDpi;
+ if (mDensity != densityDpi) {
+ mDensity = densityDpi;
+ initView();
+ }
}
@Override
@@ -145,6 +166,28 @@ public class ScrimView extends View implements ConfigurationController.Configura
mDrawable.draw(canvas);
canvas.restore();
}
+ // We also need to draw the rounded corners of the background
+ canvas.save();
+ canvas.clipRect(mExcludedRect.left, mExcludedRect.top,
+ mExcludedRect.left + mCornerRadius, mExcludedRect.top + mCornerRadius);
+ mDrawable.draw(canvas);
+ canvas.restore();
+ canvas.save();
+ canvas.clipRect(mExcludedRect.right - mCornerRadius, mExcludedRect.top,
+ mExcludedRect.right, mExcludedRect.top + mCornerRadius);
+ mDrawable.draw(canvas);
+ canvas.restore();
+ canvas.save();
+ canvas.clipRect(mExcludedRect.left, mExcludedRect.bottom - mCornerRadius,
+ mExcludedRect.left + mCornerRadius, mExcludedRect.bottom);
+ mDrawable.draw(canvas);
+ canvas.restore();
+ canvas.save();
+ canvas.clipRect(mExcludedRect.right - mCornerRadius,
+ mExcludedRect.bottom - mCornerRadius,
+ mExcludedRect.right, mExcludedRect.bottom);
+ mDrawable.draw(canvas);
+ canvas.restore();
}
}
}
@@ -252,6 +295,13 @@ public class ScrimView extends View implements ConfigurationController.Configura
return false;
}
+ /**
+ * It might look counterintuitive to have another method to set the alpha instead of
+ * only using {@link #setAlpha(float)}. In this case we're in a hardware layer
+ * optimizing blend modes, so it makes sense.
+ *
+ * @param alpha Gradient alpha from 0 to 1.
+ */
public void setViewAlpha(float alpha) {
if (alpha != mViewAlpha) {
mViewAlpha = alpha;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
index 274244ef2679..cb6e5a6e0bae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
@@ -16,6 +16,9 @@
package com.android.systemui.statusbar;
+import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS;
+import static android.app.StatusBarManager.DISABLE_NONE;
+
import android.annotation.DrawableRes;
import android.content.Context;
import android.content.res.ColorStateList;
@@ -35,9 +38,9 @@ import android.view.accessibility.AccessibilityEvent;
import android.widget.ImageView;
import android.widget.LinearLayout;
+import com.android.settingslib.graph.SignalDrawable;
import com.android.systemui.Dependency;
import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.SignalDrawable;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.policy.DarkIconDispatcher;
import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver;
@@ -48,6 +51,7 @@ import com.android.systemui.statusbar.policy.NetworkControllerImpl;
import com.android.systemui.statusbar.policy.SecurityController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
+import com.android.systemui.util.Utils.DisableStateTracker;
import java.util.ArrayList;
import java.util.List;
@@ -55,8 +59,7 @@ import java.util.Objects;
// Intimately tied to the design of res/layout/signal_cluster_view.xml
public class SignalClusterView extends LinearLayout implements NetworkControllerImpl.SignalCallback,
- SecurityController.SecurityControllerCallback, Tunable,
- DarkReceiver {
+ SecurityController.SecurityControllerCallback, Tunable, DarkReceiver {
static final String TAG = "SignalClusterView";
static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -148,6 +151,8 @@ public class SignalClusterView extends LinearLayout implements NetworkController
mIconScaleFactor = typedValue.getFloat();
mNetworkController = Dependency.get(NetworkController.class);
mSecurityController = Dependency.get(SecurityController.class);
+ addOnAttachStateChangeListener(
+ new DisableStateTracker(DISABLE_NONE, DISABLE2_SYSTEM_ICONS));
updateActivityEnabled();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
index 535300573c6e..09b11c295701 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
@@ -35,7 +35,8 @@ import java.util.Stack;
/**
* A view that can be transformed to and from.
*/
-public class ViewTransformationHelper implements TransformableView {
+public class ViewTransformationHelper implements TransformableView,
+ TransformState.TransformInfo {
private static final int TAG_CONTAINS_TRANSFORMED_VIEW = R.id.contains_transformed_view;
@@ -59,7 +60,7 @@ public class ViewTransformationHelper implements TransformableView {
public TransformState getCurrentState(int fadingView) {
View view = mTransformedViews.get(fadingView);
if (view != null && view.getVisibility() != View.GONE) {
- return TransformState.createFrom(view);
+ return TransformState.createFrom(view, this);
}
return null;
}
@@ -88,6 +89,7 @@ public class ViewTransformationHelper implements TransformableView {
endRunnable.run();
}
setVisible(false);
+ mViewTransformationAnimation = null;
} else {
abortTransformations();
}
@@ -245,7 +247,7 @@ public class ViewTransformationHelper implements TransformableView {
}
public void resetTransformedView(View view) {
- TransformState state = TransformState.createFrom(view);
+ TransformState state = TransformState.createFrom(view, this);
state.setVisible(true /* visible */, true /* force */);
state.recycle();
}
@@ -257,6 +259,11 @@ public class ViewTransformationHelper implements TransformableView {
return new ArraySet<>(mTransformedViews.values());
}
+ @Override
+ public boolean isAnimating() {
+ return mViewTransformationAnimation != null && mViewTransformationAnimation.isRunning();
+ }
+
public static abstract class CustomTransformation {
/**
* Transform a state to the given view
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java
index 7e08d5605f40..64c52ed6d29f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java
@@ -15,7 +15,13 @@
*/
package com.android.systemui.statusbar.car;
-import android.app.ActivityManager.StackId;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
+import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -101,7 +107,7 @@ class CarNavigationBarController {
}
}
- public void taskChanged(String packageName, int stackId) {
+ public void taskChanged(String packageName, ActivityManager.RunningTaskInfo taskInfo) {
// If the package name belongs to a filter, then highlight appropriate button in
// the navigation bar.
if (mFacetPackageMap.containsKey(packageName)) {
@@ -115,9 +121,11 @@ class CarNavigationBarController {
}
// Set up the persistent docked task if needed.
- if (mPersistentTaskIntent != null && !mStatusBar.hasDockedTask()
- && stackId != StackId.HOME_STACK_ID) {
- mStatusBar.startActivityOnStack(mPersistentTaskIntent, StackId.DOCKED_STACK_ID);
+ boolean isHomeTask =
+ taskInfo.configuration.windowConfiguration.getActivityType() == ACTIVITY_TYPE_HOME;
+ if (mPersistentTaskIntent != null && !mStatusBar.hasDockedTask() && !isHomeTask) {
+ mStatusBar.startActivityOnStack(mPersistentTaskIntent,
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED);
}
}
@@ -361,7 +369,7 @@ class CarNavigationBarController {
private void onFacetClicked(Intent intent, int index) {
String packageName = intent.getPackage();
- if (packageName == null) {
+ if (packageName == null && !intent.getCategories().contains(Intent.CATEGORY_HOME)) {
return;
}
@@ -375,13 +383,15 @@ class CarNavigationBarController {
// rather than the "preferred/last run" app.
intent.putExtra(EXTRA_FACET_LAUNCH_PICKER, index == mCurrentFacetIndex);
- int stackId = StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+ int windowingMode = WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
+ int activityType = ACTIVITY_TYPE_UNDEFINED;
if (intent.getCategories().contains(Intent.CATEGORY_HOME)) {
- stackId = StackId.HOME_STACK_ID;
+ windowingMode = WINDOWING_MODE_UNDEFINED;
+ activityType = ACTIVITY_TYPE_HOME;
}
setCurrentFacet(index);
- mStatusBar.startActivityOnStack(intent, stackId);
+ mStatusBar.startActivityOnStack(intent, windowingMode, activityType);
}
/**
@@ -391,6 +401,7 @@ class CarNavigationBarController {
*/
private void onFacetLongClicked(Intent intent, int index) {
setCurrentFacet(index);
- mStatusBar.startActivityOnStack(intent, StackId.FULLSCREEN_WORKSPACE_STACK_ID);
+ mStatusBar.startActivityOnStack(intent,
+ WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_UNDEFINED);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java
index 6cbbd6cd1f18..e5a311d099d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java
@@ -17,11 +17,15 @@
package com.android.systemui.statusbar.car;
import android.content.Context;
+import android.graphics.Canvas;
import android.util.AttributeSet;
+import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
import com.android.systemui.R;
+import com.android.systemui.plugins.statusbar.phone.NavGesture;
+import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
import com.android.systemui.statusbar.phone.NavigationBarView;
/**
@@ -72,4 +76,68 @@ class CarNavigationBarView extends NavigationBarView {
// Calling setNavigationIconHints in the base class will result in a NPE as the car
// navigation bar does not have a back button.
}
+
+ @Override
+ public void onPluginConnected(NavGesture plugin, Context context) {
+ // set to null version of the plugin ignoring incoming arg.
+ super.onPluginConnected(new NullNavGesture(), context);
+ }
+
+ @Override
+ public void onPluginDisconnected(NavGesture plugin) {
+ // reinstall the null nav gesture plugin
+ super.onPluginConnected(new NullNavGesture(), getContext());
+ }
+
+ /**
+ * Null object pattern to work around expectations of the base class.
+ * This is a temporary solution to have the car system ui working.
+ * Already underway is a refactor of they car sys ui as to not use this class
+ * hierarchy.
+ */
+ private static class NullNavGesture implements NavGesture {
+ @Override
+ public GestureHelper getGestureHelper() {
+ return new GestureHelper() {
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ return false;
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent event) {
+ return false;
+ }
+
+ @Override
+ public void setBarState(boolean vertical, boolean isRtl) {
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ }
+
+ @Override
+ public void onDarkIntensityChange(float intensity) {
+ }
+
+ @Override
+ public void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ }
+ };
+ }
+
+ @Override
+ public int getVersion() {
+ return 0;
+ }
+
+ @Override
+ public void onCreate(Context sysuiContext, Context pluginContext) {
+ }
+
+ @Override
+ public void onDestroy() {
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 680f693a83f8..3dfb9130af2e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -36,14 +36,19 @@ import android.view.ViewStub;
import android.view.WindowManager;
import android.widget.LinearLayout;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.BatteryMeterView;
import com.android.systemui.Dependency;
+import com.android.systemui.Prefs;
import com.android.systemui.R;
-import com.android.systemui.SwipeHelper;
+import com.android.systemui.classifier.FalsingLog;
+import com.android.systemui.classifier.FalsingManager;
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
@@ -51,10 +56,6 @@ import com.android.systemui.statusbar.phone.NavigationBarView;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.classifier.FalsingLog;
-import com.android.systemui.classifier.FalsingManager;
-import com.android.systemui.Prefs;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -84,7 +85,7 @@ public class CarStatusBar extends StatusBar implements
public void start() {
super.start();
mTaskStackListener = new TaskStackListenerImpl();
- SystemServicesProxy.getInstance(mContext).registerTaskStackListener(mTaskStackListener);
+ ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
registerPackageChangeReceivers();
mStackScroller.setScrollingEnabled(true);
@@ -246,18 +247,6 @@ public class CarStatusBar extends StatusBar implements
return null;
}
- /**
- * Returns the {@link com.android.systemui.SwipeHelper.LongPressListener} that will be
- * triggered when a notification card is long-pressed.
- */
- @Override
- protected SwipeHelper.LongPressListener getNotificationLongClicker() {
- // For the automative use case, we do not want to the user to be able to interact with
- // a notification other than a regular click. As a result, just return null for the
- // long click listener.
- return null;
- }
-
@Override
public void showBatteryView() {
if (Log.isLoggable(TAG, Log.DEBUG)) {
@@ -304,17 +293,17 @@ public class CarStatusBar extends StatusBar implements
}
/**
- * An implementation of TaskStackListener, that listens for changes in the system task
+ * An implementation of SysUiTaskStackChangeListener, that listens for changes in the system task
* stack and notifies the navigation bar.
*/
- private class TaskStackListenerImpl extends TaskStackListener {
+ private class TaskStackListenerImpl extends SysUiTaskStackChangeListener {
@Override
public void onTaskStackChanged() {
- SystemServicesProxy ssp = Recents.getSystemServices();
- ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getRunningTask();
+ ActivityManager.RunningTaskInfo runningTaskInfo =
+ ActivityManagerWrapper.getInstance().getRunningTask();
if (runningTaskInfo != null && runningTaskInfo.baseActivity != null) {
mController.taskChanged(runningTaskInfo.baseActivity.getPackageName(),
- runningTaskInfo.stackId);
+ runningTaskInfo);
}
}
}
@@ -333,8 +322,8 @@ public class CarStatusBar extends StatusBar implements
}
@Override
- public void userSwitched(int newUserId) {
- super.userSwitched(newUserId);
+ public void onUserSwitched(int newUserId) {
+ super.onUserSwitched(newUserId);
if (mFullscreenUserSwitcher != null) {
mFullscreenUserSwitcher.onUserSwitched(newUserId);
}
@@ -378,30 +367,19 @@ public class CarStatusBar extends StatusBar implements
return result;
}
- public int startActivityOnStack(Intent intent, int stackId) {
- ActivityOptions options = ActivityOptions.makeBasic();
- options.setLaunchStackId(stackId);
+ public int startActivityOnStack(Intent intent, int windowingMode, int activityType) {
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchWindowingMode(windowingMode);
+ options.setLaunchActivityType(activityType);
return startActivityWithOptions(intent, options.toBundle());
}
@Override
- protected boolean shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn) {
- // Because space is usually constrained in the auto use-case, there should not be a
- // pinned notification when the shade has been expanded. Ensure this by not pinning any
- // notification if the shade is already opened.
- if (mPanelExpanded) {
- return false;
- }
-
- return super.shouldPeek(entry, sbn);
- }
-
- @Override
public void animateExpandNotificationsPanel() {
// Because space is usually constrained in the auto use-case, there should not be a
// pinned notification when the shade has been expanded. Ensure this by removing all heads-
// up notifications.
- mHeadsUpManager.removeAllHeadsUpEntries();
+ mHeadsUpManager.releaseAllImmediately();
super.animateExpandNotificationsPanel();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/ConnectedDeviceSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/ConnectedDeviceSignalController.java
index 677fa81a12cd..0304086dbfda 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/ConnectedDeviceSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/ConnectedDeviceSignalController.java
@@ -16,10 +16,10 @@ import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.widget.ImageView;
+import com.android.settingslib.graph.SignalDrawable;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.statusbar.ScalingDrawableWrapper;
-import com.android.systemui.statusbar.phone.SignalDrawable;
import com.android.systemui.statusbar.policy.BluetoothController;
import static com.android.systemui.statusbar.phone.StatusBar.DEBUG;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
index 172c62a99db2..3ec89138d1c0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
@@ -53,7 +53,7 @@ public class FullscreenUserSwitcher {
mParent = containerStub.inflate();
mContainer = mParent.findViewById(R.id.container);
mUserGridView = mContainer.findViewById(R.id.user_grid);
- mUserGridView.init(statusBar, mUserSwitcherController, true /* showInitially */);
+ mUserGridView.init(statusBar, mUserSwitcherController, true /* overrideAlpha */);
mUserGridView.setUserSelectionListener(record -> {
if (!record.isCurrent) {
toggleSwitchInProgress(true);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridView.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridView.java
index e551801ca434..1bd820db3f7c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridView.java
@@ -16,9 +16,6 @@
package com.android.systemui.statusbar.car;
-import android.animation.Animator;
-import android.animation.Animator.AnimatorListener;
-import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
@@ -29,62 +26,110 @@ import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
-import android.support.v4.view.animation.FastOutSlowInInterpolator;
import android.util.AttributeSet;
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.Dependency;
import com.android.systemui.R;
+import com.android.systemui.qs.car.CarQSFragment;
import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Vector;
+
/**
* Displays a ViewPager with icons for the users in the system to allow switching between users.
* One of the uses of this is for the lock screen in auto.
*/
-public class UserGridView extends ViewPager {
- private static final int EXPAND_ANIMATION_TIME_MS = 200;
- private static final int HIDE_ANIMATION_TIME_MS = 133;
-
+public class UserGridView extends ViewPager implements
+ UserInfoController.OnUserInfoChangedListener {
private StatusBar mStatusBar;
private UserSwitcherController mUserSwitcherController;
private Adapter mAdapter;
private UserSelectionListener mUserSelectionListener;
- private ValueAnimator mHeightAnimator;
- private int mTargetHeight;
- private int mHeightChildren;
- private boolean mShowing;
+ private UserInfoController mUserInfoController;
+ private Vector mUserContainers;
+ private int mContainerWidth;
+ private boolean mOverrideAlpha;
+ private CarQSFragment.UserSwitchCallback mUserSwitchCallback;
public UserGridView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void init(StatusBar statusBar, UserSwitcherController userSwitcherController,
- boolean showInitially) {
+ boolean overrideAlpha) {
mStatusBar = statusBar;
mUserSwitcherController = userSwitcherController;
mAdapter = new Adapter(mUserSwitcherController);
- addOnLayoutChangeListener(mAdapter);
- setAdapter(mAdapter);
- mShowing = showInitially;
+ mUserInfoController = Dependency.get(UserInfoController.class);
+ mOverrideAlpha = overrideAlpha;
+ // Whenever the container width changes, the containers must be refreshed. Instead of
+ // doing an initial refreshContainers() to populate the containers, this listener will
+ // refresh them on layout change because that affects how the users are split into
+ // containers. Furthermore, at this point, the container width is unknown, so
+ // refreshContainers() cannot populate any containers.
+ addOnLayoutChangeListener(
+ (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+ int newWidth = Math.max(left - right, right - left);
+ if (mContainerWidth != newWidth) {
+ mContainerWidth = newWidth;
+ refreshContainers();
+ }
+ });
}
- public boolean isShowing() {
- return mShowing;
+ private void refreshContainers() {
+ mUserContainers = new Vector();
+
+ Context context = getContext();
+ LayoutInflater inflater = LayoutInflater.from(context);
+
+ for (int i = 0; i < mAdapter.getCount(); i++) {
+ ViewGroup pods = (ViewGroup) inflater.inflate(
+ R.layout.car_fullscreen_user_pod_container, null);
+
+ int iconsPerPage = mAdapter.getIconsPerPage();
+ int limit = Math.min(mUserSwitcherController.getUsers().size(), (i + 1) * iconsPerPage);
+ for (int j = i * iconsPerPage; j < limit; j++) {
+ View v = mAdapter.makeUserPod(inflater, context, j, pods);
+ if (mOverrideAlpha) {
+ v.setAlpha(1f);
+ }
+ pods.addView(v);
+ // This is hacky, but the dividers on the pod container LinearLayout don't seem
+ // to work for whatever reason. Instead, set a right margin on the pod if it's not
+ // the right-most pod and there is more than one pod in the container.
+ if (i < limit - 1 && limit > 1) {
+ ViewGroup.MarginLayoutParams params =
+ (ViewGroup.MarginLayoutParams) v.getLayoutParams();
+ params.setMargins(0, 0, getResources().getDimensionPixelSize(
+ R.dimen.car_fullscreen_user_pod_margin_between), 0);
+ v.setLayoutParams(params);
+ }
+ }
+ mUserContainers.add(pods);
+ }
+
+ mAdapter = new Adapter(mUserSwitcherController);
+ setAdapter(mAdapter);
}
- public void show() {
- mShowing = true;
- animateHeightChange(getMeasuredHeight(), mHeightChildren);
+ @Override
+ public void onUserInfoChanged(String name, Drawable picture, String userAccount) {
+ refreshContainers();
}
- public void hide() {
- mShowing = false;
- animateHeightChange(getMeasuredHeight(), 0);
+ public void setUserSwitchCallback(CarQSFragment.UserSwitchCallback callback) {
+ mUserSwitchCallback = callback;
}
public void onUserSwitched(int newUserId) {
@@ -96,6 +141,14 @@ public class UserGridView extends ViewPager {
mUserSelectionListener = userSelectionListener;
}
+ public void setListening(boolean listening) {
+ if (listening) {
+ mUserInfoController.addCallback(this);
+ } else {
+ mUserInfoController.removeCallback(this);
+ }
+ }
+
void showOfflineAuthUi() {
// TODO: Show keyguard UI in-place.
mStatusBar.executeRunnableDismissingKeyguard(null, null, true, true, true);
@@ -115,13 +168,6 @@ public class UserGridView extends ViewPager {
height = Math.max(child.getMeasuredHeight(), height);
}
- mHeightChildren = height;
-
- // Override the height if it's not showing.
- if (!mShowing) {
- height = 0;
- }
-
// Respect the AT_MOST request from parent.
if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) {
height = Math.min(MeasureSpec.getSize(heightMeasureSpec), height);
@@ -132,72 +178,19 @@ public class UserGridView extends ViewPager {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
- private void animateHeightChange(int oldHeight, int newHeight) {
- // If there is no change in height or an animation is already in progress towards the
- // desired height, then there's no need to make any changes.
- if (oldHeight == newHeight || newHeight == mTargetHeight) {
- return;
- }
-
- // Animation in progress is not going towards the new target, so cancel it.
- if (mHeightAnimator != null){
- mHeightAnimator.cancel();
- }
-
- mTargetHeight = newHeight;
- mHeightAnimator = ValueAnimator.ofInt(oldHeight, mTargetHeight);
- mHeightAnimator.addUpdateListener(valueAnimator -> {
- ViewGroup.LayoutParams layoutParams = getLayoutParams();
- layoutParams.height = (Integer) valueAnimator.getAnimatedValue();
- requestLayout();
- });
- mHeightAnimator.addListener(new AnimatorListener() {
- @Override
- public void onAnimationStart(Animator animator) {}
-
- @Override
- public void onAnimationEnd(Animator animator) {
- // ValueAnimator does not guarantee that the update listener will get an update
- // to the final value, so here, the final value is set. Though the final calculated
- // height (mTargetHeight) could be set, WRAP_CONTENT is more appropriate.
- ViewGroup.LayoutParams layoutParams = getLayoutParams();
- layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
- requestLayout();
- mHeightAnimator = null;
- }
-
- @Override
- public void onAnimationCancel(Animator animator) {}
-
- @Override
- public void onAnimationRepeat(Animator animator) {}
- });
-
- mHeightAnimator.setInterpolator(new FastOutSlowInInterpolator());
- if (oldHeight < newHeight) {
- // Expanding
- mHeightAnimator.setDuration(EXPAND_ANIMATION_TIME_MS);
- } else {
- // Hiding
- mHeightAnimator.setDuration(HIDE_ANIMATION_TIME_MS);
- }
- mHeightAnimator.start();
- }
-
/**
* This is a ViewPager.PagerAdapter which deletegates the work to a
* UserSwitcherController.BaseUserAdapter. Java doesn't support multiple inheritance so we have
* to use composition instead to achieve the same goal since both the base classes are abstract
* classes and not interfaces.
*/
- private final class Adapter extends PagerAdapter implements View.OnLayoutChangeListener {
+ private final class Adapter extends PagerAdapter {
private final int mPodWidth;
private final int mPodMarginBetween;
private final int mPodImageAvatarWidth;
private final int mPodImageAvatarHeight;
private final WrappedBaseUserAdapter mUserAdapter;
- private int mContainerWidth;
public Adapter(UserSwitcherController controller) {
super();
@@ -229,30 +222,20 @@ public class UserGridView extends ViewPager {
}
@Override
- public Object instantiateItem(ViewGroup container, int position) {
- Context context = getContext();
- LayoutInflater inflater = LayoutInflater.from(context);
-
- ViewGroup pods = (ViewGroup) inflater.inflate(
- R.layout.car_fullscreen_user_pod_container, null);
+ public void finishUpdate(ViewGroup container) {
+ if (mUserSwitchCallback != null) {
+ mUserSwitchCallback.resetShowing();
+ }
+ }
- int iconsPerPage = getIconsPerPage();
- int limit = Math.min(mUserAdapter.getCount(), (position + 1) * iconsPerPage);
- for (int i = position * iconsPerPage; i < limit; i++) {
- View v = makeUserPod(inflater, context, i, pods);
- pods.addView(v);
- // This is hacky, but the dividers on the pod container LinearLayout don't seem
- // to work for whatever reason. Instead, set a right margin on the pod if it's not
- // the right-most pod and there is more than one pod in the container.
- if (i < limit - 1 && limit > 1) {
- LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
- LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
- params.setMargins(0, 0, mPodMarginBetween, 0);
- v.setLayoutParams(params);
- }
+ @Override
+ public Object instantiateItem(ViewGroup container, int position) {
+ if (position < mUserContainers.size()) {
+ container.addView((View) mUserContainers.get(position));
+ return mUserContainers.get(position);
+ } else {
+ return null;
}
- container.addView(pods);
- return pods;
}
/**
@@ -353,17 +336,10 @@ public class UserGridView extends ViewPager {
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
-
- @Override
- public void onLayoutChange(View v, int left, int top, int right, int bottom,
- int oldLeft, int oldTop, int oldRight, int oldBottom) {
- mContainerWidth = Math.max(left - right, right - left);
- notifyDataSetChanged();
- }
}
private final class WrappedBaseUserAdapter extends UserSwitcherController.BaseUserAdapter {
- private Adapter mContainer;
+ private final Adapter mContainer;
public WrappedBaseUserAdapter(UserSwitcherController controller, Adapter container) {
super(controller);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
new file mode 100644
index 000000000000..11d20b221051
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.notification;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.os.RemoteException;
+import android.util.MathUtils;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
+import android.view.RemoteAnimationAdapter;
+import android.view.RemoteAnimationTarget;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.ViewRootImpl;
+
+import com.android.systemui.Interpolators;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.NotificationListContainer;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
+import com.android.systemui.statusbar.phone.NotificationPanelView;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.phone.StatusBarWindowView;
+
+import java.util.function.Consumer;
+
+/**
+ * A class that allows activities to be launched in a seamless way where the notification
+ * transforms nicely into the starting window.
+ */
+public class ActivityLaunchAnimator {
+
+ private static final int ANIMATION_DURATION = 400;
+ public static final long ANIMATION_DURATION_FADE_CONTENT = 67;
+ public static final long ANIMATION_DURATION_FADE_APP = 200;
+ public static final long ANIMATION_DELAY_ICON_FADE_IN = ANIMATION_DURATION -
+ CollapsedStatusBarFragment.FADE_IN_DURATION - CollapsedStatusBarFragment.FADE_IN_DELAY
+ - 16;
+ private static final long LAUNCH_TIMEOUT = 500;
+ private final NotificationPanelView mNotificationPanel;
+ private final NotificationListContainer mNotificationContainer;
+ private final StatusBarWindowView mStatusBarWindow;
+ private StatusBar mStatusBar;
+ private final Runnable mTimeoutRunnable = () -> {
+ setAnimationPending(false);
+ mStatusBar.collapsePanel(true /* animate */);
+ };
+ private boolean mAnimationPending;
+
+ public ActivityLaunchAnimator(StatusBarWindowView statusBarWindow,
+ StatusBar statusBar,
+ NotificationPanelView notificationPanel,
+ NotificationListContainer container) {
+ mNotificationPanel = notificationPanel;
+ mNotificationContainer = container;
+ mStatusBarWindow = statusBarWindow;
+ mStatusBar = statusBar;
+ }
+
+ public ActivityOptions getLaunchAnimation(
+ ExpandableNotificationRow sourceNofitication) {
+ AnimationRunner animationRunner = new AnimationRunner(sourceNofitication);
+ return ActivityOptions.makeRemoteAnimation(
+ new RemoteAnimationAdapter(animationRunner, 1000 /* Duration */, 0 /* delay */));
+ }
+
+ public boolean isAnimationPending() {
+ return mAnimationPending;
+ }
+
+ public void setLaunchResult(int launchResult) {
+ setAnimationPending((launchResult == ActivityManager.START_TASK_TO_FRONT
+ || launchResult == ActivityManager.START_SUCCESS)
+ && mStatusBar.getBarState() == StatusBarState.SHADE);
+ }
+
+ private void setAnimationPending(boolean pending) {
+ mAnimationPending = pending;
+ mStatusBarWindow.setExpandAnimationPending(pending);
+ if (pending) {
+ mStatusBarWindow.postDelayed(mTimeoutRunnable, LAUNCH_TIMEOUT);
+ } else {
+ mStatusBarWindow.removeCallbacks(mTimeoutRunnable);
+ }
+ }
+
+ class AnimationRunner extends IRemoteAnimationRunner.Stub {
+
+ private final ExpandableNotificationRow mSourceNotification;
+ private final ExpandAnimationParameters mParams;
+ private final Rect mWindowCrop = new Rect();
+ private boolean mLeashShown;
+ private boolean mInstantCollapsePanel = true;
+
+ public AnimationRunner(ExpandableNotificationRow sourceNofitication) {
+ mSourceNotification = sourceNofitication;
+ mParams = new ExpandAnimationParameters();
+ }
+
+ @Override
+ public void onAnimationStart(RemoteAnimationTarget[] remoteAnimationTargets,
+ IRemoteAnimationFinishedCallback iRemoteAnimationFinishedCallback)
+ throws RemoteException {
+ mSourceNotification.post(() -> {
+ for (RemoteAnimationTarget app : remoteAnimationTargets) {
+ if (app.mode == RemoteAnimationTarget.MODE_OPENING) {
+ setExpandAnimationRunning(true);
+ mInstantCollapsePanel = app.position.y == 0
+ && app.sourceContainerBounds.height()
+ >= mNotificationPanel.getHeight();
+ if (!mInstantCollapsePanel) {
+ mNotificationPanel.collapseWithDuration(ANIMATION_DURATION);
+ }
+ ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
+ mParams.startPosition = mSourceNotification.getLocationOnScreen();
+ mParams.startTranslationZ = mSourceNotification.getTranslationZ();
+ int targetWidth = app.sourceContainerBounds.width();
+ int notificationHeight = mSourceNotification.getActualHeight();
+ int notificationWidth = mSourceNotification.getWidth();
+ anim.setDuration(ANIMATION_DURATION);
+ anim.setInterpolator(Interpolators.LINEAR);
+ anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ mParams.linearProgress = animation.getAnimatedFraction();
+ float progress
+ = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
+ mParams.linearProgress);
+ int newWidth = (int) MathUtils.lerp(notificationWidth,
+ targetWidth, progress);
+ mParams.left = (int) ((targetWidth - newWidth) / 2.0f);
+ mParams.right = mParams.left + newWidth;
+ mParams.top = (int) MathUtils.lerp(mParams.startPosition[1],
+ app.position.y, progress);
+ mParams.bottom = (int) MathUtils.lerp(mParams.startPosition[1]
+ + notificationHeight,
+ app.position.y + app.sourceContainerBounds.bottom,
+ progress);
+ applyParamsToWindow(app);
+ applyParamsToNotification(mParams);
+ applyParamsToNotificationList(mParams);
+ }
+ });
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ setExpandAnimationRunning(false);
+ if (mInstantCollapsePanel) {
+ mStatusBar.collapsePanel(false /* animate */);
+ }
+ try {
+ iRemoteAnimationFinishedCallback.onAnimationFinished();
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ anim.start();
+ break;
+ }
+ }
+ setAnimationPending(false);
+ });
+ }
+
+ private void setExpandAnimationRunning(boolean running) {
+ mNotificationPanel.setLaunchingNotification(running);
+ mSourceNotification.setExpandAnimationRunning(running);
+ mStatusBarWindow.setExpandAnimationRunning(running);
+ mNotificationContainer.setExpandingNotification(running ? mSourceNotification : null);
+ if (!running) {
+ applyParamsToNotification(null);
+ applyParamsToNotificationList(null);
+ }
+
+ }
+
+ private void applyParamsToNotificationList(ExpandAnimationParameters params) {
+ mNotificationContainer.applyExpandAnimationParams(params);
+ mNotificationPanel.applyExpandAnimationParams(params);
+ }
+
+ private void applyParamsToNotification(ExpandAnimationParameters params) {
+ mSourceNotification.applyExpandAnimationParams(params);
+ }
+
+ private void applyParamsToWindow(RemoteAnimationTarget app) {
+ SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ if (!mLeashShown) {
+ t.show(app.leash);
+ mLeashShown = true;
+ }
+ Matrix m = new Matrix();
+ m.postTranslate(0, (float) (mParams.top - app.position.y));
+ t.setMatrix(app.leash, m, new float[9]);
+ mWindowCrop.set(mParams.left, 0, mParams.right, mParams.getHeight());
+ t.setWindowCrop(app.leash, mWindowCrop);
+ ViewRootImpl viewRootImpl = mSourceNotification.getViewRootImpl();
+ if (viewRootImpl != null) {
+ Surface systemUiSurface = viewRootImpl.mSurface;
+ t.deferTransactionUntilSurface(app.leash, systemUiSurface,
+ systemUiSurface.getNextFrameNumber());
+ }
+ t.apply();
+ }
+
+ @Override
+ public void onAnimationCancelled() throws RemoteException {
+ mSourceNotification.post(() -> {
+ setAnimationPending(false);
+ mStatusBar.onLaunchAnimationCancelled();
+ });
+ }
+ };
+
+ public static class ExpandAnimationParameters {
+ float linearProgress;
+ int[] startPosition;
+ float startTranslationZ;
+ int left;
+ int top;
+ int right;
+ int bottom;
+
+ public ExpandAnimationParameters() {
+ }
+
+ public int getTop() {
+ return top;
+ }
+
+ public int getWidth() {
+ return right - left;
+ }
+
+ public int getHeight() {
+ return bottom - top;
+ }
+
+ public int getTopChange() {
+ return Math.min(top - startPosition[1], 0);
+ }
+
+
+ public float getProgress(long delay, long duration) {
+ return MathUtils.constrain((linearProgress * ANIMATION_DURATION - delay)
+ / duration, 0.0f, 1.0f);
+ }
+
+ public float getStartTranslationZ() {
+ return startTranslationZ;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java
new file mode 100644
index 000000000000..d7b211f90be5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java
@@ -0,0 +1,77 @@
+/*
+ * 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.statusbar.notification;
+
+import android.util.FloatProperty;
+import android.util.Property;
+import android.view.View;
+
+import com.android.systemui.statusbar.stack.AnimationProperties;
+
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/**
+ * An animatable property of a view. Used with {@link PropertyAnimator}
+ */
+public interface AnimatableProperty {
+ int getAnimationStartTag();
+
+ int getAnimationEndTag();
+
+ int getAnimatorTag();
+
+ Property getProperty();
+
+ static <T extends View> AnimatableProperty from(String name, BiConsumer<T, Float> setter,
+ Function<T, Float> getter, int animatorTag, int startValueTag, int endValueTag) {
+ Property<T, Float> property = new FloatProperty<T>(name) {
+
+ @Override
+ public Float get(T object) {
+ return getter.apply(object);
+ }
+
+ @Override
+ public void setValue(T object, float value) {
+ setter.accept(object, value);
+ }
+ };
+ return new AnimatableProperty() {
+ @Override
+ public int getAnimationStartTag() {
+ return startValueTag;
+ }
+
+ @Override
+ public int getAnimationEndTag() {
+ return endValueTag;
+ }
+
+ @Override
+ public int getAnimatorTag() {
+ return animatorTag;
+ }
+
+ @Override
+ public Property getProperty() {
+ return property;
+ }
+ };
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java
index 92f26d65f25e..d3a325d3b91a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java
@@ -21,6 +21,8 @@ import android.util.Pools;
import android.view.View;
import android.widget.ImageView;
+import com.android.internal.widget.MessagingImageMessage;
+import com.android.internal.widget.MessagingMessage;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.statusbar.CrossFadeHelper;
@@ -39,8 +41,8 @@ public class ImageTransformState extends TransformState {
private Icon mIcon;
@Override
- public void initFrom(View view) {
- super.initFrom(view);
+ public void initFrom(View view, TransformInfo transformInfo) {
+ super.initFrom(view, transformInfo);
if (view instanceof ImageView) {
mIcon = (Icon) view.getTag(ICON_TAG);
}
@@ -117,13 +119,15 @@ public class ImageTransformState extends TransformState {
@Override
protected boolean transformScale(TransformState otherState) {
- return true;
+ return sameAs(otherState);
}
@Override
public void recycle() {
super.recycle();
- sInstancePool.release(this);
+ if (getClass() == ImageTransformState.class) {
+ sInstancePool.release(this);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingImageTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingImageTransformState.java
new file mode 100644
index 000000000000..b97995dd5f93
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingImageTransformState.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.notification;
+
+import android.util.Pools;
+import android.view.View;
+
+import com.android.internal.widget.MessagingImageMessage;
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.ViewTransformationHelper;
+
+/**
+ * A transform state of a image view.
+*/
+public class MessagingImageTransformState extends ImageTransformState {
+ private static Pools.SimplePool<MessagingImageTransformState> sInstancePool
+ = new Pools.SimplePool<>(40);
+ private static final int START_ACTUAL_WIDTH = R.id.transformation_start_actual_width;
+ private static final int START_ACTUAL_HEIGHT = R.id.transformation_start_actual_height;
+ private MessagingImageMessage mImageMessage;
+
+ @Override
+ public void initFrom(View view, TransformInfo transformInfo) {
+ super.initFrom(view, transformInfo);
+ mImageMessage = (MessagingImageMessage) view;
+ }
+
+ @Override
+ protected boolean sameAs(TransformState otherState) {
+ if (super.sameAs(otherState)) {
+ return true;
+ }
+ if (otherState instanceof MessagingImageTransformState) {
+ MessagingImageTransformState otherMessage = (MessagingImageTransformState) otherState;
+ return mImageMessage.sameAs(otherMessage.mImageMessage);
+ }
+ return false;
+ }
+
+ public static MessagingImageTransformState obtain() {
+ MessagingImageTransformState instance = sInstancePool.acquire();
+ if (instance != null) {
+ return instance;
+ }
+ return new MessagingImageTransformState();
+ }
+
+ @Override
+ protected boolean transformScale(TransformState otherState) {
+ return false;
+ }
+
+ @Override
+ protected void transformViewFrom(TransformState otherState, int transformationFlags,
+ ViewTransformationHelper.CustomTransformation customTransformation,
+ float transformationAmount) {
+ super.transformViewFrom(otherState, transformationFlags, customTransformation,
+ transformationAmount);
+ float interpolatedValue = mDefaultInterpolator.getInterpolation(
+ transformationAmount);
+ if (otherState instanceof MessagingImageTransformState && sameAs(otherState)) {
+ MessagingImageMessage otherMessage
+ = ((MessagingImageTransformState) otherState).mImageMessage;
+ if (transformationAmount == 0.0f) {
+ setStartActualWidth(otherMessage.getActualWidth());
+ setStartActualHeight(otherMessage.getActualHeight());
+ }
+ float startActualWidth = getStartActualWidth();
+ mImageMessage.setActualWidth(
+ (int) NotificationUtils.interpolate(startActualWidth,
+ mImageMessage.getStaticWidth(),
+ interpolatedValue));
+ float startActualHeight = getStartActualHeight();
+ mImageMessage.setActualHeight(
+ (int) NotificationUtils.interpolate(startActualHeight,
+ mImageMessage.getHeight(),
+ interpolatedValue));
+ }
+ }
+
+ public int getStartActualWidth() {
+ Object tag = mTransformedView.getTag(START_ACTUAL_WIDTH);
+ return tag == null ? -1 : (int) tag;
+ }
+
+ public void setStartActualWidth(int actualWidth) {
+ mTransformedView.setTag(START_ACTUAL_WIDTH, actualWidth);
+ }
+
+ public int getStartActualHeight() {
+ Object tag = mTransformedView.getTag(START_ACTUAL_HEIGHT);
+ return tag == null ? -1 : (int) tag;
+ }
+
+ public void setStartActualHeight(int actualWidth) {
+ mTransformedView.setTag(START_ACTUAL_HEIGHT, actualWidth);
+ }
+
+ @Override
+ public void recycle() {
+ super.recycle();
+ if (getClass() == MessagingImageTransformState.class) {
+ sInstancePool.release(this);
+ }
+ }
+
+ @Override
+ protected void resetTransformedView() {
+ super.resetTransformedView();
+ mImageMessage.setActualWidth(mImageMessage.getStaticWidth());
+ mImageMessage.setActualHeight(mImageMessage.getHeight());
+ }
+
+ @Override
+ protected void reset() {
+ super.reset();
+ mImageMessage = null;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
new file mode 100644
index 000000000000..314a31d336fd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
@@ -0,0 +1,433 @@
+/*
+ * 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.statusbar.notification;
+
+import android.content.res.Resources;
+import android.util.Pools;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.internal.widget.MessagingGroup;
+import com.android.internal.widget.MessagingImageMessage;
+import com.android.internal.widget.MessagingLayout;
+import com.android.internal.widget.MessagingLinearLayout;
+import com.android.internal.widget.MessagingMessage;
+import com.android.internal.widget.MessagingPropertyAnimator;
+import com.android.systemui.Interpolators;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * A transform state of the action list
+*/
+public class MessagingLayoutTransformState extends TransformState {
+
+ private static Pools.SimplePool<MessagingLayoutTransformState> sInstancePool
+ = new Pools.SimplePool<>(40);
+ private MessagingLinearLayout mMessageContainer;
+ private MessagingLayout mMessagingLayout;
+ private HashMap<MessagingGroup, MessagingGroup> mGroupMap = new HashMap<>();
+ private float mRelativeTranslationOffset;
+
+ public static MessagingLayoutTransformState obtain() {
+ MessagingLayoutTransformState instance = sInstancePool.acquire();
+ if (instance != null) {
+ return instance;
+ }
+ return new MessagingLayoutTransformState();
+ }
+
+ @Override
+ public void initFrom(View view, TransformInfo transformInfo) {
+ super.initFrom(view, transformInfo);
+ if (mTransformedView instanceof MessagingLinearLayout) {
+ mMessageContainer = (MessagingLinearLayout) mTransformedView;
+ mMessagingLayout = mMessageContainer.getMessagingLayout();
+ Resources resources = view.getContext().getResources();
+ mRelativeTranslationOffset = resources.getDisplayMetrics().density * 8;
+ }
+ }
+
+ @Override
+ public boolean transformViewTo(TransformState otherState, float transformationAmount) {
+ if (otherState instanceof MessagingLayoutTransformState) {
+ // It's a party! Let's transform between these two layouts!
+ transformViewInternal((MessagingLayoutTransformState) otherState, transformationAmount,
+ true /* to */);
+ return true;
+ } else {
+ return super.transformViewTo(otherState, transformationAmount);
+ }
+ }
+
+ @Override
+ public void transformViewFrom(TransformState otherState, float transformationAmount) {
+ if (otherState instanceof MessagingLayoutTransformState) {
+ // It's a party! Let's transform between these two layouts!
+ transformViewInternal((MessagingLayoutTransformState) otherState, transformationAmount,
+ false /* to */);
+ } else {
+ super.transformViewFrom(otherState, transformationAmount);
+ }
+ }
+
+ private void transformViewInternal(MessagingLayoutTransformState mlt,
+ float transformationAmount, boolean to) {
+ ensureVisible();
+ ArrayList<MessagingGroup> ownGroups = filterHiddenGroups(
+ mMessagingLayout.getMessagingGroups());
+ ArrayList<MessagingGroup> otherGroups = filterHiddenGroups(
+ mlt.mMessagingLayout.getMessagingGroups());
+ HashMap<MessagingGroup, MessagingGroup> pairs = findPairs(ownGroups, otherGroups);
+ MessagingGroup lastPairedGroup = null;
+ float currentTranslation = 0;
+ float transformationDistanceRemaining = 0;
+ for (int i = ownGroups.size() - 1; i >= 0; i--) {
+ MessagingGroup ownGroup = ownGroups.get(i);
+ MessagingGroup matchingGroup = pairs.get(ownGroup);
+ if (!isGone(ownGroup)) {
+ if (matchingGroup != null) {
+ transformGroups(ownGroup, matchingGroup, transformationAmount, to);
+ if (lastPairedGroup == null) {
+ lastPairedGroup = ownGroup;
+ if (to){
+ float totalTranslation = ownGroup.getTop() - matchingGroup.getTop();
+ transformationDistanceRemaining
+ = matchingGroup.getAvatar().getTranslationY();
+ currentTranslation = transformationDistanceRemaining - totalTranslation;
+ } else {
+ float totalTranslation = matchingGroup.getTop() - ownGroup.getTop();
+ currentTranslation = ownGroup.getAvatar().getTranslationY();
+ transformationDistanceRemaining = currentTranslation - totalTranslation;
+ }
+ }
+ } else {
+ float groupTransformationAmount = transformationAmount;
+ if (lastPairedGroup != null) {
+ adaptGroupAppear(ownGroup, transformationAmount, currentTranslation,
+ to);
+ int distance = lastPairedGroup.getTop() - ownGroup.getTop();
+ float transformationDistance = mTransformInfo.isAnimating()
+ ? distance
+ : ownGroup.getHeight() * 0.75f;
+ float translationProgress = transformationDistanceRemaining
+ - (distance - transformationDistance);
+ groupTransformationAmount =
+ translationProgress / transformationDistance;
+ groupTransformationAmount = Math.max(0.0f, Math.min(1.0f,
+ groupTransformationAmount));
+ if (to) {
+ groupTransformationAmount = 1.0f - groupTransformationAmount;
+ }
+ }
+ if (to) {
+ disappear(ownGroup, groupTransformationAmount);
+ } else {
+ appear(ownGroup, groupTransformationAmount);
+ }
+ }
+ }
+ }
+ }
+
+ private void appear(MessagingGroup ownGroup, float transformationAmount) {
+ MessagingLinearLayout ownMessages = ownGroup.getMessageContainer();
+ for (int j = 0; j < ownMessages.getChildCount(); j++) {
+ View child = ownMessages.getChildAt(j);
+ if (isGone(child)) {
+ continue;
+ }
+ appear(child, transformationAmount);
+ setClippingDeactivated(child, true);
+ }
+ appear(ownGroup.getAvatar(), transformationAmount);
+ appear(ownGroup.getSenderView(), transformationAmount);
+ appear(ownGroup.getIsolatedMessage(), transformationAmount);
+ setClippingDeactivated(ownGroup.getSenderView(), true);
+ setClippingDeactivated(ownGroup.getAvatar(), true);
+ }
+
+ private void adaptGroupAppear(MessagingGroup ownGroup, float transformationAmount,
+ float overallTranslation, boolean to) {
+ float relativeOffset;
+ if (to) {
+ relativeOffset = transformationAmount * mRelativeTranslationOffset;
+ } else {
+ relativeOffset = (1.0f - transformationAmount) * mRelativeTranslationOffset;
+ }
+ if (ownGroup.getSenderView().getVisibility() != View.GONE) {
+ relativeOffset *= 0.5f;
+ }
+ ownGroup.getMessageContainer().setTranslationY(relativeOffset);
+ ownGroup.setTranslationY(overallTranslation * 0.85f);
+ }
+
+ private void disappear(MessagingGroup ownGroup, float transformationAmount) {
+ MessagingLinearLayout ownMessages = ownGroup.getMessageContainer();
+ for (int j = 0; j < ownMessages.getChildCount(); j++) {
+ View child = ownMessages.getChildAt(j);
+ if (isGone(child)) {
+ continue;
+ }
+ disappear(child, transformationAmount);
+ setClippingDeactivated(child, true);
+ }
+ disappear(ownGroup.getAvatar(), transformationAmount);
+ disappear(ownGroup.getSenderView(), transformationAmount);
+ disappear(ownGroup.getIsolatedMessage(), transformationAmount);
+ setClippingDeactivated(ownGroup.getSenderView(), true);
+ setClippingDeactivated(ownGroup.getAvatar(), true);
+ }
+
+ private void appear(View child, float transformationAmount) {
+ if (child == null || child.getVisibility() == View.GONE) {
+ return;
+ }
+ TransformState ownState = TransformState.createFrom(child, mTransformInfo);
+ ownState.appear(transformationAmount, null);
+ ownState.recycle();
+ }
+
+ private void disappear(View child, float transformationAmount) {
+ if (child == null || child.getVisibility() == View.GONE) {
+ return;
+ }
+ TransformState ownState = TransformState.createFrom(child, mTransformInfo);
+ ownState.disappear(transformationAmount, null);
+ ownState.recycle();
+ }
+
+ private ArrayList<MessagingGroup> filterHiddenGroups(
+ ArrayList<MessagingGroup> groups) {
+ ArrayList<MessagingGroup> result = new ArrayList<>(groups);
+ for (int i = 0; i < result.size(); i++) {
+ MessagingGroup messagingGroup = result.get(i);
+ if (isGone(messagingGroup)) {
+ result.remove(i);
+ i--;
+ }
+ }
+ return result;
+ }
+
+ private void transformGroups(MessagingGroup ownGroup, MessagingGroup otherGroup,
+ float transformationAmount, boolean to) {
+ boolean useLinearTransformation =
+ otherGroup.getIsolatedMessage() == null && !mTransformInfo.isAnimating();
+ transformView(transformationAmount, to, ownGroup.getSenderView(), otherGroup.getSenderView(),
+ true /* sameAsAny */, useLinearTransformation);
+ transformView(transformationAmount, to, ownGroup.getAvatar(), otherGroup.getAvatar(),
+ true /* sameAsAny */, useLinearTransformation);
+ List<MessagingMessage> ownMessages = ownGroup.getMessages();
+ List<MessagingMessage> otherMessages = otherGroup.getMessages();
+ float previousTranslation = 0;
+ for (int i = 0; i < ownMessages.size(); i++) {
+ View child = ownMessages.get(ownMessages.size() - 1 - i).getView();
+ if (isGone(child)) {
+ continue;
+ }
+ int otherIndex = otherMessages.size() - 1 - i;
+ View otherChild = null;
+ if (otherIndex >= 0) {
+ otherChild = otherMessages.get(otherIndex).getView();
+ if (isGone(otherChild)) {
+ otherChild = null;
+ }
+ }
+ if (otherChild == null) {
+ float distanceToTop = child.getTop() + child.getHeight() + previousTranslation;
+ transformationAmount = distanceToTop / child.getHeight();
+ transformationAmount = Math.max(0.0f, Math.min(1.0f, transformationAmount));
+ if (to) {
+ transformationAmount = 1.0f - transformationAmount;
+ }
+ }
+ transformView(transformationAmount, to, child, otherChild, false, /* sameAsAny */
+ useLinearTransformation);
+ if (transformationAmount == 0.0f
+ && otherGroup.getIsolatedMessage() == otherChild) {
+ ownGroup.setTransformingImages(true);
+ }
+ if (otherChild == null) {
+ child.setTranslationY(previousTranslation);
+ setClippingDeactivated(child, true);
+ } else if (to) {
+ float totalTranslation = child.getTop() + ownGroup.getTop()
+ - otherChild.getTop() - otherChild.getTop();
+ previousTranslation = otherChild.getTranslationY() - totalTranslation;
+ } else {
+ previousTranslation = child.getTranslationY();
+ }
+ }
+ ownGroup.updateClipRect();
+ }
+
+ private void transformView(float transformationAmount, boolean to, View ownView,
+ View otherView, boolean sameAsAny, boolean useLinearTransformation) {
+ TransformState ownState = TransformState.createFrom(ownView, mTransformInfo);
+ if (useLinearTransformation) {
+ ownState.setDefaultInterpolator(Interpolators.LINEAR);
+ }
+ ownState.setIsSameAsAnyView(sameAsAny);
+ if (to) {
+ if (otherView != null) {
+ TransformState otherState = TransformState.createFrom(otherView, mTransformInfo);
+ ownState.transformViewTo(otherState, transformationAmount);
+ otherState.recycle();
+ } else {
+ ownState.disappear(transformationAmount, null);
+ }
+ } else {
+ if (otherView != null) {
+ TransformState otherState = TransformState.createFrom(otherView, mTransformInfo);
+ ownState.transformViewFrom(otherState, transformationAmount);
+ otherState.recycle();
+ } else {
+ ownState.appear(transformationAmount, null);
+ }
+ }
+ ownState.recycle();
+ }
+
+ private HashMap<MessagingGroup, MessagingGroup> findPairs(ArrayList<MessagingGroup> ownGroups,
+ ArrayList<MessagingGroup> otherGroups) {
+ mGroupMap.clear();
+ int lastMatch = Integer.MAX_VALUE;
+ for (int i = ownGroups.size() - 1; i >= 0; i--) {
+ MessagingGroup ownGroup = ownGroups.get(i);
+ MessagingGroup bestMatch = null;
+ int bestCompatibility = 0;
+ for (int j = Math.min(otherGroups.size(), lastMatch) - 1; j >= 0; j--) {
+ MessagingGroup otherGroup = otherGroups.get(j);
+ int compatibility = ownGroup.calculateGroupCompatibility(otherGroup);
+ if (compatibility > bestCompatibility) {
+ bestCompatibility = compatibility;
+ bestMatch = otherGroup;
+ lastMatch = j;
+ }
+ }
+ if (bestMatch != null) {
+ mGroupMap.put(ownGroup, bestMatch);
+ }
+ }
+ return mGroupMap;
+ }
+
+ private boolean isGone(View view) {
+ if (view.getVisibility() == View.GONE) {
+ return true;
+ }
+ final ViewGroup.LayoutParams lp = view.getLayoutParams();
+ if (lp instanceof MessagingLinearLayout.LayoutParams
+ && ((MessagingLinearLayout.LayoutParams) lp).hide) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void setVisible(boolean visible, boolean force) {
+ super.setVisible(visible, force);
+ resetTransformedView();
+ ArrayList<MessagingGroup> ownGroups = mMessagingLayout.getMessagingGroups();
+ for (int i = 0; i < ownGroups.size(); i++) {
+ MessagingGroup ownGroup = ownGroups.get(i);
+ if (!isGone(ownGroup)) {
+ MessagingLinearLayout ownMessages = ownGroup.getMessageContainer();
+ for (int j = 0; j < ownMessages.getChildCount(); j++) {
+ View child = ownMessages.getChildAt(j);
+ setVisible(child, visible, force);
+ }
+ setVisible(ownGroup.getAvatar(), visible, force);
+ setVisible(ownGroup.getSenderView(), visible, force);
+ MessagingImageMessage isolatedMessage = ownGroup.getIsolatedMessage();
+ if (isolatedMessage != null) {
+ setVisible(isolatedMessage, visible, force);
+ }
+ }
+ }
+ }
+
+ private void setVisible(View child, boolean visible, boolean force) {
+ if (isGone(child) || MessagingPropertyAnimator.isAnimatingAlpha(child)) {
+ return;
+ }
+ TransformState ownState = TransformState.createFrom(child, mTransformInfo);
+ ownState.setVisible(visible, force);
+ ownState.recycle();
+ }
+
+ @Override
+ protected void resetTransformedView() {
+ super.resetTransformedView();
+ ArrayList<MessagingGroup> ownGroups = mMessagingLayout.getMessagingGroups();
+ for (int i = 0; i < ownGroups.size(); i++) {
+ MessagingGroup ownGroup = ownGroups.get(i);
+ if (!isGone(ownGroup)) {
+ MessagingLinearLayout ownMessages = ownGroup.getMessageContainer();
+ for (int j = 0; j < ownMessages.getChildCount(); j++) {
+ View child = ownMessages.getChildAt(j);
+ if (isGone(child)) {
+ continue;
+ }
+ resetTransformedView(child);
+ setClippingDeactivated(child, false);
+ }
+ resetTransformedView(ownGroup.getAvatar());
+ resetTransformedView(ownGroup.getSenderView());
+ MessagingImageMessage isolatedMessage = ownGroup.getIsolatedMessage();
+ if (isolatedMessage != null) {
+ resetTransformedView(isolatedMessage);
+ }
+ setClippingDeactivated(ownGroup.getAvatar(), false);
+ setClippingDeactivated(ownGroup.getSenderView(), false);
+ ownGroup.setTranslationY(0);
+ ownGroup.getMessageContainer().setTranslationY(0);
+ }
+ ownGroup.setTransformingImages(false);
+ ownGroup.updateClipRect();
+ }
+ }
+
+ @Override
+ public void prepareFadeIn() {
+ super.prepareFadeIn();
+ setVisible(true /* visible */, false /* force */);
+ }
+
+ private void resetTransformedView(View child) {
+ TransformState ownState = TransformState.createFrom(child, mTransformInfo);
+ ownState.resetTransformedView();
+ ownState.recycle();
+ }
+
+ @Override
+ protected void reset() {
+ super.reset();
+ mMessageContainer = null;
+ mMessagingLayout = null;
+ }
+
+ @Override
+ public void recycle() {
+ super.recycle();
+ mGroupMap.clear();;
+ sInstancePool.release(this);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
index bca4b43afc0c..adc091457364 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
@@ -115,4 +115,9 @@ public class NotificationCustomViewWrapper extends NotificationViewWrapper {
super.setLegacy(legacy);
mIsLegacy = legacy;
}
+
+ @Override
+ public boolean shouldClipToRounding(boolean topRounded, boolean bottomRounded) {
+ return true;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java
index eb211a10737b..548f006c934d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java
@@ -58,6 +58,11 @@ public class NotificationMediaTemplateViewWrapper extends NotificationTemplateVi
@Override
public boolean isDimmable() {
- return false;
+ return getCustomBackgroundColor() == 0;
+ }
+
+ @Override
+ public boolean shouldClipToRounding(boolean topRounded, boolean bottomRounded) {
+ return true;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java
index f6ee1cac9c3d..fb5644f76ce3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java
@@ -16,7 +16,9 @@
package com.android.systemui.statusbar.notification;
+import com.android.internal.widget.MessagingLayout;
import com.android.internal.widget.MessagingLinearLayout;
+import com.android.systemui.R;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.TransformableView;
@@ -32,41 +34,20 @@ import java.util.ArrayList;
*/
public class NotificationMessagingTemplateViewWrapper extends NotificationTemplateViewWrapper {
- private View mContractedMessage;
- private ArrayList<View> mHistoricMessages = new ArrayList<View>();
+ private final int mMinHeightWithActions;
+ private MessagingLayout mMessagingLayout;
+ private MessagingLinearLayout mMessagingLinearLayout;
protected NotificationMessagingTemplateViewWrapper(Context ctx, View view,
ExpandableNotificationRow row) {
super(ctx, view, row);
+ mMessagingLayout = (MessagingLayout) view;
+ mMinHeightWithActions = NotificationUtils.getFontScaledHeight(ctx,
+ R.dimen.notification_messaging_actions_min_height);
}
private void resolveViews() {
- mContractedMessage = null;
-
- View container = mView.findViewById(com.android.internal.R.id.notification_messaging);
- if (container instanceof MessagingLinearLayout
- && ((MessagingLinearLayout) container).getChildCount() > 0) {
- MessagingLinearLayout messagingContainer = (MessagingLinearLayout) container;
-
- int childCount = messagingContainer.getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = messagingContainer.getChildAt(i);
-
- if (child.getVisibility() == View.GONE
- && child instanceof TextView
- && !TextUtils.isEmpty(((TextView) child).getText())) {
- mHistoricMessages.add(child);
- }
-
- // Only consider the first visible child - transforming to a position other than the
- // first looks bad because we have to move across other messages that are fading in.
- if (child.getId() == messagingContainer.getContractedChildId()) {
- mContractedMessage = child;
- } else if (child.getVisibility() == View.VISIBLE) {
- break;
- }
- }
- }
+ mMessagingLinearLayout = mMessagingLayout.getMessagingLinearLayout();
}
@Override
@@ -81,16 +62,22 @@ public class NotificationMessagingTemplateViewWrapper extends NotificationTempla
protected void updateTransformedTypes() {
// This also clears the existing types
super.updateTransformedTypes();
- if (mContractedMessage != null) {
- mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TEXT,
- mContractedMessage);
+ if (mMessagingLinearLayout != null) {
+ mTransformationHelper.addTransformedView(mMessagingLinearLayout.getId(),
+ mMessagingLinearLayout);
}
}
@Override
public void setRemoteInputVisible(boolean visible) {
- for (int i = 0; i < mHistoricMessages.size(); i++) {
- mHistoricMessages.get(i).setVisibility(visible ? View.VISIBLE : View.GONE);
+ mMessagingLayout.showHistoricMessages(visible);
+ }
+
+ @Override
+ public int getMinLayoutHeight() {
+ if (mActionsContainer != null && mActionsContainer.getVisibility() != View.GONE) {
+ return mMinHeightWithActions;
}
+ return super.getMinLayoutHeight();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
index bb979ebd1288..d463eae6e43f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
@@ -41,7 +41,7 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp
private ProgressBar mProgressBar;
private TextView mTitle;
private TextView mText;
- private View mActionsContainer;
+ protected View mActionsContainer;
private View mReplyAction;
private Rect mTmpRect = new Rect();
@@ -265,6 +265,15 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp
updateActionOffset();
}
+ @Override
+ public boolean shouldClipToRounding(boolean topRounded, boolean bottomRounded) {
+ if (super.shouldClipToRounding(topRounded, bottomRounded)) {
+ return true;
+ }
+ return bottomRounded && mActionsContainer != null
+ && mActionsContainer.getVisibility() != View.GONE;
+ }
+
private void updateActionOffset() {
if (mActionsContainer != null) {
// We should never push the actions higher than they are in the headsup view.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java
index 3115361c6927..7e2336c4454b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java
@@ -61,9 +61,14 @@ public class NotificationUtils {
return sLocationOffset[1] - sLocationBase[1];
}
- public static boolean isHapticFeedbackDisabled(Context context) {
- return Settings.System.getIntForUser(context.getContentResolver(),
- Settings.System.HAPTIC_FEEDBACK_ENABLED, 0, UserHandle.USER_CURRENT) == 0;
+ /**
+ * @param dimenId the dimen to look up
+ * @return the font scaled dimen as if it were in sp but doesn't shrink sizes below dp
+ */
+ public static int getFontScaledHeight(Context context, int dimenId) {
+ int dimensionPixelSize = context.getResources().getDimensionPixelSize(dimenId);
+ float factor = Math.max(1.0f, context.getResources().getDisplayMetrics().scaledDensity /
+ context.getResources().getDisplayMetrics().density);
+ return (int) (dimensionPixelSize * factor);
}
-
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
index 5200d6962ed5..17eb4c110031 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
@@ -190,4 +190,12 @@ public abstract class NotificationViewWrapper implements TransformableView {
public boolean disallowSingleClick(float x, float y) {
return false;
}
+
+ public int getMinLayoutHeight() {
+ return 0;
+ }
+
+ public boolean shouldClipToRounding(boolean topRounded, boolean bottomRounded) {
+ return false;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
index 80ba94398e1b..92dcc9e3cf9f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
@@ -34,6 +34,19 @@ import com.android.systemui.statusbar.stack.ViewState;
*/
public class PropertyAnimator {
+ public static <T extends View> void setProperty(final T view,
+ AnimatableProperty animatableProperty, float newEndValue,
+ AnimationProperties properties, boolean animated) {
+ int animatorTag = animatableProperty.getAnimatorTag();
+ ValueAnimator previousAnimator = ViewState.getChildTag(view, animatorTag);
+ if (previousAnimator != null || animated) {
+ startAnimation(view, animatableProperty, newEndValue, properties);
+ } else {
+ // no new animation needed, let's just apply the value
+ animatableProperty.getProperty().set(view, newEndValue);
+ }
+ }
+
public static <T extends View> void startAnimation(final T view,
AnimatableProperty animatableProperty, float newEndValue,
AnimationProperties properties) {
@@ -102,10 +115,4 @@ public class PropertyAnimator {
view.setTag(animationEndTag, newEndValue);
}
- public interface AnimatableProperty {
- int getAnimationStartTag();
- int getAnimationEndTag();
- int getAnimatorTag();
- Property getProperty();
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RowInflaterTask.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RowInflaterTask.java
index 3491f8121545..c2141714b391 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RowInflaterTask.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RowInflaterTask.java
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification;
import android.content.Context;
import android.support.v4.view.AsyncLayoutInflater;
+import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@@ -30,15 +31,23 @@ import com.android.systemui.statusbar.NotificationData;
* An inflater task that asynchronously inflates a ExpandableNotificationRow
*/
public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInflateFinishedListener {
+
+ private static final String TAG = "RowInflaterTask";
+ private static final boolean TRACE_ORIGIN = true;
+
private RowInflationFinishedListener mListener;
private NotificationData.Entry mEntry;
private boolean mCancelled;
+ private Throwable mInflateOrigin;
/**
* Inflates a new notificationView. This should not be called twice on this object
*/
public void inflate(Context context, ViewGroup parent, NotificationData.Entry entry,
RowInflationFinishedListener listener) {
+ if (TRACE_ORIGIN) {
+ mInflateOrigin = new Throwable("inflate requested here");
+ }
mListener = listener;
AsyncLayoutInflater inflater = new AsyncLayoutInflater(context);
mEntry = entry;
@@ -54,8 +63,16 @@ public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInf
@Override
public void onInflateFinished(View view, int resid, ViewGroup parent) {
if (!mCancelled) {
- mEntry.onInflationTaskFinished();
- mListener.onInflationFinished((ExpandableNotificationRow) view);
+ try {
+ mEntry.onInflationTaskFinished();
+ mListener.onInflationFinished((ExpandableNotificationRow) view);
+ } catch (Throwable t) {
+ if (mInflateOrigin != null) {
+ Log.e(TAG, "Error in inflation finished listener: " + t, mInflateOrigin);
+ t.addSuppressed(mInflateOrigin);
+ }
+ throw t;
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TextViewTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TextViewTransformState.java
index c4aabe48f61c..178c8130307f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TextViewTransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TextViewTransformState.java
@@ -33,8 +33,8 @@ public class TextViewTransformState extends TransformState {
private TextView mText;
@Override
- public void initFrom(View view) {
- super.initFrom(view);
+ public void initFrom(View view, TransformInfo transformInfo) {
+ super.initFrom(view, transformInfo);
if (view instanceof TextView) {
mText = (TextView) view;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
index bafedff4f78c..fc8ceb6620a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
@@ -26,6 +26,9 @@ import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
+import com.android.internal.widget.MessagingImageMessage;
+import com.android.internal.widget.MessagingPropertyAnimator;
+import com.android.internal.widget.ViewClippingUtil;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.statusbar.CrossFadeHelper;
@@ -43,23 +46,46 @@ public class TransformState {
public static final int TRANSFORM_ALL = TRANSFORM_X | TRANSFORM_Y;
private static final float UNDEFINED = -1f;
- private static final int CLIP_CLIPPING_SET = R.id.clip_children_set_tag;
- private static final int CLIP_CHILDREN_TAG = R.id.clip_children_tag;
- private static final int CLIP_TO_PADDING = R.id.clip_to_padding_tag;
private static final int TRANSFORMATION_START_X = R.id.transformation_start_x_tag;
private static final int TRANSFORMATION_START_Y = R.id.transformation_start_y_tag;
private static final int TRANSFORMATION_START_SCLALE_X = R.id.transformation_start_scale_x_tag;
private static final int TRANSFORMATION_START_SCLALE_Y = R.id.transformation_start_scale_y_tag;
private static Pools.SimplePool<TransformState> sInstancePool = new Pools.SimplePool<>(40);
+ private static ViewClippingUtil.ClippingParameters CLIPPING_PARAMETERS
+ = new ViewClippingUtil.ClippingParameters() {
+ @Override
+ public boolean shouldFinish(View view) {
+ if (view instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+ return !row.isChildInGroup();
+ }
+ return false;
+ }
+
+ @Override
+ public void onClippingStateChanged(View view, boolean isClipping) {
+ if (view instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+ if (isClipping) {
+ row.setClipToActualHeight(true);
+ } else if (row.isChildInGroup()) {
+ row.setClipToActualHeight(false);
+ }
+ }
+ }
+ };
protected View mTransformedView;
+ protected TransformInfo mTransformInfo;
private int[] mOwnPosition = new int[2];
private boolean mSameAsAny;
private float mTransformationEndY = UNDEFINED;
private float mTransformationEndX = UNDEFINED;
+ protected Interpolator mDefaultInterpolator = Interpolators.FAST_OUT_SLOW_IN;
- public void initFrom(View view) {
+ public void initFrom(View view, TransformInfo transformInfo) {
mTransformedView = view;
+ mTransformInfo = transformInfo;
}
/**
@@ -70,18 +96,22 @@ public class TransformState {
public void transformViewFrom(TransformState otherState, float transformationAmount) {
mTransformedView.animate().cancel();
if (sameAs(otherState)) {
- if (mTransformedView.getVisibility() == View.INVISIBLE
- || mTransformedView.getAlpha() != 1.0f) {
- // We have the same content, lets show ourselves
- mTransformedView.setAlpha(1.0f);
- mTransformedView.setVisibility(View.VISIBLE);
- }
+ ensureVisible();
} else {
CrossFadeHelper.fadeIn(mTransformedView, transformationAmount);
}
transformViewFullyFrom(otherState, transformationAmount);
}
+ protected void ensureVisible() {
+ if (mTransformedView.getVisibility() == View.INVISIBLE
+ || mTransformedView.getAlpha() != 1.0f) {
+ // We have the same content, lets show ourselves
+ mTransformedView.setAlpha(1.0f);
+ mTransformedView.setVisibility(View.VISIBLE);
+ }
+ }
+
public void transformViewFullyFrom(TransformState otherState, float transformationAmount) {
transformViewFrom(otherState, TRANSFORM_ALL, null, transformationAmount);
}
@@ -102,19 +132,26 @@ public class TransformState {
transformViewFrom(otherState, TRANSFORM_Y, null, transformationAmount);
}
- private void transformViewFrom(TransformState otherState, int transformationFlags,
+ protected void transformViewFrom(TransformState otherState, int transformationFlags,
ViewTransformationHelper.CustomTransformation customTransformation,
float transformationAmount) {
final View transformedView = mTransformedView;
boolean transformX = (transformationFlags & TRANSFORM_X) != 0;
boolean transformY = (transformationFlags & TRANSFORM_Y) != 0;
- boolean transformScale = transformScale(otherState);
+ int viewHeight = getViewHeight();
+ int otherHeight = otherState.getViewHeight();
+ boolean differentHeight = otherHeight != viewHeight && otherHeight != 0 && viewHeight != 0;
+ int viewWidth = getViewWidth();
+ int otherWidth = otherState.getViewWidth();
+ boolean differentWidth = otherWidth != viewWidth && otherWidth != 0 && viewWidth != 0;
+ boolean transformScale = transformScale(otherState) && (differentHeight || differentWidth);
// lets animate the positions correctly
if (transformationAmount == 0.0f
|| transformX && getTransformationStartX() == UNDEFINED
|| transformY && getTransformationStartY() == UNDEFINED
- || transformScale && getTransformationStartScaleX() == UNDEFINED
- || transformScale && getTransformationStartScaleY() == UNDEFINED) {
+ || transformScale && getTransformationStartScaleX() == UNDEFINED && differentWidth
+ || transformScale && getTransformationStartScaleY() == UNDEFINED
+ && differentHeight) {
int[] otherPosition;
if (transformationAmount != 0.0f) {
otherPosition = otherState.getLaidOutLocationOnScreen();
@@ -132,16 +169,16 @@ public class TransformState {
}
// we also want to animate the scale if we're the same
View otherView = otherState.getTransformedView();
- if (transformScale && otherState.getViewWidth() != getViewWidth()) {
- setTransformationStartScaleX(otherState.getViewWidth() * otherView.getScaleX()
- / (float) getViewWidth());
+ if (transformScale && differentWidth) {
+ setTransformationStartScaleX(otherWidth * otherView.getScaleX()
+ / (float) viewWidth);
transformedView.setPivotX(0);
} else {
setTransformationStartScaleX(UNDEFINED);
}
- if (transformScale && otherState.getViewHeight() != getViewHeight()) {
- setTransformationStartScaleY(otherState.getViewHeight() * otherView.getScaleY()
- / (float) getViewHeight());
+ if (transformScale && differentHeight) {
+ setTransformationStartScaleY(otherHeight * otherView.getScaleY()
+ / (float) viewHeight);
transformedView.setPivotY(0);
} else {
setTransformationStartScaleY(UNDEFINED);
@@ -159,7 +196,7 @@ public class TransformState {
}
setClippingDeactivated(transformedView, true);
}
- float interpolatedValue = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
+ float interpolatedValue = mDefaultInterpolator.getInterpolation(
transformationAmount);
if (transformX) {
float interpolation = interpolatedValue;
@@ -297,7 +334,7 @@ public class TransformState {
}
setClippingDeactivated(transformedView, true);
}
- float interpolatedValue = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
+ float interpolatedValue = mDefaultInterpolator.getInterpolation(
transformationAmount);
int[] otherStablePosition = otherState.getLaidOutLocationOnScreen();
int[] ownPosition = getLaidOutLocationOnScreen();
@@ -354,59 +391,8 @@ public class TransformState {
}
}
- public static void setClippingDeactivated(final View transformedView, boolean deactivated) {
- if (!(transformedView.getParent() instanceof ViewGroup)) {
- return;
- }
- ViewGroup view = (ViewGroup) transformedView.getParent();
- while (true) {
- ArraySet<View> clipSet = (ArraySet<View>) view.getTag(CLIP_CLIPPING_SET);
- if (clipSet == null) {
- clipSet = new ArraySet<>();
- view.setTag(CLIP_CLIPPING_SET, clipSet);
- }
- Boolean clipChildren = (Boolean) view.getTag(CLIP_CHILDREN_TAG);
- if (clipChildren == null) {
- clipChildren = view.getClipChildren();
- view.setTag(CLIP_CHILDREN_TAG, clipChildren);
- }
- Boolean clipToPadding = (Boolean) view.getTag(CLIP_TO_PADDING);
- if (clipToPadding == null) {
- clipToPadding = view.getClipToPadding();
- view.setTag(CLIP_TO_PADDING, clipToPadding);
- }
- ExpandableNotificationRow row = view instanceof ExpandableNotificationRow
- ? (ExpandableNotificationRow) view
- : null;
- if (!deactivated) {
- clipSet.remove(transformedView);
- if (clipSet.isEmpty()) {
- view.setClipChildren(clipChildren);
- view.setClipToPadding(clipToPadding);
- view.setTag(CLIP_CLIPPING_SET, null);
- if (row != null) {
- row.setClipToActualHeight(true);
- }
- }
- } else {
- clipSet.add(transformedView);
- view.setClipChildren(false);
- view.setClipToPadding(false);
- if (row != null && row.isChildInGroup()) {
- // We still want to clip to the parent's height
- row.setClipToActualHeight(false);
- }
- }
- if (row != null && !row.isChildInGroup()) {
- return;
- }
- final ViewParent parent = view.getParent();
- if (parent instanceof ViewGroup) {
- view = (ViewGroup) parent;
- } else {
- return;
- }
- }
+ protected void setClippingDeactivated(final View transformedView, boolean deactivated) {
+ ViewClippingUtil.setClippingDeactivated(transformedView, deactivated, CLIPPING_PARAMETERS);
}
public int[] getLaidOutLocationOnScreen() {
@@ -423,6 +409,9 @@ public class TransformState {
// remove scale
mOwnPosition[0] -= (1.0f - mTransformedView.getScaleX()) * mTransformedView.getPivotX();
mOwnPosition[1] -= (1.0f - mTransformedView.getScaleY()) * mTransformedView.getPivotY();
+
+ // Remove local translations
+ mOwnPosition[1] -= MessagingPropertyAnimator.getLocalTranslationY(mTransformedView);
return mOwnPosition;
}
@@ -444,20 +433,31 @@ public class TransformState {
CrossFadeHelper.fadeOut(mTransformedView, transformationAmount);
}
- public static TransformState createFrom(View view) {
+ public static TransformState createFrom(View view,
+ TransformInfo transformInfo) {
if (view instanceof TextView) {
TextViewTransformState result = TextViewTransformState.obtain();
- result.initFrom(view);
+ result.initFrom(view, transformInfo);
return result;
}
if (view.getId() == com.android.internal.R.id.actions_container) {
ActionListTransformState result = ActionListTransformState.obtain();
- result.initFrom(view);
+ result.initFrom(view, transformInfo);
+ return result;
+ }
+ if (view.getId() == com.android.internal.R.id.notification_messaging) {
+ MessagingLayoutTransformState result = MessagingLayoutTransformState.obtain();
+ result.initFrom(view, transformInfo);
+ return result;
+ }
+ if (view instanceof MessagingImageMessage) {
+ MessagingImageTransformState result = MessagingImageTransformState.obtain();
+ result.initFrom(view, transformInfo);
return result;
}
if (view instanceof ImageView) {
ImageTransformState result = ImageTransformState.obtain();
- result.initFrom(view);
+ result.initFrom(view, transformInfo);
if (view.getId() == com.android.internal.R.id.reply_icon_action) {
((TransformState) result).setIsSameAsAnyView(true);
}
@@ -465,15 +465,15 @@ public class TransformState {
}
if (view instanceof ProgressBar) {
ProgressTransformState result = ProgressTransformState.obtain();
- result.initFrom(view);
+ result.initFrom(view, transformInfo);
return result;
}
TransformState result = obtain();
- result.initFrom(view);
+ result.initFrom(view, transformInfo);
return result;
}
- private void setIsSameAsAnyView(boolean sameAsAny) {
+ public void setIsSameAsAnyView(boolean sameAsAny) {
mSameAsAny = sameAsAny;
}
@@ -533,6 +533,7 @@ public class TransformState {
mSameAsAny = false;
mTransformationEndX = UNDEFINED;
mTransformationEndY = UNDEFINED;
+ mDefaultInterpolator = Interpolators.FAST_OUT_SLOW_IN;
}
public void setVisible(boolean visible, boolean force) {
@@ -578,4 +579,12 @@ public class TransformState {
public View getTransformedView() {
return mTransformedView;
}
+
+ public void setDefaultInterpolator(Interpolator interpolator) {
+ mDefaultInterpolator = interpolator;
+ }
+
+ public interface TransformInfo {
+ boolean isAnimating();
+ }
}
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 1bd90fa9ca08..b220686b3056 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -14,16 +14,13 @@
package com.android.systemui.statusbar.phone;
+import android.app.AlarmManager.AlarmClockInfo;
import android.content.Context;
import android.os.Handler;
-import android.os.Looper;
import android.provider.Settings.Secure;
-
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.app.NightDisplayController;
+import com.android.internal.app.ColorDisplayController;
import com.android.systemui.Dependency;
-import com.android.systemui.Prefs;
-import com.android.systemui.Prefs.Key;
import com.android.systemui.qs.AutoAddTracker;
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.qs.SecureSetting;
@@ -31,27 +28,37 @@ import com.android.systemui.statusbar.policy.DataSaverController;
import com.android.systemui.statusbar.policy.DataSaverController.Listener;
import com.android.systemui.statusbar.policy.HotspotController;
import com.android.systemui.statusbar.policy.HotspotController.Callback;
+import com.android.systemui.statusbar.policy.NextAlarmController;
+import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback;
/**
* Manages which tiles should be automatically added to QS.
*/
public class AutoTileManager {
-
public static final String HOTSPOT = "hotspot";
public static final String SAVER = "saver";
public static final String INVERSION = "inversion";
public static final String WORK = "work";
public static final String NIGHT = "night";
+ public static final String ALARM = "alarm";
+
private final Context mContext;
private final QSTileHost mHost;
private final Handler mHandler;
private final AutoAddTracker mAutoTracker;
public AutoTileManager(Context context, QSTileHost host) {
- mAutoTracker = new AutoAddTracker(context);
+ this(context, new AutoAddTracker(context), host,
+ new Handler(Dependency.get(Dependency.BG_LOOPER)));
+ }
+
+ @VisibleForTesting
+ AutoTileManager(Context context, AutoAddTracker autoAddTracker, QSTileHost host,
+ Handler handler) {
+ mAutoTracker = autoAddTracker;
mContext = context;
mHost = host;
- mHandler = new Handler((Looper) Dependency.get(Dependency.BG_LOOPER));
+ mHandler = handler;
if (!mAutoTracker.isAdded(HOTSPOT)) {
Dependency.get(HotspotController.class).addCallback(mHotspotCallback);
}
@@ -76,20 +83,25 @@ public class AutoTileManager {
if (!mAutoTracker.isAdded(WORK)) {
Dependency.get(ManagedProfileController.class).addCallback(mProfileCallback);
}
-
if (!mAutoTracker.isAdded(NIGHT)
- && NightDisplayController.isAvailable(mContext)) {
- Dependency.get(NightDisplayController.class).setListener(mNightDisplayCallback);
+ && ColorDisplayController.isAvailable(mContext)) {
+ Dependency.get(ColorDisplayController.class).setListener(mColorDisplayCallback);
+ }
+ if (!mAutoTracker.isAdded(ALARM)) {
+ Dependency.get(NextAlarmController.class).addCallback(mNextAlarmChangeCallback);
}
}
public void destroy() {
- mColorsSetting.setListening(false);
+ if (mColorsSetting != null) {
+ mColorsSetting.setListening(false);
+ }
mAutoTracker.destroy();
Dependency.get(HotspotController.class).removeCallback(mHotspotCallback);
Dependency.get(DataSaverController.class).removeCallback(mDataSaverListener);
Dependency.get(ManagedProfileController.class).removeCallback(mProfileCallback);
- Dependency.get(NightDisplayController.class).setListener(null);
+ Dependency.get(ColorDisplayController.class).setListener(null);
+ Dependency.get(NextAlarmController.class).removeCallback(mNextAlarmChangeCallback);
}
private final ManagedProfileController.Callback mProfileCallback =
@@ -127,7 +139,7 @@ public class AutoTileManager {
private final HotspotController.Callback mHotspotCallback = new Callback() {
@Override
- public void onHotspotChanged(boolean enabled) {
+ public void onHotspotChanged(boolean enabled, int numDevices) {
if (mAutoTracker.isAdded(HOTSPOT)) return;
if (enabled) {
mHost.addTile(HOTSPOT);
@@ -138,9 +150,22 @@ public class AutoTileManager {
}
};
+ private final NextAlarmChangeCallback mNextAlarmChangeCallback = new NextAlarmChangeCallback() {
+ @Override
+ public void onNextAlarmChanged(AlarmClockInfo nextAlarm) {
+ if (mAutoTracker.isAdded(ALARM)) return;
+ if (nextAlarm != null) {
+ mHost.addTile(ALARM);
+ mAutoTracker.setTileAdded(ALARM);
+ mHandler.post(() -> Dependency.get(NextAlarmController.class)
+ .removeCallback(mNextAlarmChangeCallback));
+ }
+ }
+ };
+
@VisibleForTesting
- final NightDisplayController.Callback mNightDisplayCallback =
- new NightDisplayController.Callback() {
+ final ColorDisplayController.Callback mColorDisplayCallback =
+ new ColorDisplayController.Callback() {
@Override
public void onActivated(boolean activated) {
if (activated) {
@@ -150,8 +175,8 @@ public class AutoTileManager {
@Override
public void onAutoModeChanged(int autoMode) {
- if (autoMode == NightDisplayController.AUTO_MODE_CUSTOM
- || autoMode == NightDisplayController.AUTO_MODE_TWILIGHT) {
+ if (autoMode == ColorDisplayController.AUTO_MODE_CUSTOM
+ || autoMode == ColorDisplayController.AUTO_MODE_TWILIGHT) {
addNightTile();
}
}
@@ -160,7 +185,7 @@ public class AutoTileManager {
if (mAutoTracker.isAdded(NIGHT)) return;
mHost.addTile(NIGHT);
mAutoTracker.setTileAdded(NIGHT);
- mHandler.post(() -> Dependency.get(NightDisplayController.class)
+ mHandler.post(() -> Dependency.get(ColorDisplayController.class)
.setListener(null));
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java
index a83e6591c48b..7284ee8b39bc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java
@@ -14,7 +14,6 @@
package com.android.systemui.statusbar.phone;
-import android.graphics.drawable.Drawable;
import android.view.View;
import com.android.systemui.plugins.statusbar.phone.NavBarButtonProvider.ButtonInterface;
@@ -35,10 +34,12 @@ public class ButtonDispatcher {
private View.OnClickListener mClickListener;
private View.OnTouchListener mTouchListener;
private View.OnLongClickListener mLongClickListener;
+ private View.OnHoverListener mOnHoverListener;
private Boolean mLongClickable;
private Integer mAlpha;
private Float mDarkIntensity;
private Integer mVisibility = -1;
+ private Boolean mDelayTouchFeedback;
private KeyButtonDrawable mImageDrawable;
private View mCurrentView;
private boolean mVertical;
@@ -56,6 +57,7 @@ public class ButtonDispatcher {
view.setOnClickListener(mClickListener);
view.setOnTouchListener(mTouchListener);
view.setOnLongClickListener(mLongClickListener);
+ view.setOnHoverListener(mOnHoverListener);
if (mLongClickable != null) {
view.setLongClickable(mLongClickable);
}
@@ -71,10 +73,10 @@ public class ButtonDispatcher {
if (mImageDrawable != null) {
((ButtonInterface) view).setImageDrawable(mImageDrawable);
}
-
- if (view instanceof ButtonInterface) {
- ((ButtonInterface) view).setVertical(mVertical);
+ if (mDelayTouchFeedback != null) {
+ ((ButtonInterface) view).setDelayTouchFeedback(mDelayTouchFeedback);
}
+ ((ButtonInterface) view).setVertical(mVertical);
}
public int getId() {
@@ -89,6 +91,10 @@ public class ButtonDispatcher {
return mAlpha != null ? mAlpha : 1;
}
+ public KeyButtonDrawable getImageDrawable() {
+ return mImageDrawable;
+ }
+
public void setImageDrawable(KeyButtonDrawable drawable) {
mImageDrawable = drawable;
final int N = mViews.size();
@@ -130,6 +136,14 @@ public class ButtonDispatcher {
}
}
+ public void setDelayTouchFeedback(boolean delay) {
+ mDelayTouchFeedback = delay;
+ final int N = mViews.size();
+ for (int i = 0; i < N; i++) {
+ ((ButtonInterface) mViews.get(i)).setDelayTouchFeedback(delay);
+ }
+ }
+
public void setOnClickListener(View.OnClickListener clickListener) {
mClickListener = clickListener;
final int N = mViews.size();
@@ -162,6 +176,22 @@ public class ButtonDispatcher {
}
}
+ public void setOnHoverListener(View.OnHoverListener hoverListener) {
+ mOnHoverListener = hoverListener;
+ final int N = mViews.size();
+ for (int i = 0; i < N; i++) {
+ mViews.get(i).setOnHoverListener(mOnHoverListener);
+ }
+ }
+
+ public void setClickable(boolean clickable) {
+ abortCurrentGesture();
+ final int N = mViews.size();
+ for (int i = 0; i < N; i++) {
+ mViews.get(i).setClickable(clickable);
+ }
+ }
+
public ArrayList<View> getViews() {
return mViews;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
index 2c3f452e8274..f7f791ebaf63 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -51,15 +51,19 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
public static final String TAG = "CollapsedStatusBarFragment";
private static final String EXTRA_PANEL_STATE = "panel_state";
+ public static final int FADE_IN_DURATION = 320;
+ public static final int FADE_IN_DELAY = 50;
private PhoneStatusBarView mStatusBar;
private KeyguardMonitor mKeyguardMonitor;
private NetworkController mNetworkController;
private LinearLayout mSystemIconArea;
+ private View mClockView;
private View mNotificationIconAreaInner;
private int mDisabled1;
private StatusBar mStatusBarComponent;
private DarkIconManager mDarkIconManager;
private SignalClusterView mSignalClusterView;
+ private View mOperatorNameFrame;
private SignalCallback mSignalCallback = new SignalCallback() {
@Override
@@ -92,11 +96,13 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
mDarkIconManager = new DarkIconManager(view.findViewById(R.id.statusIcons));
Dependency.get(StatusBarIconController.class).addIconGroup(mDarkIconManager);
mSystemIconArea = mStatusBar.findViewById(R.id.system_icon_area);
+ mClockView = mStatusBar.findViewById(R.id.clock);
mSignalClusterView = mStatusBar.findViewById(R.id.signal_cluster);
Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mSignalClusterView);
// Default to showing until we know otherwise.
showSystemIconArea(false);
initEmergencyCryptkeeperText();
+ initOperatorName();
}
@Override
@@ -150,8 +156,10 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
if ((diff1 & DISABLE_SYSTEM_INFO) != 0) {
if ((state1 & DISABLE_SYSTEM_INFO) != 0) {
hideSystemIconArea(animate);
+ hideOperatorName(animate);
} else {
showSystemIconArea(animate);
+ showOperatorName(animate);
}
}
if ((diff1 & DISABLE_NOTIFICATION_ICONS) != 0) {
@@ -193,10 +201,12 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
public void hideSystemIconArea(boolean animate) {
animateHide(mSystemIconArea, animate);
+ animateHide(mClockView, animate);
}
public void showSystemIconArea(boolean animate) {
animateShow(mSystemIconArea, animate);
+ animateShow(mClockView, animate);
}
public void hideNotificationIconArea(boolean animate) {
@@ -207,6 +217,18 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
animateShow(mNotificationIconAreaInner, animate);
}
+ public void hideOperatorName(boolean animate) {
+ if (mOperatorNameFrame != null) {
+ animateHide(mOperatorNameFrame, animate);
+ }
+ }
+
+ public void showOperatorName(boolean animate) {
+ if (mOperatorNameFrame != null) {
+ animateShow(mOperatorNameFrame, animate);
+ }
+ }
+
/**
* Hides a view.
*/
@@ -237,9 +259,9 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
}
v.animate()
.alpha(1f)
- .setDuration(320)
+ .setDuration(FADE_IN_DURATION)
.setInterpolator(Interpolators.ALPHA_IN)
- .setStartDelay(50)
+ .setStartDelay(FADE_IN_DELAY)
// We need to clean up any pending end action from animateHide if we call
// both hide and show in the same frame before the animation actually gets started.
@@ -268,4 +290,11 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
parent.removeView(emergencyViewStub);
}
}
+
+ private void initOperatorName() {
+ if (getResources().getBoolean(R.bool.config_showOperatorNameInStatusBar)) {
+ ViewStub stub = mStatusBar.findViewById(R.id.operator_name);
+ mOperatorNameFrame = stub.inflate();
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DoubleTapHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DoubleTapHelper.java
index dcb6a3801844..0d62703cbced 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DoubleTapHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DoubleTapHelper.java
@@ -142,7 +142,7 @@ public class DoubleTapHelper {
&& Math.abs(event.getY() - mDownY) < mTouchSlop;
}
- private boolean isWithinDoubleTapSlop(MotionEvent event) {
+ public boolean isWithinDoubleTapSlop(MotionEvent event) {
if (!mActivated) {
// If we're not activated there's no double tap slop to satisfy.
return true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 6b7397b3f8ca..fb3adf45df31 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -25,11 +25,14 @@ import android.util.MathUtils;
import android.util.SparseBooleanArray;
import com.android.internal.hardware.AmbientDisplayConfiguration;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
+import com.android.systemui.doze.AlwaysOnDisplayPolicy;
+import com.android.systemui.tuner.TunerService;
import java.io.PrintWriter;
-public class DozeParameters {
+public class DozeParameters implements TunerService.Tunable {
private static final int MAX_DURATION = 60 * 1000;
public static final String DOZE_SENSORS_WAKE_UP_FULLY = "doze_sensors_wake_up_fully";
@@ -37,19 +40,24 @@ public class DozeParameters {
private final AmbientDisplayConfiguration mAmbientDisplayConfiguration;
private static IntInOutMatcher sPickupSubtypePerformsProxMatcher;
+ private final AlwaysOnDisplayPolicy mAlwaysOnPolicy;
+
+ private boolean mDozeAlwaysOn;
public DozeParameters(Context context) {
mContext = context;
mAmbientDisplayConfiguration = new AmbientDisplayConfiguration(mContext);
+ mAlwaysOnPolicy = new AlwaysOnDisplayPolicy(context);
+
+ Dependency.get(TunerService.class).addTunable(this, Settings.Secure.DOZE_ALWAYS_ON,
+ Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED);
}
public void dump(PrintWriter pw) {
pw.println(" DozeParameters:");
pw.print(" getDisplayStateSupported(): "); pw.println(getDisplayStateSupported());
- pw.print(" getPulseDuration(pickup=false): "); pw.println(getPulseDuration(false));
- pw.print(" getPulseDuration(pickup=true): "); pw.println(getPulseDuration(true));
- pw.print(" getPulseInDuration(pickup=false): "); pw.println(getPulseInDuration(false));
- pw.print(" getPulseInDuration(pickup=true): "); pw.println(getPulseInDuration(true));
+ pw.print(" getPulseDuration(): "); pw.println(getPulseDuration());
+ pw.print(" getPulseInDuration(): "); pw.println(getPulseInDuration());
pw.print(" getPulseInVisibleDuration(): "); pw.println(getPulseVisibleDuration());
pw.print(" getPulseOutDuration(): "); pw.println(getPulseOutDuration());
pw.print(" getPulseOnSigMotion(): "); pw.println(getPulseOnSigMotion());
@@ -81,14 +89,17 @@ public class DozeParameters {
return mContext.getResources().getBoolean(R.bool.doze_suspend_display_state_supported);
}
- public int getPulseDuration(boolean pickup) {
- return getPulseInDuration(pickup) + getPulseVisibleDuration() + getPulseOutDuration();
+ public int getPulseDuration() {
+ return getPulseInDuration() + getPulseVisibleDuration() + getPulseOutDuration();
+ }
+
+ public float getScreenBrightnessDoze() {
+ return mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_screenBrightnessDoze) / 255f;
}
- public int getPulseInDuration(boolean pickupOrDoubleTap) {
- return pickupOrDoubleTap
- ? getInt("doze.pulse.duration.in.pickup", R.integer.doze_pulse_duration_in_pickup)
- : getInt("doze.pulse.duration.in", R.integer.doze_pulse_duration_in);
+ public int getPulseInDuration() {
+ return getInt("doze.pulse.duration.in", R.integer.doze_pulse_duration_in);
}
public int getPulseVisibleDuration() {
@@ -119,8 +130,50 @@ public class DozeParameters {
return getInt("doze.pickup.vibration.threshold", R.integer.doze_pickup_vibration_threshold);
}
+ /**
+ * For how long a wallpaper can be visible in AoD before it fades aways.
+ * @return duration in millis.
+ */
+ public long getWallpaperAodDuration() {
+ return mAlwaysOnPolicy.wallpaperVisibilityDuration;
+ }
+
+ /**
+ * How long it takes for the wallpaper fade away (Animation duration.)
+ * @return duration in millis.
+ */
+ public long getWallpaperFadeOutDuration() {
+ return mAlwaysOnPolicy.wallpaperFadeOutDuration;
+ }
+
+ /**
+ * Checks if always on is available and enabled for the current user.
+ * @return {@code true} if enabled and available.
+ */
public boolean getAlwaysOn() {
- return mAmbientDisplayConfiguration.alwaysOnEnabled(UserHandle.USER_CURRENT);
+ return mDozeAlwaysOn;
+ }
+
+ /**
+ * Some screens need to be completely black before changing the display power mode,
+ * unexpected behavior might happen if this parameter isn't respected.
+ *
+ * @return {@code true} if screen needs to be completely black before a power transition.
+ */
+ public boolean getDisplayNeedsBlanking() {
+ return mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_displayBlanksAfterDoze);
+ }
+
+ /**
+ * Whether we can implement our own screen off animation or if we need
+ * to rely on DisplayPowerManager to dim the display.
+ *
+ * @return {@code true} if SystemUI can control the screen off animation.
+ */
+ public boolean getCanControlScreenOffAnimation() {
+ return !mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_dozeAfterScreenOff);
}
private boolean getBoolean(String propName, int resId) {
@@ -161,6 +214,10 @@ public class DozeParameters {
return mContext.getResources().getBoolean(R.bool.doze_double_tap_reports_touch_coordinates);
}
+ @Override
+ public void onTuningChanged(String key, String newValue) {
+ mDozeAlwaysOn = mAmbientDisplayConfiguration.alwaysOnEnabled(UserHandle.USER_CURRENT);
+ }
/**
* Parses a spec of the form `1,2,3,!5,*`. The resulting object will match numbers that are
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
index 8afb8490808e..1011383b72e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
@@ -16,16 +16,11 @@
package com.android.systemui.statusbar.phone;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.content.Context;
import android.os.Handler;
import android.util.Log;
-import android.view.animation.Interpolator;
-import com.android.systemui.Interpolators;
import com.android.systemui.doze.DozeHost;
import com.android.systemui.doze.DozeLog;
@@ -40,74 +35,59 @@ public class DozeScrimController {
private final Handler mHandler = new Handler();
private final ScrimController mScrimController;
- private final Context mContext;
-
private boolean mDozing;
private DozeHost.PulseCallback mPulseCallback;
private int mPulseReason;
- private Animator mInFrontAnimator;
- private Animator mBehindAnimator;
- private float mInFrontTarget;
- private float mBehindTarget;
- private boolean mDozingAborted;
- private boolean mWakeAndUnlocking;
private boolean mFullyPulsing;
- private float mAodFrontScrimOpacity = 0;
- private Runnable mSetDozeInFrontAlphaDelayed;
+ private final ScrimController.Callback mScrimCallback = new ScrimController.Callback() {
+ @Override
+ public void onDisplayBlanked() {
+ if (DEBUG) {
+ Log.d(TAG, "Pulse in, mDozing=" + mDozing + " mPulseReason="
+ + DozeLog.pulseReasonToString(mPulseReason));
+ }
+ if (!mDozing) {
+ return;
+ }
+
+ // Signal that the pulse is ready to turn the screen on and draw.
+ pulseStarted();
+ }
+
+ @Override
+ public void onFinished() {
+ if (DEBUG) {
+ Log.d(TAG, "Pulse in finished, mDozing=" + mDozing);
+ }
+ if (!mDozing) {
+ return;
+ }
+ mHandler.postDelayed(mPulseOut, mDozeParameters.getPulseVisibleDuration());
+ mHandler.postDelayed(mPulseOutExtended,
+ mDozeParameters.getPulseVisibleDurationExtended());
+ mFullyPulsing = true;
+ }
+
+ /**
+ * Transition was aborted before it was over.
+ */
+ @Override
+ public void onCancelled() {
+ pulseFinished();
+ }
+ };
public DozeScrimController(ScrimController scrimController, Context context) {
- mContext = context;
mScrimController = scrimController;
mDozeParameters = new DozeParameters(context);
}
- public void setDozing(boolean dozing, boolean animate) {
+ public void setDozing(boolean dozing) {
if (mDozing == dozing) return;
mDozing = dozing;
- mWakeAndUnlocking = false;
- if (mDozing) {
- mDozingAborted = false;
- abortAnimations();
- mScrimController.setDozeBehindAlpha(1f);
- setDozeInFrontAlpha(mDozeParameters.getAlwaysOn() ? mAodFrontScrimOpacity : 1f);
- } else {
+ if (!mDozing) {
cancelPulsing();
- if (animate) {
- startScrimAnimation(false /* inFront */, 0f /* target */,
- NotificationPanelView.DOZE_ANIMATION_DURATION,
- Interpolators.LINEAR_OUT_SLOW_IN);
- startScrimAnimation(true /* inFront */, 0f /* target */,
- NotificationPanelView.DOZE_ANIMATION_DURATION,
- Interpolators.LINEAR_OUT_SLOW_IN);
- } else {
- abortAnimations();
- mScrimController.setDozeBehindAlpha(0f);
- setDozeInFrontAlpha(0f);
- }
- }
- }
-
- /**
- * Set the opacity of the front scrim when showing AOD1
- *
- * Used to emulate lower brightness values than the hardware supports natively.
- */
- public void setAodDimmingScrim(float scrimOpacity) {
- mAodFrontScrimOpacity = scrimOpacity;
- if (mDozing && !isPulsing() && !mDozingAborted && !mWakeAndUnlocking
- && mDozeParameters.getAlwaysOn()) {
- setDozeInFrontAlpha(mAodFrontScrimOpacity);
- }
- }
-
- public void setWakeAndUnlocking() {
- // Immediately abort the doze scrims in case of wake-and-unlock
- // for pulsing so the Keyguard fade-out animation scrim can take over.
- if (!mWakeAndUnlocking) {
- mWakeAndUnlocking = true;
- mScrimController.setDozeBehindAlpha(0f);
- setDozeInFrontAlpha(0f);
}
}
@@ -118,37 +98,21 @@ public class DozeScrimController {
}
if (!mDozing || mPulseCallback != null) {
+ if (DEBUG) {
+ Log.d(TAG, "Pulse supressed. Dozing: " + mDozeParameters + " had callback? "
+ + (mPulseCallback != null));
+ }
// Pulse suppressed.
callback.onPulseFinished();
return;
}
- // Begin pulse. Note that it's very important that the pulse finished callback
+ // Begin pulse. Note that it's very important that the pulse finished callback
// be invoked when we're done so that the caller can drop the pulse wakelock.
mPulseCallback = callback;
mPulseReason = reason;
- setDozeInFrontAlpha(1f);
- mHandler.post(mPulseIn);
- }
-
- /**
- * Aborts pulsing immediately.
- */
- public void abortPulsing() {
- cancelPulsing();
- if (mDozing && !mWakeAndUnlocking) {
- mScrimController.setDozeBehindAlpha(1f);
- setDozeInFrontAlpha(mDozeParameters.getAlwaysOn() && !mDozingAborted
- ? mAodFrontScrimOpacity : 1f);
- }
- }
- /**
- * Aborts dozing immediately.
- */
- public void abortDoze() {
- mDozingAborted = true;
- abortPulsing();
+ mScrimController.transitionTo(ScrimState.PULSING, mScrimCallback);
}
public void pulseOutNow() {
@@ -157,17 +121,6 @@ public class DozeScrimController {
}
}
- public void onScreenTurnedOn() {
- if (isPulsing()) {
- final boolean pickupOrDoubleTap = mPulseReason == DozeLog.PULSE_REASON_SENSOR_PICKUP
- || mPulseReason == DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP;
- startScrimAnimation(true /* inFront */, 0f,
- mDozeParameters.getPulseInDuration(pickupOrDoubleTap),
- pickupOrDoubleTap ? Interpolators.LINEAR_OUT_SLOW_IN : Interpolators.ALPHA_OUT,
- mPulseInFinished);
- }
- }
-
public boolean isPulsing() {
return mPulseCallback != null;
}
@@ -181,11 +134,9 @@ public class DozeScrimController {
}
private void cancelPulsing() {
- if (DEBUG) Log.d(TAG, "Cancel pulsing");
-
if (mPulseCallback != null) {
+ if (DEBUG) Log.d(TAG, "Cancel pulsing");
mFullyPulsing = false;
- mHandler.removeCallbacks(mPulseIn);
mHandler.removeCallbacks(mPulseOut);
mHandler.removeCallbacks(mPulseOutExtended);
pulseFinished();
@@ -193,151 +144,20 @@ public class DozeScrimController {
}
private void pulseStarted() {
+ DozeLog.tracePulseStart(mPulseReason);
if (mPulseCallback != null) {
mPulseCallback.onPulseStarted();
}
}
private void pulseFinished() {
+ DozeLog.tracePulseFinish();
if (mPulseCallback != null) {
mPulseCallback.onPulseFinished();
mPulseCallback = null;
}
}
- private void abortAnimations() {
- if (mInFrontAnimator != null) {
- mInFrontAnimator.cancel();
- }
- if (mBehindAnimator != null) {
- mBehindAnimator.cancel();
- }
- }
-
- private void startScrimAnimation(final boolean inFront, float target, long duration,
- Interpolator interpolator) {
- startScrimAnimation(inFront, target, duration, interpolator, null /* endRunnable */);
- }
-
- private void startScrimAnimation(final boolean inFront, float target, long duration,
- Interpolator interpolator, final Runnable endRunnable) {
- Animator current = getCurrentAnimator(inFront);
- if (current != null) {
- float currentTarget = getCurrentTarget(inFront);
- if (currentTarget == target) {
- return;
- }
- current.cancel();
- }
- ValueAnimator anim = ValueAnimator.ofFloat(getDozeAlpha(inFront), target);
- anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- float value = (float) animation.getAnimatedValue();
- setDozeAlpha(inFront, value);
- }
- });
- anim.setInterpolator(interpolator);
- anim.setDuration(duration);
- anim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- setCurrentAnimator(inFront, null);
- if (endRunnable != null) {
- endRunnable.run();
- }
- }
- });
- anim.start();
- setCurrentAnimator(inFront, anim);
- setCurrentTarget(inFront, target);
- }
-
- private float getCurrentTarget(boolean inFront) {
- return inFront ? mInFrontTarget : mBehindTarget;
- }
-
- private void setCurrentTarget(boolean inFront, float target) {
- if (inFront) {
- mInFrontTarget = target;
- } else {
- mBehindTarget = target;
- }
- }
-
- private Animator getCurrentAnimator(boolean inFront) {
- return inFront ? mInFrontAnimator : mBehindAnimator;
- }
-
- private void setCurrentAnimator(boolean inFront, Animator animator) {
- if (inFront) {
- mInFrontAnimator = animator;
- } else {
- mBehindAnimator = animator;
- }
- }
-
- private void setDozeAlpha(boolean inFront, float alpha) {
- if (mWakeAndUnlocking) {
- return;
- }
- if (inFront) {
- mScrimController.setDozeInFrontAlpha(alpha);
- } else {
- mScrimController.setDozeBehindAlpha(alpha);
- }
- }
-
- private float getDozeAlpha(boolean inFront) {
- return inFront
- ? mScrimController.getDozeInFrontAlpha()
- : mScrimController.getDozeBehindAlpha();
- }
-
- private void setDozeInFrontAlpha(float opacity) {
- setDozeInFrontAlphaDelayed(opacity, 0 /* delay */);
-
- }
-
- private void setDozeInFrontAlphaDelayed(float opacity, long delayMs) {
- if (mSetDozeInFrontAlphaDelayed != null) {
- mHandler.removeCallbacks(mSetDozeInFrontAlphaDelayed);
- mSetDozeInFrontAlphaDelayed = null;
- }
- if (delayMs <= 0) {
- mScrimController.setDozeInFrontAlpha(opacity);
- } else {
- mHandler.postDelayed(mSetDozeInFrontAlphaDelayed = () -> {
- setDozeInFrontAlpha(opacity);
- }, delayMs);
- }
- }
-
- private final Runnable mPulseIn = new Runnable() {
- @Override
- public void run() {
- if (DEBUG) Log.d(TAG, "Pulse in, mDozing=" + mDozing + " mPulseReason="
- + DozeLog.pulseReasonToString(mPulseReason));
- if (!mDozing) return;
- DozeLog.tracePulseStart(mPulseReason);
-
- // Signal that the pulse is ready to turn the screen on and draw.
- pulseStarted();
- }
- };
-
- private final Runnable mPulseInFinished = new Runnable() {
- @Override
- public void run() {
- if (DEBUG) Log.d(TAG, "Pulse in finished, mDozing=" + mDozing);
- if (!mDozing) return;
- mHandler.postDelayed(mPulseOut, mDozeParameters.getPulseVisibleDuration());
- mHandler.postDelayed(mPulseOutExtended,
- mDozeParameters.getPulseVisibleDurationExtended());
- mFullyPulsing = true;
- }
- };
-
private final Runnable mPulseOutExtended = new Runnable() {
@Override
public void run() {
@@ -354,38 +174,13 @@ public class DozeScrimController {
mHandler.removeCallbacks(mPulseOutExtended);
if (DEBUG) Log.d(TAG, "Pulse out, mDozing=" + mDozing);
if (!mDozing) return;
- startScrimAnimation(true /* inFront */, 1,
- mDozeParameters.getPulseOutDuration(),
- Interpolators.ALPHA_IN, mPulseOutFinishing);
- }
- };
-
- private final Runnable mPulseOutFinishing = new Runnable() {
- @Override
- public void run() {
- if (DEBUG) Log.d(TAG, "Pulse out finished");
- DozeLog.tracePulseFinish();
- if (mDozeParameters.getAlwaysOn() && mDozing) {
- // Setting power states can block rendering. For AOD, delay finishing the pulse and
- // setting the power state until the fully black scrim had time to hit the
- // framebuffer.
- mHandler.postDelayed(mPulseOutFinished, 30);
- } else {
- mPulseOutFinished.run();
- }
- }
- };
-
- private final Runnable mPulseOutFinished = new Runnable() {
- @Override
- public void run() {
- // Signal that the pulse is all finished so we can turn the screen off now.
- DozeScrimController.this.pulseFinished();
- if (mDozeParameters.getAlwaysOn()) {
- // Setting power states can happen after we push out the frame. Make sure we
- // stay fully opaque until the power state request reaches the lower levels.
- setDozeInFrontAlphaDelayed(mAodFrontScrimOpacity, 100);
- }
+ mScrimController.transitionTo(ScrimState.AOD,
+ new ScrimController.Callback() {
+ @Override
+ public void onDisplayBlanked() {
+ pulseFinished();
+ }
+ });
}
};
-}
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
index 91369dbd5f89..a2b1013167de 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
@@ -26,7 +26,7 @@ import android.util.Log;
import com.android.keyguard.KeyguardConstants;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.keyguard.LatencyTracker;
+import com.android.internal.util.LatencyTracker;
import com.android.systemui.Dependency;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.ScreenLifecycle;
@@ -181,9 +181,9 @@ public class FingerprintUnlockController extends KeyguardUpdateMonitorCallback {
}
private boolean pulsingOrAod() {
- boolean pulsing = mDozeScrimController.isPulsing();
- boolean dozingWithScreenOn = mStatusBar.isDozing() && !mStatusBar.isScreenFullyOff();
- return pulsing || dozingWithScreenOn;
+ final ScrimState scrimState = mScrimController.getState();
+ return scrimState == ScrimState.AOD
+ || scrimState == ScrimState.PULSING;
}
@Override
@@ -246,15 +246,12 @@ public class FingerprintUnlockController extends KeyguardUpdateMonitorCallback {
true /* allowEnterAnimation */);
} else if (mMode == MODE_WAKE_AND_UNLOCK){
Trace.beginSection("MODE_WAKE_AND_UNLOCK");
- mDozeScrimController.abortDoze();
} else {
Trace.beginSection("MODE_WAKE_AND_UNLOCK_FROM_DREAM");
mUpdateMonitor.awakenFromDream();
}
mStatusBarWindowManager.setStatusBarFocusable(false);
mKeyguardViewMediator.onWakeAndUnlocking();
- mScrimController.setWakeAndUnlocking();
- mDozeScrimController.setWakeAndUnlocking();
if (mStatusBar.getNavigationBarView() != null) {
mStatusBar.getNavigationBarView().setWakeAndUnlocking(true);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
new file mode 100644
index 000000000000..aba5cdf0ca2b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -0,0 +1,446 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Resources;
+import android.support.v4.util.ArraySet;
+import android.util.Log;
+import android.util.Pools;
+import android.view.View;
+import android.view.ViewTreeObserver;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Dumpable;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.NotificationData;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.HashSet;
+import java.util.Stack;
+
+/**
+ * A implementation of HeadsUpManager for phone and car.
+ */
+public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable,
+ ViewTreeObserver.OnComputeInternalInsetsListener, VisualStabilityManager.Callback,
+ OnHeadsUpChangedListener {
+ private static final String TAG = "HeadsUpManagerPhone";
+ private static final boolean DEBUG = false;
+
+ private final View mStatusBarWindowView;
+ private final int mStatusBarHeight;
+ private final NotificationGroupManager mGroupManager;
+ private final StatusBar mBar;
+ private final VisualStabilityManager mVisualStabilityManager;
+
+ private boolean mReleaseOnExpandFinish;
+ private boolean mTrackingHeadsUp;
+ private HashSet<String> mSwipedOutKeys = new HashSet<>();
+ private HashSet<NotificationData.Entry> mEntriesToRemoveAfterExpand = new HashSet<>();
+ private ArraySet<NotificationData.Entry> mEntriesToRemoveWhenReorderingAllowed
+ = new ArraySet<>();
+ private boolean mIsExpanded;
+ private int[] mTmpTwoArray = new int[2];
+ private boolean mHeadsUpGoingAway;
+ private boolean mWaitingOnCollapseWhenGoingAway;
+ private boolean mIsObserving;
+ private int mStatusBarState;
+
+ private final Pools.Pool<HeadsUpEntryPhone> mEntryPool = new Pools.Pool<HeadsUpEntryPhone>() {
+ private Stack<HeadsUpEntryPhone> mPoolObjects = new Stack<>();
+
+ @Override
+ public HeadsUpEntryPhone acquire() {
+ if (!mPoolObjects.isEmpty()) {
+ return mPoolObjects.pop();
+ }
+ return new HeadsUpEntryPhone();
+ }
+
+ @Override
+ public boolean release(@NonNull HeadsUpEntryPhone instance) {
+ mPoolObjects.push(instance);
+ return true;
+ }
+ };
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // Constructor:
+
+ public HeadsUpManagerPhone(@NonNull final Context context, @NonNull View statusBarWindowView,
+ @NonNull NotificationGroupManager groupManager, @NonNull StatusBar bar,
+ @NonNull VisualStabilityManager visualStabilityManager) {
+ super(context);
+
+ mStatusBarWindowView = statusBarWindowView;
+ mGroupManager = groupManager;
+ mBar = bar;
+ mVisualStabilityManager = visualStabilityManager;
+
+ Resources resources = mContext.getResources();
+ mStatusBarHeight = resources.getDimensionPixelSize(
+ com.android.internal.R.dimen.status_bar_height);
+
+ addListener(new OnHeadsUpChangedListener() {
+ @Override
+ public void onHeadsUpPinnedModeChanged(boolean hasPinnedNotification) {
+ if (DEBUG) Log.w(TAG, "onHeadsUpPinnedModeChanged");
+ updateTouchableRegionListener();
+ }
+ });
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // Public methods:
+
+ /**
+ * Decides whether a click is invalid for a notification, i.e it has not been shown long enough
+ * that a user might have consciously clicked on it.
+ *
+ * @param key the key of the touched notification
+ * @return whether the touch is invalid and should be discarded
+ */
+ public boolean shouldSwallowClick(@NonNull String key) {
+ HeadsUpManager.HeadsUpEntry entry = getHeadsUpEntry(key);
+ return entry != null && mClock.currentTimeMillis() < entry.postTime;
+ }
+
+ public void onExpandingFinished() {
+ if (mReleaseOnExpandFinish) {
+ releaseAllImmediately();
+ mReleaseOnExpandFinish = false;
+ } else {
+ for (NotificationData.Entry entry : mEntriesToRemoveAfterExpand) {
+ if (isHeadsUp(entry.key)) {
+ // Maybe the heads-up was removed already
+ removeHeadsUpEntry(entry);
+ }
+ }
+ }
+ mEntriesToRemoveAfterExpand.clear();
+ }
+
+ /**
+ * Sets the tracking-heads-up flag. If the flag is true, HeadsUpManager doesn't remove the entry
+ * from the list even after a Heads Up Notification is gone.
+ */
+ public void setTrackingHeadsUp(boolean trackingHeadsUp) {
+ mTrackingHeadsUp = trackingHeadsUp;
+ }
+
+ /**
+ * Notify that the status bar panel gets expanded or collapsed.
+ *
+ * @param isExpanded True to notify expanded, false to notify collapsed.
+ */
+ public void setIsPanelExpanded(boolean isExpanded) {
+ if (isExpanded != mIsExpanded) {
+ mIsExpanded = isExpanded;
+ if (isExpanded) {
+ // make sure our state is sane
+ mWaitingOnCollapseWhenGoingAway = false;
+ mHeadsUpGoingAway = false;
+ updateTouchableRegionListener();
+ }
+ }
+ }
+
+ /**
+ * Set the current state of the statusbar.
+ */
+ public void setStatusBarState(int statusBarState) {
+ mStatusBarState = statusBarState;
+ }
+
+ /**
+ * Set that we are exiting the headsUp pinned mode, but some notifications might still be
+ * animating out. This is used to keep the touchable regions in a sane state.
+ */
+ public void setHeadsUpGoingAway(boolean headsUpGoingAway) {
+ if (headsUpGoingAway != mHeadsUpGoingAway) {
+ mHeadsUpGoingAway = headsUpGoingAway;
+ if (!headsUpGoingAway) {
+ waitForStatusBarLayout();
+ }
+ updateTouchableRegionListener();
+ }
+ }
+
+ /**
+ * Notifies that a remote input textbox in notification gets active or inactive.
+ * @param entry The entry of the target notification.
+ * @param remoteInputActive True to notify active, False to notify inactive.
+ */
+ public void setRemoteInputActive(
+ @NonNull NotificationData.Entry entry, boolean remoteInputActive) {
+ HeadsUpEntryPhone headsUpEntry = getHeadsUpEntryPhone(entry.key);
+ if (headsUpEntry != null && headsUpEntry.remoteInputActive != remoteInputActive) {
+ headsUpEntry.remoteInputActive = remoteInputActive;
+ if (remoteInputActive) {
+ headsUpEntry.removeAutoRemovalCallbacks();
+ } else {
+ headsUpEntry.updateEntry(false /* updatePostTime */);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ public void removeMinimumDisplayTimeForTesting() {
+ mMinimumDisplayTime = 0;
+ mHeadsUpNotificationDecay = 0;
+ mTouchAcceptanceDelay = 0;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // HeadsUpManager public methods overrides:
+
+ @Override
+ public boolean isTrackingHeadsUp() {
+ return mTrackingHeadsUp;
+ }
+
+ @Override
+ public void snooze() {
+ super.snooze();
+ mReleaseOnExpandFinish = true;
+ }
+
+ /**
+ * React to the removal of the notification in the heads up.
+ *
+ * @return true if the notification was removed and false if it still needs to be kept around
+ * for a bit since it wasn't shown long enough
+ */
+ @Override
+ public boolean removeNotification(@NonNull String key, boolean ignoreEarliestRemovalTime) {
+ if (wasShownLongEnough(key) || ignoreEarliestRemovalTime) {
+ return super.removeNotification(key, ignoreEarliestRemovalTime);
+ } else {
+ HeadsUpEntryPhone entry = getHeadsUpEntryPhone(key);
+ entry.removeAsSoonAsPossible();
+ return false;
+ }
+ }
+
+ public void addSwipedOutNotification(@NonNull String key) {
+ mSwipedOutKeys.add(key);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // Dumpable overrides:
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("HeadsUpManagerPhone state:");
+ dumpInternal(fd, pw, args);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // ViewTreeObserver.OnComputeInternalInsetsListener overrides:
+
+ /**
+ * Overridden from TreeObserver.
+ */
+ @Override
+ public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) {
+ if (mIsExpanded || mBar.isBouncerShowing()) {
+ // The touchable region is always the full area when expanded
+ return;
+ }
+ if (hasPinnedHeadsUp()) {
+ ExpandableNotificationRow topEntry = getTopEntry().row;
+ if (topEntry.isChildInGroup()) {
+ final ExpandableNotificationRow groupSummary
+ = mGroupManager.getGroupSummary(topEntry.getStatusBarNotification());
+ if (groupSummary != null) {
+ topEntry = groupSummary;
+ }
+ }
+ topEntry.getLocationOnScreen(mTmpTwoArray);
+ int minX = mTmpTwoArray[0];
+ int maxX = mTmpTwoArray[0] + topEntry.getWidth();
+ int maxY = topEntry.getIntrinsicHeight();
+
+ info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+ info.touchableRegion.set(minX, 0, maxX, maxY);
+ } else if (mHeadsUpGoingAway || mWaitingOnCollapseWhenGoingAway) {
+ info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+ info.touchableRegion.set(0, 0, mStatusBarWindowView.getWidth(), mStatusBarHeight);
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // VisualStabilityManager.Callback overrides:
+
+ @Override
+ public void onReorderingAllowed() {
+ mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(false);
+ for (NotificationData.Entry entry : mEntriesToRemoveWhenReorderingAllowed) {
+ if (isHeadsUp(entry.key)) {
+ // Maybe the heads-up was removed already
+ removeHeadsUpEntry(entry);
+ }
+ }
+ mEntriesToRemoveWhenReorderingAllowed.clear();
+ mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(true);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // HeadsUpManager utility (protected) methods overrides:
+
+ @Override
+ protected HeadsUpEntry createHeadsUpEntry() {
+ return mEntryPool.acquire();
+ }
+
+ @Override
+ protected void releaseHeadsUpEntry(HeadsUpEntry entry) {
+ entry.reset();
+ mEntryPool.release((HeadsUpEntryPhone) entry);
+ }
+
+ @Override
+ protected boolean shouldHeadsUpBecomePinned(NotificationData.Entry entry) {
+ return mStatusBarState != StatusBarState.KEYGUARD && !mIsExpanded
+ || super.shouldHeadsUpBecomePinned(entry);
+ }
+
+ @Override
+ protected void dumpInternal(FileDescriptor fd, PrintWriter pw, String[] args) {
+ super.dumpInternal(fd, pw, args);
+ pw.print(" mStatusBarState="); pw.println(mStatusBarState);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // Private utility methods:
+
+ @Nullable
+ private HeadsUpEntryPhone getHeadsUpEntryPhone(@NonNull String key) {
+ return (HeadsUpEntryPhone) getHeadsUpEntry(key);
+ }
+
+ @Nullable
+ private HeadsUpEntryPhone getTopHeadsUpEntryPhone() {
+ return (HeadsUpEntryPhone) getTopHeadsUpEntry();
+ }
+
+ private boolean wasShownLongEnough(@NonNull String key) {
+ if (mSwipedOutKeys.contains(key)) {
+ // We always instantly dismiss views being manually swiped out.
+ mSwipedOutKeys.remove(key);
+ return true;
+ }
+
+ HeadsUpEntryPhone headsUpEntry = getHeadsUpEntryPhone(key);
+ HeadsUpEntryPhone topEntry = getTopHeadsUpEntryPhone();
+ return headsUpEntry != topEntry || headsUpEntry.wasShownLongEnough();
+ }
+
+ /**
+ * We need to wait on the whole panel to collapse, before we can remove the touchable region
+ * listener.
+ */
+ private void waitForStatusBarLayout() {
+ mWaitingOnCollapseWhenGoingAway = true;
+ mStatusBarWindowView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft,
+ int oldTop, int oldRight, int oldBottom) {
+ if (mStatusBarWindowView.getHeight() <= mStatusBarHeight) {
+ mStatusBarWindowView.removeOnLayoutChangeListener(this);
+ mWaitingOnCollapseWhenGoingAway = false;
+ updateTouchableRegionListener();
+ }
+ }
+ });
+ }
+
+ private void updateTouchableRegionListener() {
+ boolean shouldObserve = hasPinnedHeadsUp() || mHeadsUpGoingAway
+ || mWaitingOnCollapseWhenGoingAway;
+ if (shouldObserve == mIsObserving) {
+ return;
+ }
+ if (shouldObserve) {
+ mStatusBarWindowView.getViewTreeObserver().addOnComputeInternalInsetsListener(this);
+ mStatusBarWindowView.requestLayout();
+ } else {
+ mStatusBarWindowView.getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
+ }
+ mIsObserving = shouldObserve;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // HeadsUpEntryPhone:
+
+ protected class HeadsUpEntryPhone extends HeadsUpManager.HeadsUpEntry {
+ public void setEntry(@NonNull final NotificationData.Entry entry) {
+ Runnable removeHeadsUpRunnable = () -> {
+ if (!mVisualStabilityManager.isReorderingAllowed()) {
+ mEntriesToRemoveWhenReorderingAllowed.add(entry);
+ mVisualStabilityManager.addReorderingAllowedCallback(
+ HeadsUpManagerPhone.this);
+ } else if (!mTrackingHeadsUp) {
+ removeHeadsUpEntry(entry);
+ } else {
+ mEntriesToRemoveAfterExpand.add(entry);
+ }
+ };
+
+ super.setEntry(entry, removeHeadsUpRunnable);
+ }
+
+ public boolean wasShownLongEnough() {
+ return earliestRemovaltime < mClock.currentTimeMillis();
+ }
+
+ @Override
+ public void updateEntry(boolean updatePostTime) {
+ super.updateEntry(updatePostTime);
+
+ if (mEntriesToRemoveAfterExpand.contains(entry)) {
+ mEntriesToRemoveAfterExpand.remove(entry);
+ }
+ if (mEntriesToRemoveWhenReorderingAllowed.contains(entry)) {
+ mEntriesToRemoveWhenReorderingAllowed.remove(entry);
+ }
+ }
+
+ @Override
+ public void expanded(boolean expanded) {
+ if (this.expanded == expanded) {
+ return;
+ }
+
+ this.expanded = expanded;
+ if (expanded) {
+ removeAutoRemovalCallbacks();
+ } else {
+ updateEntry(false /* updatePostTime */);
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
index c85571c1895d..2bfdefe39017 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
@@ -23,7 +23,7 @@ import android.view.ViewConfiguration;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.ExpandableView;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
/**
@@ -31,7 +31,7 @@ import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
*/
public class HeadsUpTouchHelper implements Gefingerpoken {
- private HeadsUpManager mHeadsUpManager;
+ private HeadsUpManagerPhone mHeadsUpManager;
private NotificationStackScrollLayout mStackScroller;
private int mTrackingPointer;
private float mTouchSlop;
@@ -43,7 +43,7 @@ public class HeadsUpTouchHelper implements Gefingerpoken {
private NotificationPanelView mPanel;
private ExpandableNotificationRow mPickedChild;
- public HeadsUpTouchHelper(HeadsUpManager headsUpManager,
+ public HeadsUpTouchHelper(HeadsUpManagerPhone headsUpManager,
NotificationStackScrollLayout stackScroller,
NotificationPanelView notificationPanelView) {
mHeadsUpManager = headsUpManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
index df1ffdaf3d87..46d982768179 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
@@ -529,6 +529,13 @@ public class KeyguardAffordanceHelper {
KeyguardAffordanceView targetView = left ? mLeftIcon : mRightIcon;
KeyguardAffordanceView otherView = left ? mRightIcon : mLeftIcon;
startSwiping(targetView);
+
+ // Do not animate the circle expanding if the affordance isn't visible,
+ // otherwise the circle will be meaningless.
+ if (targetView.getVisibility() != View.VISIBLE) {
+ animate = false;
+ }
+
if (animate) {
fling(0, false, !left);
updateIcon(otherView, 0.0f, 0, true, false, true, false);
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 f0588626e79d..ca66e987933c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -51,6 +51,7 @@ import android.telecom.TelecomManager;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
+import android.util.MathUtils;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
@@ -166,6 +167,10 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
private String mLeftButtonStr;
private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
private boolean mDozing;
+ private int mIndicationBottomMargin;
+ private int mIndicationBottomMarginAmbient;
+ private float mDarkAmount;
+ private int mBurnInXOffset;
public KeyguardBottomAreaView(Context context) {
this(context, null);
@@ -235,6 +240,10 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
mEnterpriseDisclosure = findViewById(
R.id.keyguard_indication_enterprise_disclosure);
mIndicationText = findViewById(R.id.keyguard_indication_text);
+ mIndicationBottomMargin = getResources().getDimensionPixelSize(
+ R.dimen.keyguard_indication_margin_bottom);
+ mIndicationBottomMarginAmbient = getResources().getDimensionPixelSize(
+ R.dimen.keyguard_indication_margin_bottom_ambient);
updateCameraVisibility();
mUnlockMethodCache = UnlockMethodCache.getInstance(getContext());
mUnlockMethodCache.addListener(this);
@@ -303,11 +312,13 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
- int indicationBottomMargin = getResources().getDimensionPixelSize(
+ mIndicationBottomMargin = getResources().getDimensionPixelSize(
R.dimen.keyguard_indication_margin_bottom);
+ mIndicationBottomMarginAmbient = getResources().getDimensionPixelSize(
+ R.dimen.keyguard_indication_margin_bottom_ambient);
MarginLayoutParams mlp = (MarginLayoutParams) mIndicationArea.getLayoutParams();
- if (mlp.bottomMargin != indicationBottomMargin) {
- mlp.bottomMargin = indicationBottomMargin;
+ if (mlp.bottomMargin != mIndicationBottomMargin) {
+ mlp.bottomMargin = mIndicationBottomMargin;
mIndicationArea.setLayoutParams(mlp);
}
@@ -543,6 +554,22 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
}
}
+ public void setDarkAmount(float darkAmount) {
+ if (darkAmount == mDarkAmount) {
+ return;
+ }
+ mDarkAmount = darkAmount;
+ // Let's randomize the bottom margin every time we wake up to avoid burn-in.
+ if (darkAmount == 0) {
+ mIndicationBottomMarginAmbient = getResources().getDimensionPixelSize(
+ R.dimen.keyguard_indication_margin_bottom_ambient)
+ + (int) (Math.random() * mIndicationText.getTextSize());
+ }
+ mIndicationArea.setAlpha(MathUtils.lerp(1f, 0.7f, darkAmount));
+ mIndicationArea.setTranslationY(MathUtils.lerp(0,
+ mIndicationBottomMargin - mIndicationBottomMarginAmbient, darkAmount));
+ }
+
private static boolean isSuccessfulLaunch(int result) {
return result == ActivityManager.START_SUCCESS
|| result == ActivityManager.START_DELIVERED_TO_TOP
@@ -687,11 +714,6 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
if (mRightAffordanceView.getVisibility() == View.VISIBLE) {
startFinishDozeAnimationElement(mRightAffordanceView, delay);
}
- mIndicationArea.setAlpha(0f);
- mIndicationArea.animate()
- .alpha(1f)
- .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
- .setDuration(NotificationPanelView.DOZE_ANIMATION_DURATION);
}
private void startFinishDozeAnimationElement(View element, long delay) {
@@ -815,6 +837,22 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
}
}
+ public void dozeTimeTick() {
+ if (mDarkAmount == 1) {
+ // Move indication every minute to avoid burn-in
+ final int dozeTranslation = mIndicationBottomMargin - mIndicationBottomMarginAmbient;
+ mIndicationArea.setTranslationY(dozeTranslation + (float) Math.random() * 5);
+ }
+ }
+
+ public void setBurnInXOffset(int burnInXOffset) {
+ if (mBurnInXOffset == burnInXOffset) {
+ return;
+ }
+ mBurnInXOffset = burnInXOffset;
+ mIndicationArea.setTranslationX(burnInXOffset);
+ }
+
private class DefaultLeftButton implements IntentButton {
private IconState mIconState = new IconState();
@@ -822,8 +860,10 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
@Override
public IconState getIcon() {
mLeftIsVoiceAssist = canLaunchVoiceAssist();
+ final boolean showAffordance =
+ getResources().getBoolean(R.bool.config_keyguardShowLeftAffordance);
if (mLeftIsVoiceAssist) {
- mIconState.isVisible = mUserSetupComplete;
+ mIconState.isVisible = mUserSetupComplete && showAffordance;
if (mLeftAssistIcon == null) {
mIconState.drawable = mContext.getDrawable(R.drawable.ic_mic_26dp);
} else {
@@ -832,7 +872,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
mIconState.contentDescription = mContext.getString(
R.string.accessibility_voice_assist_button);
} else {
- mIconState.isVisible = mUserSetupComplete && isPhoneVisible();
+ mIconState.isVisible = mUserSetupComplete && showAffordance && isPhoneVisible();
mIconState.drawable = mContext.getDrawable(R.drawable.ic_phone_24dp);
mIconState.contentDescription = mContext.getString(
R.string.accessibility_phone_button);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index 99debee2dad3..699e8cf145bc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.phone;
-import android.app.ActivityManager;
import android.content.Context;
import android.os.Handler;
import android.os.UserHandle;
@@ -102,7 +101,7 @@ public class KeyguardBouncer {
return;
}
- final int activeUserId = ActivityManager.getCurrentUser();
+ final int activeUserId = KeyguardUpdateMonitor.getCurrentUser();
final boolean isSystemUser =
UserManager.isSplitSystemUser() && activeUserId == UserHandle.USER_SYSTEM;
final boolean allowDismissKeyguard = !isSystemUser && activeUserId == keyguardUserId;
@@ -122,6 +121,8 @@ public class KeyguardBouncer {
// Split up the work over multiple frames.
DejankUtils.postAfterTraversal(mShowRunnable);
+
+ mCallback.onBouncerVisiblityChanged(true /* shown */);
}
private final Runnable mShowRunnable = new Runnable() {
@@ -130,6 +131,10 @@ public class KeyguardBouncer {
mRoot.setVisibility(View.VISIBLE);
mKeyguardView.onResume();
showPromptReason(mBouncerPromptReason);
+ final CharSequence customMessage = mCallback.consumeCustomMessage();
+ if (customMessage != null) {
+ mKeyguardView.showErrorMessage(customMessage);
+ }
// We might still be collapsed and the view didn't have time to layout yet or still
// be small, let's wait on the predraw to do the animation in that case.
if (mKeyguardView.getHeight() != 0 && mKeyguardView.getHeight() != mStatusBarHeight) {
@@ -182,6 +187,7 @@ public class KeyguardBouncer {
mDismissCallbackRegistry.notifyDismissCancelled();
}
mFalsingManager.onBouncerHidden();
+ mCallback.onBouncerVisiblityChanged(false /* shown */);
cancelShowRunnable();
if (mKeyguardView != null) {
mKeyguardView.cancelDismissAction();
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 f7aa818f0abf..389be1ad6d77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -20,6 +20,7 @@ import static com.android.systemui.statusbar.notification.NotificationUtils.inte
import android.content.res.Resources;
import android.graphics.Path;
+import android.util.MathUtils;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.PathInterpolator;
@@ -45,8 +46,7 @@ public class KeyguardClockPositionAlgorithm {
private static final float BURN_IN_PREVENTION_PERIOD_Y = 521;
private static final float BURN_IN_PREVENTION_PERIOD_X = 83;
- private int mClockNotificationsMarginMin;
- private int mClockNotificationsMarginMax;
+ private int mClockNotificationsMargin;
private float mClockYFractionMin;
private float mClockYFractionMax;
private int mMaxKeyguardNotifications;
@@ -84,10 +84,8 @@ public class KeyguardClockPositionAlgorithm {
* Refreshes the dimension values.
*/
public void loadDimens(Resources res) {
- mClockNotificationsMarginMin = res.getDimensionPixelSize(
- R.dimen.keyguard_clock_notifications_margin_min);
- mClockNotificationsMarginMax = res.getDimensionPixelSize(
- R.dimen.keyguard_clock_notifications_margin_max);
+ mClockNotificationsMargin = res.getDimensionPixelSize(
+ R.dimen.keyguard_clock_notifications_margin);
mClockYFractionMin = res.getFraction(R.fraction.keyguard_clock_y_fraction_min, 1, 1);
mClockYFractionMax = res.getFraction(R.fraction.keyguard_clock_y_fraction_max, 1, 1);
mMoreCardNotificationAmount =
@@ -117,7 +115,7 @@ public class KeyguardClockPositionAlgorithm {
public float getMinStackScrollerPadding(int height, int keyguardStatusHeight) {
return mClockYFractionMin * height + keyguardStatusHeight / 2
- + mClockNotificationsMarginMin;
+ + mClockNotificationsMargin;
}
public void run(Result result) {
@@ -125,21 +123,15 @@ public class KeyguardClockPositionAlgorithm {
float clockAdjustment = getClockYExpansionAdjustment();
float topPaddingAdjMultiplier = getTopPaddingAdjMultiplier();
result.stackScrollerPaddingAdjustment = (int) (clockAdjustment*topPaddingAdjMultiplier);
- int clockNotificationsPadding = getClockNotificationsPadding()
+ result.clockY = y;
+ int clockNotificationsPadding = mClockNotificationsMargin
+ result.stackScrollerPaddingAdjustment;
int padding = y + clockNotificationsPadding;
- result.clockY = y;
- result.stackScrollerPadding = mKeyguardStatusHeight + padding;
- result.clockScale = getClockScale(result.stackScrollerPadding,
- result.clockY,
- y + getClockNotificationsPadding() + mKeyguardStatusHeight);
+ result.clockScale = getClockScale(mKeyguardStatusHeight + padding,
+ y, y + mClockNotificationsMargin + mKeyguardStatusHeight);
result.clockAlpha = getClockAlpha(result.clockScale);
- result.stackScrollerPadding = (int) interpolate(
- result.stackScrollerPadding,
- mClockBottom + y + mDozingStackPadding,
- mDarkAmount);
-
+ result.stackScrollerPadding = mClockBottom + y + mDozingStackPadding;
result.clockX = (int) interpolate(0, burnInPreventionOffsetX(), mDarkAmount);
}
@@ -154,22 +146,16 @@ public class KeyguardClockPositionAlgorithm {
return interpolate(progress, 1, mDarkAmount);
}
- private int getClockNotificationsPadding() {
- float t = getNotificationAmountT();
- t = Math.min(t, 1.0f);
- return (int) (t * mClockNotificationsMarginMin + (1 - t) * mClockNotificationsMarginMax);
- }
-
private float getClockYFraction() {
float t = getNotificationAmountT();
t = Math.min(t, 1.0f);
- return (1 - t) * mClockYFractionMax + t * mClockYFractionMin;
+ return MathUtils.lerp(mClockYFractionMax, mClockYFractionMin, t);
}
private int getClockY() {
- // Dark: Align the bottom edge of the clock at one third:
- // clockBottomEdge = result - mKeyguardStatusHeight / 2 + mClockBottom
- float clockYDark = (0.33f * mHeight + (float) mKeyguardStatusHeight / 2 - mClockBottom)
+ // Dark: Align the bottom edge of the clock at about half of the screen:
+ float clockYDark = (mClockYFractionMax * mHeight +
+ (float) mKeyguardStatusHeight / 2 - mClockBottom)
+ burnInPreventionOffsetY();
float clockYRegular = getClockYFraction() * mHeight;
return (int) interpolate(clockYRegular, clockYDark, mDarkAmount);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index a6691b1656df..09acf3e4b53b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -115,7 +115,7 @@ public class KeyguardStatusBarView extends RelativeLayout
lp = (MarginLayoutParams) mSystemIconsSuperContainer.getLayoutParams();
lp.height = getResources().getDimensionPixelSize(
- R.dimen.status_bar_header_height);
+ com.android.internal.R.dimen.quick_qs_total_height);
lp.setMarginStart(getResources().getDimensionPixelSize(
R.dimen.system_icons_super_container_margin_start));
mSystemIconsSuperContainer.setLayoutParams(lp);
@@ -333,7 +333,7 @@ public class KeyguardStatusBarView extends RelativeLayout
return false;
}
- public void onOverlayChanged() {
+ public void onThemeChanged() {
@ColorInt int textColor = Utils.getColorAttr(mContext, R.attr.wallpaperTextColor);
@ColorInt int iconColor = Utils.getDefaultColor(mContext, Color.luminance(textColor) < 0.5 ?
R.color.dark_mode_icon_color_single_tone :
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
index 533771a31d0e..1c361caa4a1a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
@@ -175,9 +175,8 @@ public class LightBarController implements BatteryController.BatteryStateChangeC
private boolean isLight(int vis, int barMode, int flag) {
boolean isTransparentBar = (barMode == MODE_TRANSPARENT
|| barMode == MODE_LIGHTS_OUT_TRANSPARENT);
- boolean allowLight = isTransparentBar && !mBatteryController.isPowerSave();
boolean light = (vis & flag) != 0;
- return allowLight && light;
+ return isTransparentBar && light;
}
private boolean animateChange() {
@@ -264,8 +263,10 @@ public class LightBarController implements BatteryController.BatteryStateChangeC
pw.println(" StatusBarTransitionsController:");
mStatusBarIconController.getTransitionsController().dump(fd, pw, args);
pw.println();
- pw.println(" NavigationBarTransitionsController:");
- mNavigationBarController.dump(fd, pw, args);
- pw.println();
+ if (mNavigationBarController != null) {
+ pw.println(" NavigationBarTransitionsController:");
+ mNavigationBarController.dump(fd, pw, args);
+ pw.println();
+ }
}
}
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 5c9446ce8672..264f5749fa73 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
@@ -250,7 +250,7 @@ public class LockIcon extends KeyguardAffordanceView implements OnUserInfoChange
}
break;
case STATE_FACE_UNLOCK:
- iconRes = com.android.internal.R.drawable.ic_account_circle;
+ iconRes = R.drawable.ic_face_unlock;
break;
case STATE_FINGERPRINT:
// If screen is off and device asleep, use the draw on animation so the first frame
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
index 87f5ca7adf73..40ddf5b497ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
@@ -124,8 +124,8 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen
} else {
if (selectedUser != null) {
// Show the selected user's static wallpaper.
- return LoaderResult.success(
- mWallpaperManager.getBitmapAsUser(selectedUser.getIdentifier()));
+ return LoaderResult.success(mWallpaperManager.getBitmapAsUser(
+ selectedUser.getIdentifier(), true /* hardware */));
} else {
// When there is no selected user, show the system wallpaper
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
index 316bd5bcaca2..0f8d59b12ddb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
@@ -61,14 +61,10 @@ public class ManagedProfileControllerImpl implements ManagedProfileController {
public void setWorkModeEnabled(boolean enableWorkMode) {
synchronized (mProfiles) {
for (UserInfo ui : mProfiles) {
- if (enableWorkMode) {
- if (!mUserManager.trySetQuietModeDisabled(ui.id, null)) {
- StatusBarManager statusBarManager = (StatusBarManager) mContext
- .getSystemService(android.app.Service.STATUS_BAR_SERVICE);
- statusBarManager.collapsePanels();
- }
- } else {
- mUserManager.setQuietModeEnabled(ui.id, true);
+ if (!mUserManager.requestQuietModeEnabled(!enableWorkMode, UserHandle.of(ui.id))) {
+ StatusBarManager statusBarManager = (StatusBarManager) mContext
+ .getSystemService(android.app.Service.STATUS_BAR_SERVICE);
+ statusBarManager.collapsePanels();
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index c44e1db367b8..72938c25b753 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -22,8 +22,13 @@ import static android.app.StatusBarManager.windowStateToString;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT;
import static com.android.systemui.statusbar.phone.StatusBar.DEBUG_WINDOW_STATE;
import static com.android.systemui.statusbar.phone.StatusBar.dumpBarTransitions;
+import static com.android.systemui.OverviewProxyService.OverviewProxyListener;
import android.accessibilityservice.AccessibilityServiceInfo;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.annotation.IdRes;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManagerNative;
@@ -39,6 +44,7 @@ import android.content.res.Configuration;
import android.database.ContentObserver;
import android.graphics.PixelFormat;
import android.graphics.Rect;
+import android.graphics.drawable.AnimatedVectorDrawable;
import android.inputmethodservice.InputMethodService;
import android.os.Binder;
import android.os.Bundle;
@@ -56,6 +62,7 @@ import android.view.IRotationWatcher.Stub;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
+import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
@@ -64,22 +71,29 @@ import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener;
+import android.widget.Button;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.keyguard.LatencyTracker;
+import com.android.internal.util.LatencyTracker;
import com.android.systemui.Dependency;
+import com.android.systemui.OverviewProxyService;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.SysUiServiceProvider;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.stackdivider.Divider;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.CommandQueue.Callbacks;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
+import com.android.systemui.statusbar.policy.KeyButtonDrawable;
import com.android.systemui.statusbar.policy.KeyButtonView;
+import com.android.systemui.statusbar.policy.RotationLockController;
import com.android.systemui.statusbar.stack.StackStateAnimator;
import java.io.FileDescriptor;
@@ -107,9 +121,11 @@ public class NavigationBarFragment extends Fragment implements Callbacks {
private int mNavigationIconHints = 0;
private int mNavigationBarMode;
+ private boolean mAccessibilityFeedbackEnabled;
private AccessibilityManager mAccessibilityManager;
private MagnificationContentObserver mMagnificationObserver;
private ContentResolver mContentResolver;
+ private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
private int mDisabledFlags1;
private StatusBar mStatusBar;
@@ -125,8 +141,32 @@ public class NavigationBarFragment extends Fragment implements Callbacks {
private int mSystemUiVisibility;
private LightBarController mLightBarController;
+ private OverviewProxyService mOverviewProxyService;
+
public boolean mHomeBlockedThisTouch;
+ private int mLastRotationSuggestion;
+ private boolean mHoveringRotationSuggestion;
+ private RotationLockController mRotationLockController;
+ private TaskStackListenerImpl mTaskStackListener;
+
+ private final Runnable mRemoveRotationProposal = () -> safeSetRotationButtonState(false);
+ private Animator mRotateShowAnimator;
+ private Animator mRotateHideAnimator;
+
+ private final OverviewProxyListener mOverviewProxyListener = new OverviewProxyListener() {
+ @Override
+ public void onConnectionChanged(boolean isConnected) {
+ mNavigationBarView.onOverviewProxyConnectionChanged(isConnected);
+ updateScreenPinningGestures();
+ }
+
+ @Override
+ public void onRecentsAnimationStarted() {
+ mNavigationBarView.setRecentsAnimationStarted(true);
+ }
+ };
+
// ----- Fragment Lifecycle Callbacks -----
@Override
@@ -152,6 +192,7 @@ public class NavigationBarFragment extends Fragment implements Callbacks {
mDisabledFlags1 = savedInstanceState.getInt(EXTRA_DISABLE_STATE, 0);
}
mAssistManager = Dependency.get(AssistManager.class);
+ mOverviewProxyService = Dependency.get(OverviewProxyService.class);
try {
WindowManagerGlobal.getWindowManagerService()
@@ -159,6 +200,12 @@ public class NavigationBarFragment extends Fragment implements Callbacks {
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
+
+ mRotationLockController = Dependency.get(RotationLockController.class);
+
+ // Register the task stack listener
+ mTaskStackListener = new TaskStackListenerImpl();
+ ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
}
@Override
@@ -174,6 +221,9 @@ public class NavigationBarFragment extends Fragment implements Callbacks {
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
+
+ // Unregister the task stack listener
+ ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener);
}
@Override
@@ -188,7 +238,7 @@ public class NavigationBarFragment extends Fragment implements Callbacks {
mNavigationBarView = (NavigationBarView) view;
mNavigationBarView.setDisabledFlags(mDisabledFlags1);
- mNavigationBarView.setComponents(mRecents, mDivider);
+ mNavigationBarView.setComponents(mRecents, mDivider, mStatusBar.getPanel());
mNavigationBarView.setOnVerticalChangedListener(this::onVerticalChanged);
mNavigationBarView.setOnTouchListener(this::onNavigationTouch);
if (savedInstanceState != null) {
@@ -202,12 +252,14 @@ public class NavigationBarFragment extends Fragment implements Callbacks {
filter.addAction(Intent.ACTION_SCREEN_ON);
getContext().registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null, null);
notifyNavigationBarScreenOn();
+ mOverviewProxyService.addCallback(mOverviewProxyListener);
}
@Override
public void onDestroyView() {
super.onDestroyView();
mNavigationBarView.getLightTransitionsController().destroy(getContext());
+ mOverviewProxyService.removeCallback(mOverviewProxyListener);
getContext().unregisterReceiver(mBroadcastReceiver);
}
@@ -263,7 +315,7 @@ public class NavigationBarFragment extends Fragment implements Callbacks {
boolean showImeSwitcher) {
boolean imeShown = (vis & InputMethodService.IME_VISIBLE) != 0;
int hints = mNavigationIconHints;
- if ((backDisposition == InputMethodService.BACK_DISPOSITION_WILL_DISMISS) || imeShown) {
+ if (imeShown && backDisposition != InputMethodService.BACK_DISPOSITION_WILL_NOT_DISMISS) {
hints |= NAVIGATION_HINT_BACK_ALT;
} else {
hints &= ~NAVIGATION_HINT_BACK_ALT;
@@ -300,6 +352,64 @@ public class NavigationBarFragment extends Fragment implements Callbacks {
}
}
+ @Override
+ public void onRotationProposal(final int rotation, boolean isValid) {
+ // This method will be called on rotation suggestion changes even if the proposed rotation
+ // is not valid for the top app. Use invalid rotation choices as a signal to remove the
+ // rotate button if shown.
+
+ if (!isValid) {
+ safeSetRotationButtonState(false);
+ return;
+ }
+
+ if (rotation == mWindowManager.getDefaultDisplay().getRotation()) {
+ // Use this as a signal to remove any current suggestions
+ getView().getHandler().removeCallbacks(mRemoveRotationProposal);
+ safeSetRotationButtonState(false);
+ } else {
+ mLastRotationSuggestion = rotation; // Remember rotation for click
+ safeSetRotationButtonState(true);
+ rescheduleRotationTimeout(false);
+ mMetricsLogger.visible(MetricsEvent.ROTATION_SUGGESTION_SHOWN);
+ }
+ }
+
+ private void safeSetRotationButtonState(boolean vis) {
+ if (mNavigationBarView != null) mNavigationBarView.setRotateSuggestionButtonState(vis);
+ }
+
+ private void safeSetRotationButtonState(boolean vis, boolean force) {
+ if (mNavigationBarView != null) {
+ mNavigationBarView.setRotateSuggestionButtonState(vis, force);
+ }
+ }
+
+ private void rescheduleRotationTimeout(final boolean reasonHover) {
+ // May be called due to a new rotation proposal or a change in hover state
+ if (reasonHover) {
+ // Don't reschedule if a hide animator is running
+ if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) {
+ return;
+ }
+ // Don't reschedule if not visible
+ if (mNavigationBarView.getRotateSuggestionButton().getVisibility() != View.VISIBLE) {
+ return;
+ }
+ }
+
+ Handler h = getView().getHandler();
+ h.removeCallbacks(mRemoveRotationProposal); // Stop any pending removal
+ h.postDelayed(mRemoveRotationProposal,
+ computeRotationProposalTimeout()); // Schedule timeout
+ }
+
+ private int computeRotationProposalTimeout() {
+ if (mAccessibilityFeedbackEnabled) return 20000;
+ if (mHoveringRotationSuggestion) return 16000;
+ return 6000;
+ }
+
// Injected from StatusBar at creation.
public void setCurrentSysuiVisibility(int systemUiVisibility) {
mSystemUiVisibility = systemUiVisibility;
@@ -351,6 +461,7 @@ public class NavigationBarFragment extends Fragment implements Callbacks {
if (masked != mDisabledFlags1) {
mDisabledFlags1 = masked;
if (mNavigationBarView != null) mNavigationBarView.setDisabledFlags(state1);
+ updateScreenPinningGestures();
}
}
@@ -364,7 +475,8 @@ public class NavigationBarFragment extends Fragment implements Callbacks {
private boolean shouldDisableNavbarGestures() {
return !mStatusBar.isDeviceProvisioned()
- || (mDisabledFlags1 & StatusBarManager.DISABLE_SEARCH) != 0;
+ || (mDisabledFlags1 & StatusBarManager.DISABLE_SEARCH) != 0
+ || mNavigationBarView.getRecentsButton().getVisibility() != View.VISIBLE;
}
private void repositionNavigationBar() {
@@ -376,6 +488,24 @@ public class NavigationBarFragment extends Fragment implements Callbacks {
((View) mNavigationBarView.getParent()).getLayoutParams());
}
+ private void updateScreenPinningGestures() {
+ if (mNavigationBarView == null) {
+ return;
+ }
+
+ // Change the cancel pin gesture to home and back if recents button is invisible
+ boolean recentsVisible = mNavigationBarView.isRecentsButtonVisible();
+ ButtonDispatcher homeButton = mNavigationBarView.getHomeButton();
+ ButtonDispatcher backButton = mNavigationBarView.getBackButton();
+ if (recentsVisible) {
+ homeButton.setOnLongClickListener(this::onHomeLongClick);
+ backButton.setOnLongClickListener(this::onLongPressBackRecents);
+ } else {
+ homeButton.setOnLongClickListener(this::onLongPressBackHome);
+ backButton.setOnLongClickListener(this::onLongPressBackHome);
+ }
+ }
+
private void notifyNavigationBarScreenOn() {
mNavigationBarView.notifyScreenOn();
}
@@ -391,16 +521,19 @@ public class NavigationBarFragment extends Fragment implements Callbacks {
ButtonDispatcher backButton = mNavigationBarView.getBackButton();
backButton.setLongClickable(true);
- backButton.setOnLongClickListener(this::onLongPressBackRecents);
ButtonDispatcher homeButton = mNavigationBarView.getHomeButton();
homeButton.setOnTouchListener(this::onHomeTouch);
- homeButton.setOnLongClickListener(this::onHomeLongClick);
ButtonDispatcher accessibilityButton = mNavigationBarView.getAccessibilityButton();
accessibilityButton.setOnClickListener(this::onAccessibilityClick);
accessibilityButton.setOnLongClickListener(this::onAccessibilityLongClick);
updateAccessibilityServicesState(mAccessibilityManager);
+
+ ButtonDispatcher rotateSuggestionButton = mNavigationBarView.getRotateSuggestionButton();
+ rotateSuggestionButton.setOnClickListener(this::onRotateSuggestionClick);
+ rotateSuggestionButton.setOnHoverListener(this::onRotateSuggestionHover);
+ updateScreenPinningGestures();
}
private boolean onHomeTouch(View v, MotionEvent event) {
@@ -437,7 +570,7 @@ public class NavigationBarFragment extends Fragment implements Callbacks {
}
private boolean onNavigationTouch(View v, MotionEvent event) {
- mStatusBar.checkUserAutohide(v, event);
+ mStatusBar.checkUserAutohide(event);
return false;
}
@@ -446,9 +579,10 @@ public class NavigationBarFragment extends Fragment implements Callbacks {
if (shouldDisableNavbarGestures()) {
return false;
}
- MetricsLogger.action(getContext(), MetricsEvent.ACTION_ASSIST_LONG_PRESS);
+ mMetricsLogger.action(MetricsEvent.ACTION_ASSIST_LONG_PRESS);
mAssistManager.startAssist(new Bundle() /* args */);
mStatusBar.awakenDreams();
+
if (mNavigationBarView != null) {
mNavigationBarView.abortCurrentGesture();
}
@@ -480,20 +614,29 @@ public class NavigationBarFragment extends Fragment implements Callbacks {
mCommandQueue.toggleRecentApps();
}
+ private boolean onLongPressBackHome(View v) {
+ return onLongPressNavigationButtons(v, R.id.back, R.id.home);
+ }
+
+ private boolean onLongPressBackRecents(View v) {
+ return onLongPressNavigationButtons(v, R.id.back, R.id.recent_apps);
+ }
+
/**
- * This handles long-press of both back and recents. They are
- * handled together to capture them both being long-pressed
+ * This handles long-press of both back and recents/home. Back is the common button with
+ * combination of recents if it is visible or home if recents is invisible.
+ * They are handled together to capture them both being long-pressed
* at the same time to exit screen pinning (lock task).
*
- * When accessibility mode is on, only a long-press from recents
+ * When accessibility mode is on, only a long-press from recents/home
* is required to exit.
*
* In all other circumstances we try to pass through long-press events
* for Back, so that apps can still use it. Which can be from two things.
* 1) Not currently in screen pinning (lock task).
- * 2) Back is long-pressed without recents.
+ * 2) Back is long-pressed without recents/home.
*/
- private boolean onLongPressBackRecents(View v) {
+ private boolean onLongPressNavigationButtons(View v, @IdRes int btnId1, @IdRes int btnId2) {
try {
boolean sendBackLongPress = false;
IActivityManager activityManager = ActivityManagerNative.getDefault();
@@ -501,33 +644,40 @@ public class NavigationBarFragment extends Fragment implements Callbacks {
boolean inLockTaskMode = activityManager.isInLockTaskMode();
if (inLockTaskMode && !touchExplorationEnabled) {
long time = System.currentTimeMillis();
+
// If we recently long-pressed the other button then they were
// long-pressed 'together'
if ((time - mLastLockToAppLongPress) < LOCK_TO_APP_GESTURE_TOLERENCE) {
- activityManager.stopLockTaskMode();
+ activityManager.stopSystemLockTaskMode();
// When exiting refresh disabled flags.
mNavigationBarView.setDisabledFlags(mDisabledFlags1, true);
return true;
- } else if ((v.getId() == R.id.back)
- && !mNavigationBarView.getRecentsButton().getCurrentView().isPressed()) {
- // If we aren't pressing recents right now then they presses
- // won't be together, so send the standard long-press action.
- sendBackLongPress = true;
+ } else if (v.getId() == btnId1) {
+ ButtonDispatcher button = btnId2 == R.id.recent_apps
+ ? mNavigationBarView.getRecentsButton()
+ : mNavigationBarView.getHomeButton();
+ if (!button.getCurrentView().isPressed()) {
+ // If we aren't pressing recents/home right now then they presses
+ // won't be together, so send the standard long-press action.
+ sendBackLongPress = true;
+ }
}
mLastLockToAppLongPress = time;
} else {
// If this is back still need to handle sending the long-press event.
- if (v.getId() == R.id.back) {
+ if (v.getId() == btnId1) {
sendBackLongPress = true;
} else if (touchExplorationEnabled && inLockTaskMode) {
- // When in accessibility mode a long press that is recents (not back)
+ // When in accessibility mode a long press that is recents/home (not back)
// should stop lock task.
- activityManager.stopLockTaskMode();
+ activityManager.stopSystemLockTaskMode();
// When exiting refresh disabled flags.
mNavigationBarView.setDisabledFlags(mDisabledFlags1, true);
return true;
- } else if (v.getId() == R.id.recent_apps) {
- return onLongPressRecents();
+ } else if (v.getId() == btnId2) {
+ return btnId2 == R.id.recent_apps
+ ? onLongPressRecents()
+ : onHomeLongClick(mNavigationBarView.getHomeButton().getCurrentView());
}
}
if (sendBackLongPress) {
@@ -575,6 +725,7 @@ public class NavigationBarFragment extends Fragment implements Callbacks {
} catch (Settings.SettingNotFoundException e) {
}
+ boolean feedbackEnabled = false;
// AccessibilityManagerService resolves services for the current user since the local
// AccessibilityManager is created from a Context with the INTERACT_ACROSS_USERS permission
final List<AccessibilityServiceInfo> services =
@@ -585,13 +736,33 @@ public class NavigationBarFragment extends Fragment implements Callbacks {
if ((info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0) {
requestingServices++;
}
+
+ if (info.feedbackType != 0 && info.feedbackType !=
+ AccessibilityServiceInfo.FEEDBACK_GENERIC) {
+ feedbackEnabled = true;
+ }
}
+ mAccessibilityFeedbackEnabled = feedbackEnabled;
+
final boolean showAccessibilityButton = requestingServices >= 1;
final boolean targetSelection = requestingServices >= 2;
mNavigationBarView.setAccessibilityButtonState(showAccessibilityButton, targetSelection);
}
+ private void onRotateSuggestionClick(View v) {
+ mMetricsLogger.action(MetricsEvent.ACTION_ROTATION_SUGGESTION_ACCEPTED);
+ mRotationLockController.setRotationLockedAtAngle(true, mLastRotationSuggestion);
+ }
+
+ private boolean onRotateSuggestionHover(View v, MotionEvent event) {
+ final int action = event.getActionMasked();
+ mHoveringRotationSuggestion = (action == MotionEvent.ACTION_HOVER_ENTER)
+ || (action == MotionEvent.ACTION_HOVER_MOVE);
+ rescheduleRotationTimeout(true);
+ return false; // Must return false so a11y hover events are dispatched correctly.
+ }
+
// ----- Methods that StatusBar talks to (should be minimized) -----
public void setLightBarController(LightBarController lightBarController) {
@@ -639,11 +810,21 @@ public class NavigationBarFragment extends Fragment implements Callbacks {
private final Stub mRotationWatcher = new Stub() {
@Override
- public void onRotationChanged(int rotation) throws RemoteException {
+ public void onRotationChanged(final int rotation) throws RemoteException {
// We need this to be scheduled as early as possible to beat the redrawing of
// window in response to the orientation change.
Handler h = getView().getHandler();
Message msg = Message.obtain(h, () -> {
+
+ // If the screen rotation changes while locked, potentially update lock to flow with
+ // new screen rotation and hide any showing suggestions.
+ if (mRotationLockController.isRotationLocked()) {
+ if (shouldOverrideUserLockPrefs(rotation)) {
+ mRotationLockController.setRotationLockedAtAngle(true, rotation);
+ }
+ safeSetRotationButtonState(false, true);
+ }
+
if (mNavigationBarView != null
&& mNavigationBarView.needsReorient(rotation)) {
repositionNavigationBar();
@@ -652,6 +833,12 @@ public class NavigationBarFragment extends Fragment implements Callbacks {
msg.setAsynchronous(true);
h.sendMessageAtFrontOfQueue(msg);
}
+
+ private boolean shouldOverrideUserLockPrefs(final int rotation) {
+ // Only override user prefs when returning to portrait.
+ // Don't let apps that force landscape or 180 alter user lock.
+ return rotation == Surface.ROTATION_0;
+ }
};
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@@ -665,6 +852,31 @@ public class NavigationBarFragment extends Fragment implements Callbacks {
}
};
+ class TaskStackListenerImpl extends SysUiTaskStackChangeListener {
+ // Invalidate any rotation suggestion on task change or activity orientation change
+ // Note: all callbacks happen on main thread
+
+ @Override
+ public void onTaskStackChanged() {
+ safeSetRotationButtonState(false);
+ }
+
+ @Override
+ public void onTaskRemoved(int taskId) {
+ safeSetRotationButtonState(false);
+ }
+
+ @Override
+ public void onTaskMovedToFront(int taskId) {
+ safeSetRotationButtonState(false);
+ }
+
+ @Override
+ public void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation) {
+ safeSetRotationButtonState(false);
+ }
+ }
+
public static View create(Context context, FragmentListener listener) {
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
@@ -678,6 +890,7 @@ public class NavigationBarFragment extends Fragment implements Callbacks {
PixelFormat.TRANSLUCENT);
lp.token = new Binder();
lp.setTitle("NavigationBar");
+ lp.accessibilityTitle = context.getString(R.string.nav_bar);
lp.windowAnimations = 0;
View navigationBarView = LayoutInflater.from(context).inflate(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
index ee9a791585c4..d15c771ec098 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
@@ -16,36 +16,42 @@
package com.android.systemui.statusbar.phone;
+import static android.view.WindowManager.DOCKED_INVALID;
+import static android.view.WindowManager.DOCKED_LEFT;
+import static android.view.WindowManager.DOCKED_TOP;
+import static com.android.systemui.OverviewProxyService.DEBUG_OVERVIEW_PROXY;
+import static com.android.systemui.OverviewProxyService.TAG_OPS;
+
import android.app.ActivityManager;
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
import android.graphics.Rect;
-import android.view.GestureDetector;
+import android.os.RemoteException;
+import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
-import android.view.ViewConfiguration;
-
-import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
import com.android.systemui.Dependency;
+import com.android.systemui.OverviewProxyService;
+import com.android.systemui.OverviewProxyService.OverviewProxyListener;
import com.android.systemui.R;
import com.android.systemui.RecentsComponent;
+import com.android.systemui.SysUiServiceProvider;
import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper;
+import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.stackdivider.Divider;
import com.android.systemui.tuner.TunerService;
-import static android.view.WindowManager.DOCKED_INVALID;
-import static android.view.WindowManager.DOCKED_LEFT;
-import static android.view.WindowManager.DOCKED_TOP;
-
/**
* Class to detect gestures on the navigation bar.
*/
-public class NavigationBarGestureHelper extends GestureDetector.SimpleOnGestureListener
- implements TunerService.Tunable, GestureHelper {
+public class NavigationBarGestureHelper implements TunerService.Tunable, GestureHelper {
+ private static final String TAG = "NavBarGestureHelper";
private static final String KEY_DOCK_WINDOW_GESTURE = "overview_nav_bar_gesture";
/**
* When dragging from the navigation bar, we drag in recents.
@@ -67,32 +73,43 @@ public class NavigationBarGestureHelper extends GestureDetector.SimpleOnGestureL
private Context mContext;
private NavigationBarView mNavigationBarView;
private boolean mIsVertical;
- private boolean mIsRTL;
- private final GestureDetector mTaskSwitcherDetector;
+ private final QuickScrubController mQuickScrubController;
private final int mScrollTouchSlop;
- private final int mMinFlingVelocity;
+ private final Matrix mTransformGlobalMatrix = new Matrix();
+ private final Matrix mTransformLocalMatrix = new Matrix();
+ private final StatusBar mStatusBar;
private int mTouchDownX;
private int mTouchDownY;
private boolean mDownOnRecents;
private VelocityTracker mVelocityTracker;
+ private OverviewProxyService mOverviewProxyService = Dependency.get(OverviewProxyService.class);
+ private final OverviewProxyListener mOverviewProxyListener = new OverviewProxyListener() {
+ @Override
+ public void onRecentsAnimationStarted() {
+ mRecentsAnimationStarted = true;
+ mQuickScrubController.cancelQuickSwitch();
+ }
+ };
+ private boolean mRecentsAnimationStarted;
private boolean mDockWindowEnabled;
private boolean mDockWindowTouchSlopExceeded;
private int mDragMode;
public NavigationBarGestureHelper(Context context) {
mContext = context;
- ViewConfiguration configuration = ViewConfiguration.get(context);
+ mStatusBar = SysUiServiceProvider.getComponent(context, StatusBar.class);
Resources r = context.getResources();
mScrollTouchSlop = r.getDimensionPixelSize(R.dimen.navigation_bar_min_swipe_distance);
- mMinFlingVelocity = configuration.getScaledMinimumFlingVelocity();
- mTaskSwitcherDetector = new GestureDetector(context, this);
+ mQuickScrubController = new QuickScrubController(context);
Dependency.get(TunerService.class).addTunable(this, KEY_DOCK_WINDOW_GESTURE);
+ mOverviewProxyService.addCallback(mOverviewProxyListener);
}
public void destroy() {
Dependency.get(TunerService.class).removeTunable(this);
+ mOverviewProxyService.removeCallback(mOverviewProxyListener);
}
public void setComponents(RecentsComponent recentsComponent, Divider divider,
@@ -100,42 +117,89 @@ public class NavigationBarGestureHelper extends GestureDetector.SimpleOnGestureL
mRecentsComponent = recentsComponent;
mDivider = divider;
mNavigationBarView = navigationBarView;
+ mQuickScrubController.setComponents(mNavigationBarView);
}
public void setBarState(boolean isVertical, boolean isRTL) {
mIsVertical = isVertical;
- mIsRTL = isRTL;
+ mQuickScrubController.setBarState(isVertical, isRTL);
+ }
+
+ private boolean proxyMotionEvents(MotionEvent event) {
+ final IOverviewProxy overviewProxy = mOverviewProxyService.getProxy();
+ if (overviewProxy != null) {
+ mNavigationBarView.requestUnbufferedDispatch(event);
+ event.transform(mTransformGlobalMatrix);
+ try {
+ overviewProxy.onMotionEvent(event);
+ if (DEBUG_OVERVIEW_PROXY) {
+ Log.d(TAG_OPS, "Send MotionEvent: " + event.toString());
+ }
+ return true;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Callback failed", e);
+ } finally {
+ event.transform(mTransformLocalMatrix);
+ }
+ }
+ return false;
}
public boolean onInterceptTouchEvent(MotionEvent event) {
- // If we move more than a fixed amount, then start capturing for the
- // task switcher detector
- mTaskSwitcherDetector.onTouchEvent(event);
int action = event.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
mTouchDownX = (int) event.getX();
mTouchDownY = (int) event.getY();
+ mTransformGlobalMatrix.set(Matrix.IDENTITY_MATRIX);
+ mTransformLocalMatrix.set(Matrix.IDENTITY_MATRIX);
+ mNavigationBarView.transformMatrixToGlobal(mTransformGlobalMatrix);
+ mNavigationBarView.transformMatrixToLocal(mTransformLocalMatrix);
+ mRecentsAnimationStarted = false;
break;
}
- case MotionEvent.ACTION_MOVE: {
- int x = (int) event.getX();
- int y = (int) event.getY();
- int xDiff = Math.abs(x - mTouchDownX);
- int yDiff = Math.abs(y - mTouchDownY);
- boolean exceededTouchSlop = !mIsVertical
- ? xDiff > mScrollTouchSlop && xDiff > yDiff
- : yDiff > mScrollTouchSlop && yDiff > xDiff;
- if (exceededTouchSlop) {
- return true;
- }
- break;
- }
- case MotionEvent.ACTION_CANCEL:
- case MotionEvent.ACTION_UP:
- break;
}
- return mDockWindowEnabled && interceptDockWindowEvent(event);
+ boolean handledByQuickscrub = mQuickScrubController.onInterceptTouchEvent(event);
+ if (mStatusBar.isPresenterFullyCollapsed() && !handledByQuickscrub) {
+ // Proxy motion events until we start intercepting for quickscrub
+ proxyMotionEvents(event);
+ }
+
+ boolean result = handledByQuickscrub;
+ result |= mRecentsAnimationStarted;
+ if (mDockWindowEnabled) {
+ result |= interceptDockWindowEvent(event);
+ }
+ return result;
+ }
+
+ public boolean onTouchEvent(MotionEvent event) {
+ // The same down event was just sent on intercept and therefore can be ignored here
+ boolean ignoreProxyDownEvent = event.getAction() == MotionEvent.ACTION_DOWN
+ && mOverviewProxyService.getProxy() != null;
+ boolean result = mStatusBar.isPresenterFullyCollapsed()
+ && (mQuickScrubController.onTouchEvent(event)
+ || ignoreProxyDownEvent
+ || proxyMotionEvents(event));
+ result |= mRecentsAnimationStarted;
+ if (mDockWindowEnabled) {
+ result |= handleDockWindowEvent(event);
+ }
+ return result;
+ }
+
+ public void onDraw(Canvas canvas) {
+ if (mOverviewProxyService.getProxy() != null) {
+ mQuickScrubController.onDraw(canvas);
+ }
+ }
+
+ public void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ mQuickScrubController.onLayout(changed, left, top, right, bottom);
+ }
+
+ public void onDarkIntensityChange(float intensity) {
+ mQuickScrubController.onDarkIntensityChange(intensity);
}
private boolean interceptDockWindowEvent(MotionEvent event) {
@@ -206,7 +270,7 @@ public class NavigationBarGestureHelper extends GestureDetector.SimpleOnGestureL
&& mDivider.getView().getWindowManagerProxy().getDockSide() == DOCKED_INVALID) {
Rect initialBounds = null;
int dragMode = calculateDragMode();
- int createMode = ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+ int createMode = ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
if (dragMode == DRAG_MODE_DIVIDER) {
initialBounds = new Rect();
mDivider.getView().calculateBoundsForPosition(mIsVertical
@@ -218,10 +282,10 @@ public class NavigationBarGestureHelper extends GestureDetector.SimpleOnGestureL
initialBounds);
} else if (dragMode == DRAG_MODE_RECENTS && mTouchDownX
< mContext.getResources().getDisplayMetrics().widthPixels / 2) {
- createMode = ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
+ createMode = ActivityManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
}
- boolean docked = mRecentsComponent.dockTopTask(dragMode, createMode, initialBounds,
- MetricsEvent.ACTION_WINDOW_DOCK_SWIPE);
+ boolean docked = mRecentsComponent.splitPrimaryTask(dragMode, createMode,
+ initialBounds, MetricsEvent.ACTION_WINDOW_DOCK_SWIPE);
if (docked) {
mDragMode = dragMode;
if (mDragMode == DRAG_MODE_DIVIDER) {
@@ -274,37 +338,6 @@ public class NavigationBarGestureHelper extends GestureDetector.SimpleOnGestureL
return DRAG_MODE_RECENTS;
}
- public boolean onTouchEvent(MotionEvent event) {
- boolean result = mTaskSwitcherDetector.onTouchEvent(event);
- if (mDockWindowEnabled) {
- result |= handleDockWindowEvent(event);
- }
- return result;
- }
-
- @Override
- public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
- float absVelX = Math.abs(velocityX);
- float absVelY = Math.abs(velocityY);
- boolean isValidFling = absVelX > mMinFlingVelocity &&
- mIsVertical ? (absVelY > absVelX) : (absVelX > absVelY);
- if (isValidFling && mRecentsComponent != null) {
- boolean showNext;
- if (!mIsRTL) {
- showNext = mIsVertical ? (velocityY < 0) : (velocityX < 0);
- } else {
- // In RTL, vertical is still the same, but horizontal is flipped
- showNext = mIsVertical ? (velocityY < 0) : (velocityX > 0);
- }
- if (showNext) {
- mRecentsComponent.showNextAffiliatedTask();
- } else {
- mRecentsComponent.showPrevAffiliatedTask();
- }
- }
- return true;
- }
-
@Override
public void onTuningChanged(String key, String newValue) {
switch (key) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
index 9f89fe6cb371..989423530599 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
@@ -57,7 +57,7 @@ public class NavigationBarInflaterView extends FrameLayout
public static final String NAV_BAR_LEFT = "sysui_nav_bar_left";
public static final String NAV_BAR_RIGHT = "sysui_nav_bar_right";
- public static final String MENU_IME = "menu_ime";
+ public static final String MENU_IME_ROTATE = "menu_ime";
public static final String BACK = "back";
public static final String HOME = "home";
public static final String RECENT = "recent";
@@ -319,7 +319,7 @@ public class NavigationBarInflaterView extends FrameLayout
String s = Dependency.get(TunerService.class).getValue(NAV_BAR_LEFT, NAVSPACE);
button = extractButton(s);
} else if (RIGHT.equals(button)) {
- String s = Dependency.get(TunerService.class).getValue(NAV_BAR_RIGHT, MENU_IME);
+ String s = Dependency.get(TunerService.class).getValue(NAV_BAR_RIGHT, MENU_IME_ROTATE);
button = extractButton(s);
}
// Let plugins go first so they can override a standard view if they want.
@@ -333,7 +333,7 @@ public class NavigationBarInflaterView extends FrameLayout
v = inflater.inflate(R.layout.back, parent, false);
} else if (RECENT.equals(button)) {
v = inflater.inflate(R.layout.recent_apps, parent, false);
- } else if (MENU_IME.equals(button)) {
+ } else if (MENU_IME_ROTATE.equals(button)) {
v = inflater.inflate(R.layout.menu_ime, parent, false);
} else if (NAVSPACE.equals(button)) {
v = inflater.inflate(R.layout.nav_key_space, parent, false);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
index b81a3b0416a4..e09d31cea082 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
@@ -26,7 +26,7 @@ import android.view.IWallpaperVisibilityListener;
import android.view.IWindowManager;
import android.view.MotionEvent;
import android.view.View;
-import android.view.WindowManagerGlobal;
+import android.view.View.OnLayoutChangeListener;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.Dependency;
@@ -37,10 +37,12 @@ public final class NavigationBarTransitions extends BarTransitions {
private final NavigationBarView mView;
private final IStatusBarService mBarService;
private final LightBarTransitionsController mLightTransitionsController;
+ private final boolean mAllowAutoDimWallpaperNotVisible;
private boolean mWallpaperVisible;
private boolean mLightsOut;
private boolean mAutoDim;
+ private View mNavButtons;
public NavigationBarTransitions(NavigationBarView view) {
super(view, R.drawable.nav_background);
@@ -49,6 +51,8 @@ public final class NavigationBarTransitions extends BarTransitions {
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
mLightTransitionsController = new LightBarTransitionsController(view.getContext(),
this::applyDarkIntensity);
+ mAllowAutoDimWallpaperNotVisible = view.getContext().getResources()
+ .getBoolean(R.bool.config_navigation_bar_enable_auto_dim_no_visible_wallpaper);
IWindowManager windowManagerService = Dependency.get(IWindowManager.class);
Handler handler = Handler.getMain();
@@ -64,6 +68,18 @@ public final class NavigationBarTransitions extends BarTransitions {
}, Display.DEFAULT_DISPLAY);
} catch (RemoteException e) {
}
+ mView.addOnLayoutChangeListener(
+ (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+ View currentView = mView.getCurrentView();
+ if (currentView != null) {
+ mNavButtons = currentView.findViewById(R.id.nav_buttons);
+ applyLightsOut(false, true);
+ }
+ });
+ View currentView = mView.getCurrentView();
+ if (currentView != null) {
+ mNavButtons = currentView.findViewById(R.id.nav_buttons);
+ }
}
public void init() {
@@ -80,8 +96,8 @@ public final class NavigationBarTransitions extends BarTransitions {
@Override
protected boolean isLightsOut(int mode) {
- return super.isLightsOut(mode) || (mAutoDim && !mWallpaperVisible
- && mode != MODE_WARNING);
+ return super.isLightsOut(mode) || (mAllowAutoDimWallpaperNotVisible && mAutoDim
+ && !mWallpaperVisible && mode != MODE_WARNING);
}
public LightBarTransitionsController getLightTransitionsController() {
@@ -103,21 +119,20 @@ public final class NavigationBarTransitions extends BarTransitions {
if (!force && lightsOut == mLightsOut) return;
mLightsOut = lightsOut;
-
- final View navButtons = mView.getCurrentView().findViewById(R.id.nav_buttons);
+ if (mNavButtons == null) return;
// ok, everyone, stop it right there
- navButtons.animate().cancel();
+ mNavButtons.animate().cancel();
// Bump percentage by 10% if dark.
float darkBump = mLightTransitionsController.getCurrentDarkIntensity() / 10;
final float navButtonsAlpha = lightsOut ? 0.6f + darkBump : 1f;
if (!animate) {
- navButtons.setAlpha(navButtonsAlpha);
+ mNavButtons.setAlpha(navButtonsAlpha);
} else {
final int duration = lightsOut ? LIGHTS_OUT_DURATION : LIGHTS_IN_DURATION;
- navButtons.animate()
+ mNavButtons.animate()
.alpha(navButtonsAlpha)
.setDuration(duration)
.start();
@@ -136,6 +151,7 @@ public final class NavigationBarTransitions extends BarTransitions {
if (mAutoDim) {
applyLightsOut(false, true);
}
+ mView.onDarkIntensityChange(darkIntensity);
}
private final View.OnTouchListener mLightsOutListener = new View.OnTouchListener() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 9a7039a515a1..7994253276ab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.phone;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.animation.LayoutTransition;
import android.animation.LayoutTransition.TransitionListener;
import android.animation.ObjectAnimator;
@@ -26,11 +28,14 @@ import android.app.ActivityManager;
import android.app.StatusBarManager;
import android.content.Context;
import android.content.res.Configuration;
+import android.graphics.Canvas;
import android.graphics.Point;
import android.graphics.Rect;
+import android.graphics.drawable.AnimatedVectorDrawable;
import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
+import android.support.annotation.ColorInt;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
@@ -47,15 +52,19 @@ import android.widget.FrameLayout;
import com.android.settingslib.Utils;
import com.android.systemui.Dependency;
import com.android.systemui.DockedStackExistsListener;
+import com.android.systemui.Interpolators;
+import com.android.systemui.OverviewProxyService;
import com.android.systemui.R;
import com.android.systemui.RecentsComponent;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.statusbar.phone.NavGesture;
import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper;
+import com.android.systemui.recents.RecentsOnboarding;
import com.android.systemui.stackdivider.Divider;
import com.android.systemui.statusbar.policy.DeadZone;
import com.android.systemui.statusbar.policy.KeyButtonDrawable;
+import com.android.systemui.statusbar.policy.TintedKeyButtonDrawable;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -65,6 +74,8 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
final static boolean DEBUG = false;
final static String TAG = "StatusBar/NavBarView";
+ final static int BUTTON_FADE_IN_OUT_DURATION_MS = 100;
+
// slippery nav bar when everything is disabled, e.g. during setup
final static boolean SLIPPERY_WHEN_DISABLED = true;
@@ -80,6 +91,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
boolean mShowMenu;
boolean mShowAccessibilityButton;
boolean mLongClickableAccessibilityButton;
+ boolean mShowRotateButton;
int mDisabledFlags = 0;
int mNavigationIconHints = 0;
@@ -92,10 +104,12 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
private KeyButtonDrawable mImeIcon;
private KeyButtonDrawable mMenuIcon;
private KeyButtonDrawable mAccessibilityIcon;
+ private KeyButtonDrawable mRotateSuggestionIcon;
private GestureHelper mGestureHelper;
private DeadZone mDeadZone;
private final NavigationBarTransitions mBarTransitions;
+ private final OverviewProxyService mOverviewProxyService;
// workaround for LayoutTransitions leaving the nav buttons in a weird state (bug 5549288)
final static boolean WORKAROUND_INVALID_LAYOUT = true;
@@ -117,6 +131,10 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
private NavigationBarInflaterView mNavigationInflaterView;
private RecentsComponent mRecentsComponent;
private Divider mDivider;
+ private RecentsOnboarding mRecentsOnboarding;
+ private NotificationPanelView mPanelView;
+
+ private Animator mRotateHideAnimator;
private class NavTransitionListener implements TransitionListener {
private boolean mBackTransitioning;
@@ -208,6 +226,9 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
mShowAccessibilityButton = false;
mLongClickableAccessibilityButton = false;
+ mOverviewProxyService = Dependency.get(OverviewProxyService.class);
+ mRecentsOnboarding = new RecentsOnboarding(context, mOverviewProxyService);
+
mConfiguration = new Configuration();
mConfiguration.updateFrom(context.getResources().getConfiguration());
updateIcons(context, Configuration.EMPTY, mConfiguration);
@@ -221,6 +242,8 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
mButtonDispatchers.put(R.id.ime_switcher, new ButtonDispatcher(R.id.ime_switcher));
mButtonDispatchers.put(R.id.accessibility_button,
new ButtonDispatcher(R.id.accessibility_button));
+ mButtonDispatchers.put(R.id.rotate_suggestion,
+ new ButtonDispatcher(R.id.rotate_suggestion));
}
public BarTransitions getBarTransitions() {
@@ -231,9 +254,11 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
return mBarTransitions.getLightTransitionsController();
}
- public void setComponents(RecentsComponent recentsComponent, Divider divider) {
+ public void setComponents(RecentsComponent recentsComponent, Divider divider,
+ NotificationPanelView panel) {
mRecentsComponent = recentsComponent;
mDivider = divider;
+ mPanelView = panel;
if (mGestureHelper instanceof NavigationBarGestureHelper) {
((NavigationBarGestureHelper) mGestureHelper).setComponents(
recentsComponent, divider, this);
@@ -245,12 +270,16 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
notifyVerticalChangedListener(mVertical);
}
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- if (mGestureHelper.onTouchEvent(event)) {
- return true;
+ public void setRecentsAnimationStarted(boolean started) {
+ if (mRecentsOnboarding != null) {
+ mRecentsOnboarding.onRecentsAnimationStarted();
}
- return super.onTouchEvent(event);
+ }
+
+ public void onConnectionChanged(boolean isConnected) {
+ updateSlippery();
+ setDisabledFlags(mDisabledFlags, true);
+ setUpSwipeUpOnboarding(isConnected);
}
@Override
@@ -258,6 +287,14 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
return mGestureHelper.onInterceptTouchEvent(event);
}
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (mGestureHelper.onTouchEvent(event)) {
+ return true;
+ }
+ return super.onTouchEvent(event);
+ }
+
public void abortCurrentGesture() {
getHomeButton().abortCurrentGesture();
}
@@ -296,10 +333,18 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
return mButtonDispatchers.get(R.id.accessibility_button);
}
+ public ButtonDispatcher getRotateSuggestionButton() {
+ return mButtonDispatchers.get(R.id.rotate_suggestion);
+ }
+
public SparseArray<ButtonDispatcher> getButtonDispatchers() {
return mButtonDispatchers;
}
+ public boolean isRecentsButtonVisible() {
+ return getRecentsButton().getVisibility() == View.VISIBLE;
+ }
+
private void updateCarModeIcons(Context ctx) {
mBackCarModeIcon = getDrawable(ctx,
R.drawable.ic_sysbar_back_carmode, R.drawable.ic_sysbar_back_carmode);
@@ -319,14 +364,23 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
}
if (oldConfig.densityDpi != newConfig.densityDpi
|| oldConfig.getLayoutDirection() != newConfig.getLayoutDirection()) {
- mBackIcon = getDrawable(ctx, R.drawable.ic_sysbar_back, R.drawable.ic_sysbar_back_dark);
+ final boolean proxyAvailable = mOverviewProxyService.getProxy() != null;
+ mBackIcon = proxyAvailable
+ ? getDrawable(ctx, R.drawable.ic_sysbar_back_quick_step,
+ R.drawable.ic_sysbar_back_quick_step_dark)
+ : getDrawable(ctx, R.drawable.ic_sysbar_back, R.drawable.ic_sysbar_back_dark);
mBackLandIcon = mBackIcon;
- mBackAltIcon = getDrawable(ctx,
- R.drawable.ic_sysbar_back_ime, R.drawable.ic_sysbar_back_ime_dark);
+ mBackAltIcon = proxyAvailable
+ ? getDrawable(ctx, R.drawable.ic_sysbar_back_ime_quick_step,
+ R.drawable.ic_sysbar_back_ime_quick_step_dark)
+ : getDrawable(ctx, R.drawable.ic_sysbar_back_ime,
+ R.drawable.ic_sysbar_back_ime_dark);
mBackAltLandIcon = mBackAltIcon;
- mHomeDefaultIcon = getDrawable(ctx,
- R.drawable.ic_sysbar_home, R.drawable.ic_sysbar_home_dark);
+ mHomeDefaultIcon = proxyAvailable
+ ? getDrawable(ctx, R.drawable.ic_sysbar_home_quick_step,
+ R.drawable.ic_sysbar_home_quick_step_dark)
+ : getDrawable(ctx, R.drawable.ic_sysbar_home, R.drawable.ic_sysbar_home_dark);
mRecentIcon = getDrawable(ctx,
R.drawable.ic_sysbar_recent, R.drawable.ic_sysbar_recent_dark);
mMenuIcon = getDrawable(ctx, R.drawable.ic_sysbar_menu, R.drawable.ic_sysbar_menu_dark);
@@ -340,6 +394,11 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
mImeIcon = getDrawable(darkContext, lightContext,
R.drawable.ic_ime_switcher_default, R.drawable.ic_ime_switcher_default);
+ int lightColor = Utils.getColorAttr(lightContext, R.attr.singleToneColor);
+ int darkColor = Utils.getColorAttr(darkContext, R.attr.singleToneColor);
+ mRotateSuggestionIcon = getDrawable(ctx, R.drawable.ic_sysbar_rotate_button,
+ lightColor, darkColor);
+
if (ALTERNATE_CAR_MODE_UI) {
updateCarModeIcons(ctx);
}
@@ -357,6 +416,11 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
darkContext.getDrawable(darkIcon));
}
+ private KeyButtonDrawable getDrawable(Context ctx, @DrawableRes int icon,
+ @ColorInt int lightColor, @ColorInt int darkColor) {
+ return TintedKeyButtonDrawable.create(ctx.getDrawable(icon), lightColor, darkColor);
+ }
+
@Override
public void setLayoutDirection(int layoutDirection) {
// Reload all the icons
@@ -416,17 +480,22 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
getHomeButton().setImageDrawable(mHomeDefaultIcon);
}
- // The Accessibility button always overrides the appearance of the IME switcher
+ // Update IME button visibility, a11y and rotate button always overrides the appearance
final boolean showImeButton =
- !mShowAccessibilityButton && ((hints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN)
- != 0);
+ !mShowAccessibilityButton &&
+ !mShowRotateButton &&
+ ((hints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0);
getImeSwitchButton().setVisibility(showImeButton ? View.VISIBLE : View.INVISIBLE);
getImeSwitchButton().setImageDrawable(mImeIcon);
- // Update menu button in case the IME state has changed.
+ // Update menu button, visibility logic in method
setMenuVisibility(mShowMenu, true);
getMenuButton().setImageDrawable(mMenuIcon);
+ // Update rotate button, visibility altered by a11y button logic
+ getRotateSuggestionButton().setImageDrawable(mRotateSuggestionIcon);
+
+ // Update a11y button, visibility logic in state method
setAccessibilityButtonState(mShowAccessibilityButton, mLongClickableAccessibilityButton);
getAccessibilityButton().setImageDrawable(mAccessibilityIcon);
@@ -448,10 +517,22 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
// Always disable recents when alternate car mode UI is active.
boolean disableRecent = mUseCarModeUi
- || ((disabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0);
- final boolean disableBack = ((disabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0)
+ || ((disabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0);
+
+ boolean disableBack = ((disabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0)
&& ((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) == 0);
+ if ((disableRecent || disableBack) && inScreenPinning()) {
+ // Don't hide back and recents buttons when in screen pinning mode, as they are used for
+ // exiting.
+ disableBack = false;
+ disableRecent = false;
+ }
+ if (mOverviewProxyService.getProxy() != null) {
+ // When overview is connected to the launcher service, disable the recents button
+ disableRecent = true;
+ }
+
ViewGroup navButtons = (ViewGroup) getCurrentView().findViewById(R.id.nav_buttons);
if (navButtons != null) {
LayoutTransition lt = navButtons.getLayoutTransition();
@@ -461,20 +542,16 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
}
}
}
- if (inLockTask() && disableRecent && !disableHome) {
- // Don't hide recents when in lock task, it is used for exiting.
- // Unless home is hidden, then in DPM locked mode and no exit available.
- disableRecent = false;
- }
getBackButton().setVisibility(disableBack ? View.INVISIBLE : View.VISIBLE);
getHomeButton().setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE);
getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE);
}
- private boolean inLockTask() {
+ private boolean inScreenPinning() {
try {
- return ActivityManager.getService().isInLockTaskMode();
+ return ActivityManager.getService().getLockTaskModeState()
+ == ActivityManager.LOCK_TASK_MODE_PINNED;
} catch (RemoteException e) {
return false;
}
@@ -527,6 +604,35 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
}
}
+ public void onPanelExpandedChange(boolean expanded) {
+ updateSlippery();
+ }
+
+ private void updateSlippery() {
+ setSlippery(mOverviewProxyService.getProxy() != null && mPanelView.isFullyExpanded());
+ }
+
+ private void setSlippery(boolean slippery) {
+ boolean changed = false;
+ final ViewGroup navbarView = ((ViewGroup) getParent());
+ final WindowManager.LayoutParams lp = (WindowManager.LayoutParams) navbarView
+ .getLayoutParams();
+ if (lp == null) {
+ return;
+ }
+ if (slippery && (lp.flags & WindowManager.LayoutParams.FLAG_SLIPPERY) == 0) {
+ lp.flags |= WindowManager.LayoutParams.FLAG_SLIPPERY;
+ changed = true;
+ } else if (!slippery && (lp.flags & WindowManager.LayoutParams.FLAG_SLIPPERY) != 0) {
+ lp.flags &= ~WindowManager.LayoutParams.FLAG_SLIPPERY;
+ changed = true;
+ }
+ if (changed) {
+ WindowManager wm = (WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE);
+ wm.updateViewLayout(navbarView, lp);
+ }
+ }
+
public void setMenuVisibility(final boolean show) {
setMenuVisibility(show, false);
}
@@ -536,8 +642,10 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
mShowMenu = show;
- // Only show Menu if IME switcher and Accessibility button not shown.
- final boolean shouldShow = mShowMenu && !mShowAccessibilityButton &&
+ // Only show Menu if IME switcher, rotate and Accessibility buttons are not shown.
+ final boolean shouldShow = mShowMenu &&
+ !mShowAccessibilityButton &&
+ !mShowRotateButton &&
((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) == 0);
getMenuButton().setVisibility(shouldShow ? View.VISIBLE : View.INVISIBLE);
@@ -547,15 +655,96 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
mShowAccessibilityButton = visible;
mLongClickableAccessibilityButton = longClickable;
if (visible) {
- // Accessibility button overrides Menu and IME switcher buttons.
+ // Accessibility button overrides Menu, IME switcher and rotate buttons.
setMenuVisibility(false, true);
getImeSwitchButton().setVisibility(View.INVISIBLE);
+ setRotateSuggestionButtonState(false, true);
}
getAccessibilityButton().setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
getAccessibilityButton().setLongClickable(longClickable);
}
+ public void setRotateSuggestionButtonState(final boolean visible) {
+ setRotateSuggestionButtonState(visible, false);
+ }
+
+ public void setRotateSuggestionButtonState(final boolean visible, final boolean force) {
+ ButtonDispatcher rotBtn = getRotateSuggestionButton();
+ final boolean currentlyVisible = mShowRotateButton;
+
+ // Rerun a show animation to indicate change but don't rerun a hide animation
+ if (!visible && !currentlyVisible) return;
+
+ View currentView = rotBtn.getCurrentView();
+ if (currentView == null) return;
+
+ KeyButtonDrawable kbd = rotBtn.getImageDrawable();
+ if (kbd == null) return;
+
+ AnimatedVectorDrawable animIcon = null;
+ if (kbd.getDrawable(0) instanceof AnimatedVectorDrawable) {
+ animIcon = (AnimatedVectorDrawable) kbd.getDrawable(0);
+ }
+
+ if (visible) { // Appear and change, cannot force
+ setRotateButtonVisibility(true);
+
+ // Stop any currently running hide animations
+ if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) {
+ mRotateHideAnimator.pause();
+ }
+
+ // Reset the alpha if any has changed due to hide animation
+ currentView.setAlpha(1f);
+
+ // Run the rotate icon's animation if it has one
+ if (animIcon != null) {
+ animIcon.reset();
+ animIcon.start();
+ }
+
+ } else { // Hide
+ if (force) {
+ // If a hide animator is running stop it and instantly make invisible
+ if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) {
+ mRotateHideAnimator.pause();
+ }
+ setRotateButtonVisibility(false);
+ return;
+ }
+
+ // Don't start any new hide animations if one is running
+ if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) return;
+
+ ObjectAnimator fadeOut = ObjectAnimator.ofFloat(currentView, "alpha",
+ 0f);
+ fadeOut.setDuration(BUTTON_FADE_IN_OUT_DURATION_MS);
+ fadeOut.setInterpolator(Interpolators.LINEAR);
+ fadeOut.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ setRotateButtonVisibility(false);
+ }
+ });
+
+ mRotateHideAnimator = fadeOut;
+ fadeOut.start();
+ }
+ }
+
+ private void setRotateButtonVisibility(final boolean visible) {
+ // Never show if a11y is visible
+ final boolean adjVisible = visible && !mShowAccessibilityButton;
+ final int vis = adjVisible ? View.VISIBLE : View.INVISIBLE;
+
+ getRotateSuggestionButton().setVisibility(vis);
+ mShowRotateButton = visible;
+
+ // Hide/restore other button visibility, if necessary
+ setNavigationIconHints(mNavigationIconHints, true);
+ }
+
@Override
public void onFinishInflate() {
mNavigationInflaterView = (NavigationBarInflaterView) findViewById(
@@ -568,6 +757,35 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
updateRotatedViews();
}
+ public void onDarkIntensityChange(float intensity) {
+ if (mGestureHelper != null) {
+ mGestureHelper.onDarkIntensityChange(intensity);
+ }
+ if (mRecentsOnboarding != null) {
+ mRecentsOnboarding.setContentDarkIntensity(intensity);
+ }
+ }
+
+ public void onOverviewProxyConnectionChanged(boolean isConnected) {
+ setSlippery(!isConnected);
+ setDisabledFlags(mDisabledFlags, true);
+ setUpSwipeUpOnboarding(isConnected);
+ updateIcons(getContext(), Configuration.EMPTY, mConfiguration);
+ setNavigationIconHints(mNavigationIconHints, true);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ mGestureHelper.onDraw(canvas);
+ super.onDraw(canvas);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ mGestureHelper.onLayout(changed, left, top, right, bottom);
+ }
+
private void updateRotatedViews() {
mRotatedViews[Surface.ROTATION_0] =
mRotatedViews[Surface.ROTATION_180] = findViewById(R.id.rot0);
@@ -664,6 +882,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
updateTaskSwitchHelper();
updateIcons(getContext(), mConfiguration, newConfig);
updateRecentsIcon();
+ mRecentsOnboarding.onConfigurationChanged(newConfig);
if (uiCarModeChanged || mConfiguration.densityDpi != newConfig.densityDpi
|| mConfiguration.getLayoutDirection() != newConfig.getLayoutDirection()) {
// If car mode or density changes, we need to reset the icons.
@@ -752,6 +971,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
onPluginDisconnected(null); // Create default gesture helper
Dependency.get(PluginManager.class).addPluginListener(this,
NavGesture.class, false /* Only one */);
+ setUpSwipeUpOnboarding(mOverviewProxyService.getProxy() != null);
}
@Override
@@ -761,6 +981,15 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
if (mGestureHelper != null) {
mGestureHelper.destroy();
}
+ setUpSwipeUpOnboarding(false);
+ }
+
+ private void setUpSwipeUpOnboarding(boolean connectedToOverviewProxy) {
+ if (connectedToOverviewProxy) {
+ mRecentsOnboarding.onConnectedToLauncher();
+ } else {
+ mRecentsOnboarding.onDisconnectedFromLauncher();
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index 0f246c621eb9..5cf4c4c70974 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -16,8 +16,6 @@
package com.android.systemui.statusbar.phone;
-import static com.android.systemui.statusbar.notification.NotificationUtils.isHapticFeedbackDisabled;
-
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Canvas;
@@ -102,8 +100,10 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
}.setDuration(200).setDelay(50);
public static final int MAX_VISIBLE_ICONS_WHEN_DARK = 5;
+ public static final int MAX_STATIC_ICONS = 4;
+ private static final int MAX_DOTS = 3;
- private boolean mShowAllIcons = true;
+ private boolean mIsStaticLayout = true;
private final HashMap<View, IconState> mIconStates = new HashMap<>();
private int mDotPadding;
private int mStaticDotRadius;
@@ -117,19 +117,17 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
private int mSpeedBumpIndex = -1;
private int mIconSize;
private float mOpenedAmount = 0.0f;
- private float mVisualOverflowAdaption;
private boolean mDisallowNextAnimation;
private boolean mAnimationsEnabled = true;
- private boolean mVibrateOnAnimation;
- private Vibrator mVibrator;
private ArrayMap<String, ArrayList<StatusBarIcon>> mReplacingIcons;
- private int mDarkOffsetX;
+ // Keep track of the last visible icon so collapsed container can report on its location
+ private IconState mLastVisibleIconState;
+
public NotificationIconContainer(Context context, AttributeSet attrs) {
super(context, attrs);
initDimens();
setWillNotDraw(!DEBUG);
- mVibrator = mContext.getSystemService(Vibrator.class);
}
private void initDimens() {
@@ -168,7 +166,7 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
mIconSize = child.getWidth();
}
}
- if (mShowAllIcons) {
+ if (mIsStaticLayout) {
resetViewStates();
calculateIconTranslations();
applyIconStates();
@@ -209,7 +207,7 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
mAddAnimationStartIndex = Math.min(mAddAnimationStartIndex, childIndex);
}
}
- if (mDark && child instanceof StatusBarIconView) {
+ if (child instanceof StatusBarIconView) {
((StatusBarIconView) child).setDark(mDark, false, 0);
}
}
@@ -292,7 +290,8 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
float translationX = getActualPaddingStart();
int firstOverflowIndex = -1;
int childCount = getChildCount();
- int maxVisibleIcons = mDark ? MAX_VISIBLE_ICONS_WHEN_DARK : childCount;
+ int maxVisibleIcons = mDark ? MAX_VISIBLE_ICONS_WHEN_DARK :
+ mIsStaticLayout ? MAX_STATIC_ICONS : childCount;
float layoutEnd = getLayoutEnd();
float overflowStart = layoutEnd - mIconSize * (2 + OVERFLOW_EARLY_AMOUNT);
boolean hasAmbient = mSpeedBumpIndex != -1 && mSpeedBumpIndex < getChildCount();
@@ -325,23 +324,6 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
visualOverflowStart += (translationX - overflowStart) / mIconSize
* (mStaticDotRadius * 2 + mDotPadding);
}
- if (mShowAllIcons) {
- // We want to perfectly position the overflow in the static state, such that
- // it's perfectly centered instead of measuring it from the end.
- mVisualOverflowAdaption = 0;
- if (firstOverflowIndex != -1) {
- View firstOverflowView = getChildAt(i);
- IconState overflowState = mIconStates.get(firstOverflowView);
- float totalAmount = layoutEnd - overflowState.xTranslation;
- float newPosition = overflowState.xTranslation + totalAmount / 2
- - totalDotLength / 2
- - mIconSize * 0.5f + mStaticDotRadius;
- mVisualOverflowAdaption = newPosition - visualOverflowStart;
- visualOverflowStart = newPosition;
- }
- } else {
- visualOverflowStart += mVisualOverflowAdaption * (1f - mOpenedAmount);
- }
}
translationX += iconState.iconAppearAmount * view.getWidth() * drawingScale;
}
@@ -353,20 +335,24 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
IconState iconState = mIconStates.get(view);
int dotWidth = mStaticDotRadius * 2 + mDotPadding;
iconState.xTranslation = translationX;
- if (numDots <= 3) {
+ if (numDots <= MAX_DOTS) {
if (numDots == 1 && iconState.iconAppearAmount < 0.8f) {
iconState.visibleState = StatusBarIconView.STATE_ICON;
numDots--;
} else {
iconState.visibleState = StatusBarIconView.STATE_DOT;
}
- translationX += (numDots == 3 ? 3 * dotWidth : dotWidth)
+ translationX += (numDots == MAX_DOTS ? MAX_DOTS * dotWidth : dotWidth)
* iconState.iconAppearAmount;
+ mLastVisibleIconState = iconState;
} else {
iconState.visibleState = StatusBarIconView.STATE_HIDDEN;
}
numDots++;
}
+ } else if (childCount > 0) {
+ View lastChild = getChildAt(childCount - 1);
+ mLastVisibleIconState = mIconStates.get(lastChild);
}
boolean center = mDark;
if (center && translationX < getLayoutEnd()) {
@@ -391,14 +377,6 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
iconState.xTranslation = getWidth() - iconState.xTranslation - view.getWidth();
}
}
-
- if (mDark && mDarkOffsetX != 0) {
- for (int i = 0; i < childCount; i++) {
- View view = getChildAt(i);
- IconState iconState = mIconStates.get(view);
- iconState.xTranslation += mDarkOffsetX;
- }
- }
}
private float getLayoutEnd() {
@@ -420,13 +398,13 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
}
/**
- * Sets whether the layout should always show all icons.
+ * Sets whether the layout should always show the same number of icons.
* If this is true, the icon positions will be updated on layout.
* If this if false, the layout is managed from the outside and layouting won't trigger a
* repositioning of the icons.
*/
- public void setShowAllIcons(boolean showAllIcons) {
- mShowAllIcons = showAllIcons;
+ public void setIsStaticLayout(boolean isStaticLayout) {
+ mIsStaticLayout = isStaticLayout;
}
public void setActualLayoutWidth(int actualLayoutWidth) {
@@ -457,6 +435,14 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
return mActualLayoutWidth;
}
+ public int getFinalTranslationX() {
+ if (mLastVisibleIconState == null) {
+ return 0;
+ }
+
+ return (int) (mLastVisibleIconState.xTranslation + mIconSize * (1 + OVERFLOW_EARLY_AMOUNT));
+ }
+
public void setChangingViewPositions(boolean changingViewPositions) {
mChangingViewPositions = changingViewPositions;
}
@@ -484,21 +470,41 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
mOpenedAmount = expandAmount;
}
- public float getVisualOverflowAdaption() {
- return mVisualOverflowAdaption;
- }
-
- public void setVisualOverflowAdaption(float visualOverflowAdaption) {
- mVisualOverflowAdaption = visualOverflowAdaption;
- }
-
public boolean hasOverflow() {
+ if (mIsStaticLayout) {
+ return getChildCount() > MAX_STATIC_ICONS;
+ }
+
float width = (getChildCount() + OVERFLOW_EARLY_AMOUNT) * mIconSize;
return width - (getWidth() - getActualPaddingStart() - getActualPaddingEnd()) > 0;
}
- public void setVibrateOnAnimation(boolean vibrateOnAnimation) {
- mVibrateOnAnimation = vibrateOnAnimation;
+ /**
+ * If the overflow is in the range [1, max_dots - 1) (basically 1 or 2 dots), then
+ * extra padding will have to be accounted for
+ *
+ * This method has no meaning for non-static containers
+ */
+ public boolean hasPartialOverflow() {
+ if (mIsStaticLayout) {
+ int count = getChildCount();
+ return count > MAX_STATIC_ICONS && count <= MAX_STATIC_ICONS + MAX_DOTS;
+ }
+
+ return false;
+ }
+
+ /**
+ * Get padding that can account for extra dots up to the max. The only valid values for
+ * this method are for 1 or 2 dots.
+ * @return only extraDotPadding or extraDotPadding * 2
+ */
+ public int getPartialOverflowExtraPadding() {
+ if (!hasPartialOverflow()) {
+ return 0;
+ }
+
+ return (MAX_STATIC_ICONS + MAX_DOTS - getChildCount()) * (mStaticDotRadius + mDotPadding);
}
public int getIconSize() {
@@ -519,10 +525,6 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
mAnimationsEnabled = enabled;
}
- public void setDarkOffsetX(int offsetX) {
- mDarkOffsetX = offsetX;
- }
-
public void setReplacingIcons(ArrayMap<String, ArrayList<StatusBarIcon>> replacingIcons) {
mReplacingIcons = replacingIcons;
}
@@ -608,39 +610,14 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
} else {
super.applyToView(view);
}
- boolean wasInShelf = icon.isInShelf();
boolean inShelf = iconAppearAmount == 1.0f;
icon.setIsInShelf(inShelf);
- if (shouldVibrateChange(wasInShelf != inShelf)) {
- AsyncTask.execute(
- () -> mVibrator.vibrate(VibrationEffect.get(
- VibrationEffect.EFFECT_TICK)));
- }
}
justAdded = false;
justReplaced = false;
needsCannedAnimation = false;
}
- private boolean shouldVibrateChange(boolean inShelfChanged) {
- if (!mVibrateOnAnimation) {
- return false;
- }
- if (justAdded) {
- return false;
- }
- if (!mAnimationsEnabled) {
- return false;
- }
- if (!inShelfChanged) {
- return false;
- }
- if (isHapticFeedbackDisabled(mContext)) {
- return false;
- }
- return true;
- }
-
public boolean hasCustomTransformHeight() {
return isLastExpandIcon && customTransformHeight != NO_VALUE;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index c19161839998..0e8fcbabf2ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.phone;
+import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
@@ -43,7 +45,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
-import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
import android.widget.FrameLayout;
import com.android.internal.logging.MetricsLogger;
@@ -65,8 +67,7 @@ import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.NotificationUtils;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
@@ -110,6 +111,7 @@ public class NotificationPanelView extends PanelView implements
}
};
private final PowerManager mPowerManager;
+ private final AccessibilityManager mAccessibilityManager;
private KeyguardAffordanceHelper mAffordanceHelper;
private KeyguardUserSwitcher mKeyguardUserSwitcher;
@@ -239,12 +241,17 @@ public class NotificationPanelView extends PanelView implements
private ValueAnimator mDarkAnimator;
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private boolean mUserSetupComplete;
+ private int mQsNotificationTopPadding;
+ private float mExpandOffset;
+ private boolean mHideIconsDuringNotificationLaunch = true;
public NotificationPanelView(Context context, AttributeSet attrs) {
super(context, attrs);
setWillNotDraw(!DEBUG);
mFalsingManager = FalsingManager.getInstance(context);
mPowerManager = context.getSystemService(PowerManager.class);
+ mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
+ setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
}
public void setStatusBar(StatusBar bar) {
@@ -307,6 +314,8 @@ public class NotificationPanelView extends PanelView implements
R.dimen.max_notification_fadeout_height);
mIndicationBottomPadding = getResources().getDimensionPixelSize(
R.dimen.keyguard_indication_bottom_padding);
+ mQsNotificationTopPadding = getResources().getDimensionPixelSize(
+ R.dimen.qs_notification_padding);
}
public void updateResources() {
@@ -330,7 +339,7 @@ public class NotificationPanelView extends PanelView implements
}
}
- public void onOverlayChanged() {
+ public void onThemeChanged() {
// Re-inflate the status view group.
int index = indexOfChild(mKeyguardStatusView);
removeView(mKeyguardStatusView);
@@ -448,11 +457,12 @@ public class NotificationPanelView extends PanelView implements
boolean animate = mNotificationStackScroller.isAddOrRemoveAnimationPending();
int stackScrollerPadding;
if (mStatusBarState != StatusBarState.KEYGUARD) {
- stackScrollerPadding = (mQs != null ? mQs.getHeader().getHeight() : 0) + mQsPeekHeight;
+ stackScrollerPadding = (mQs != null ? mQs.getHeader().getHeight() : 0) + mQsPeekHeight
+ + mQsNotificationTopPadding;
mTopPaddingAdjustment = 0;
} else {
mClockPositionAlgorithm.setup(
- mStatusBar.getMaxKeyguardNotifications(),
+ mStatusBar.getMaxNotificationsWhileLocked(),
getMaxPanelHeight(),
getExpandedHeight(),
mNotificationStackScroller.getNotGoneChildCount(),
@@ -473,7 +483,8 @@ public class NotificationPanelView extends PanelView implements
mTopPaddingAdjustment = mClockPositionResult.stackScrollerPaddingAdjustment;
}
mNotificationStackScroller.setIntrinsicPadding(stackScrollerPadding);
- mNotificationStackScroller.setDarkShelfOffsetX(mClockPositionResult.clockX);
+ mNotificationStackScroller.setAntiBurnInOffsetX(mClockPositionResult.clockX);
+ mKeyguardBottomArea.setBurnInXOffset(mClockPositionResult.clockX);
requestScrollerTopPaddingUpdate(animate);
}
@@ -503,7 +514,8 @@ public class NotificationPanelView extends PanelView implements
if (suppressedSummary) {
continue;
}
- if (!mStatusBar.shouldShowOnKeyguard(row.getStatusBarNotification())) {
+ if (!mStatusBar.getNotificationLockscreenUserManager().shouldShowOnKeyguard(
+ row.getStatusBarNotification())) {
continue;
}
if (row.isRemoved()) {
@@ -596,7 +608,7 @@ public class NotificationPanelView extends PanelView implements
mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE;
}
closeQs();
- mStatusBar.closeAndSaveGuts(true /* leavebehind */, true /* force */,
+ mStatusBar.getGutsManager().closeAndSaveGuts(true /* leavebehind */, true /* force */,
true /* controls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
mNotificationStackScroller.setOverScrollAmount(0f, true /* onTop */, false /* animate */,
true /* cancelAnimators */);
@@ -652,22 +664,12 @@ public class NotificationPanelView extends PanelView implements
}
@Override
- public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
- if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
- event.getText().add(getKeyguardOrLockScreenString());
- mLastAnnouncementWasQuickSettings = false;
- return true;
- }
- return super.dispatchPopulateAccessibilityEventInternal(event);
- }
-
- @Override
public boolean onInterceptTouchEvent(MotionEvent event) {
- if (mBlockTouches || mQs.isCustomizing()) {
+ if (mBlockTouches || mQsFullyExpanded && mQs.onInterceptTouchEvent(event)) {
return false;
}
initDownStates(event);
- if (mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
+ if (mBar.panelEnabled() && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
mIsExpansionFromHeadsUp = true;
MetricsLogger.count(mContext, COUNTER_PANEL_OPEN, 1);
MetricsLogger.count(mContext, COUNTER_PANEL_OPEN_PEEK, 1);
@@ -704,7 +706,7 @@ public class NotificationPanelView extends PanelView implements
mInitialHeightOnTouch = mQsExpansionHeight;
mQsTracking = true;
mIntercepting = false;
- mNotificationStackScroller.removeLongPressCallback();
+ mNotificationStackScroller.cancelLongPress();
}
break;
case MotionEvent.ACTION_POINTER_UP:
@@ -740,7 +742,7 @@ public class NotificationPanelView extends PanelView implements
mInitialTouchY = y;
mInitialTouchX = x;
mIntercepting = false;
- mNotificationStackScroller.removeLongPressCallback();
+ mNotificationStackScroller.cancelLongPress();
return true;
}
break;
@@ -818,7 +820,7 @@ public class NotificationPanelView extends PanelView implements
private float getQsExpansionFraction() {
return Math.min(1f, (mQsExpansionHeight - mQsMinExpansionHeight)
- / (getTempQsMaxExpansion() - mQsMinExpansionHeight));
+ / (mQsMaxExpansionHeight - mQsMinExpansionHeight));
}
@Override
@@ -1291,10 +1293,6 @@ public class NotificationPanelView extends PanelView implements
setQsExpanded(true);
} else if (height <= mQsMinExpansionHeight && mQsExpanded) {
setQsExpanded(false);
- if (mLastAnnouncementWasQuickSettings && !mTracking && !isCollapsing()) {
- announceForAccessibility(getKeyguardOrLockScreenString());
- mLastAnnouncementWasQuickSettings = false;
- }
}
mQsExpansionHeight = height;
updateQsExpansion();
@@ -1320,13 +1318,10 @@ public class NotificationPanelView extends PanelView implements
updateClock(mClockPositionResult.clockAlpha, mClockPositionResult.clockScale);
}
- // Upon initialisation when we are not layouted yet we don't want to announce that we are
- // fully expanded, hence the != 0.0f check.
- if (height != 0.0f && mQsFullyExpanded && !mLastAnnouncementWasQuickSettings) {
- announceForAccessibility(getContext().getString(
- R.string.accessibility_desc_quick_settings));
- mLastAnnouncementWasQuickSettings = true;
+ if (mAccessibilityManager.isEnabled()) {
+ setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
}
+
if (mQsFullyExpanded && mFalsingManager.shouldEnforceBouncer()) {
mStatusBar.executeRunnableDismissingKeyguard(null, null /* cancelAction */,
false /* dismissShade */, true /* afterKeyguardGone */, false /* deferred */);
@@ -1341,9 +1336,13 @@ public class NotificationPanelView extends PanelView implements
mQs.setQsExpansion(getQsExpansionFraction(), getHeaderTranslation());
}
- private String getKeyguardOrLockScreenString() {
+ private String determineAccessibilityPaneTitle() {
if (mQs != null && mQs.isCustomizing()) {
return getContext().getString(R.string.accessibility_desc_quick_settings_edit);
+ } else if (mQsExpansionHeight != 0.0f && mQsFullyExpanded) {
+ // Upon initialisation when we are not layouted yet we don't want to announce that we
+ // are fully expanded, hence the != 0.0f check.
+ return getContext().getString(R.string.accessibility_desc_quick_settings);
} else if (mStatusBarState == StatusBarState.KEYGUARD) {
return getContext().getString(R.string.accessibility_desc_lock_screen);
} else {
@@ -1361,7 +1360,7 @@ public class NotificationPanelView extends PanelView implements
// take the maximum and linearly interpolate with the panel expansion for a nice motion.
int maxNotifications = mClockPositionResult.stackScrollerPadding
- mClockPositionResult.stackScrollerPaddingAdjustment;
- int maxQs = getTempQsMaxExpansion();
+ int maxQs = mQsMaxExpansionHeight + mQsNotificationTopPadding;
int max = mStatusBarState == StatusBarState.KEYGUARD
? Math.max(maxNotifications, maxQs)
: maxQs;
@@ -1375,9 +1374,9 @@ public class NotificationPanelView extends PanelView implements
// from a scrolled quick settings.
return interpolate(getQsExpansionFraction(),
mNotificationStackScroller.getIntrinsicPadding(),
- mQsMaxExpansionHeight);
+ mQsMaxExpansionHeight + mQsNotificationTopPadding);
} else {
- return mQsExpansionHeight;
+ return mQsExpansionHeight + mQsNotificationTopPadding;
}
}
@@ -1544,7 +1543,7 @@ public class NotificationPanelView extends PanelView implements
/ (panelHeightQsExpanded - panelHeightQsCollapsed);
}
setQsExpansion(mQsMinExpansionHeight
- + t * (getTempQsMaxExpansion() - mQsMinExpansionHeight));
+ + t * (mQsMaxExpansionHeight - mQsMinExpansionHeight));
}
updateExpandedHeight(expandedHeight);
updateHeader();
@@ -1560,20 +1559,12 @@ public class NotificationPanelView extends PanelView implements
private void updatePanelExpanded() {
boolean isExpanded = !isFullyCollapsed();
if (mPanelExpanded != isExpanded) {
- mHeadsUpManager.setIsExpanded(isExpanded);
+ mHeadsUpManager.setIsPanelExpanded(isExpanded);
mStatusBar.setPanelExpanded(isExpanded);
mPanelExpanded = isExpanded;
}
}
- /**
- * @return a temporary override of {@link #mQsMaxExpansionHeight}, which is needed when
- * collapsing QS / the panel when QS was scrolled
- */
- private int getTempQsMaxExpansion() {
- return mQsMaxExpansionHeight;
- }
-
private int calculatePanelHeightShade() {
int emptyBottomMargin = mNotificationStackScroller.getEmptyBottomMargin();
int maxHeight = mNotificationStackScroller.getHeight() - emptyBottomMargin
@@ -1596,6 +1587,10 @@ public class NotificationPanelView extends PanelView implements
}
int maxQsHeight = mQsMaxExpansionHeight;
+ if (mKeyguardShowing) {
+ maxQsHeight += mQsNotificationTopPadding;
+ }
+
// If an animation is changing the size of the QS panel, take the animated value.
if (mQsSizeChangeAnimator != null) {
maxQsHeight = (int) mQsSizeChangeAnimator.getAnimatedValue();
@@ -1672,8 +1667,9 @@ public class NotificationPanelView extends PanelView implements
if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
return 0;
}
- float translation = NotificationUtils.interpolate(-mQsMinExpansionHeight, 0,
- mNotificationStackScroller.getAppearFraction(mExpandedHeight));
+ float translation = MathUtils.lerp(-mQsMinExpansionHeight, 0,
+ Math.min(1.0f, mNotificationStackScroller.getAppearFraction(mExpandedHeight)))
+ + mExpandOffset;
return Math.min(0, translation);
}
@@ -1874,6 +1870,9 @@ public class NotificationPanelView extends PanelView implements
requestScrollerTopPaddingUpdate(false /* animate */);
requestPanelHeightUpdate();
}
+ if (mAccessibilityManager.isEnabled()) {
+ setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
+ }
}
@Override
@@ -2330,7 +2329,7 @@ public class NotificationPanelView extends PanelView implements
}
@Override
- public void setHeadsUpManager(HeadsUpManager headsUpManager) {
+ public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
super.setHeadsUpManager(headsUpManager);
mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager, mNotificationStackScroller,
this);
@@ -2537,6 +2536,9 @@ public class NotificationPanelView extends PanelView implements
}
public boolean hideStatusBarIconsWhenExpanded() {
+ if (mLaunchingNotification) {
+ return mHideIconsDuringNotificationLaunch;
+ }
return !isFullWidth() || !mShowIconsWhenExpanded;
}
@@ -2607,7 +2609,8 @@ public class NotificationPanelView extends PanelView implements
private void setDarkAmount(float amount) {
mDarkAmount = amount;
- mKeyguardStatusView.setDark(mDarkAmount);
+ mKeyguardStatusView.setDarkAmount(mDarkAmount);
+ mKeyguardBottomArea.setDarkAmount(mDarkAmount);
positionClockAndNotifications();
}
@@ -2620,6 +2623,9 @@ public class NotificationPanelView extends PanelView implements
public void setPulsing(boolean pulsing) {
mKeyguardStatusView.setPulsing(pulsing);
+ positionClockAndNotifications();
+ mNotificationStackScroller.setPulsing(pulsing, mKeyguardStatusView.getLocationOnScreen()[1]
+ + mKeyguardStatusView.getClockBottom());
}
public void setAmbientIndicationBottomPadding(int ambientIndicationBottomPadding) {
@@ -2629,8 +2635,9 @@ public class NotificationPanelView extends PanelView implements
}
}
- public void refreshTime() {
+ public void dozeTimeTick() {
mKeyguardStatusView.refreshTime();
+ mKeyguardBottomArea.dozeTimeTick();
if (mDarkAmount > 0) {
positionClockAndNotifications();
}
@@ -2658,4 +2665,19 @@ public class NotificationPanelView extends PanelView implements
public LockIcon getLockIcon() {
return mKeyguardBottomArea.getLockIcon();
}
+
+ public void applyExpandAnimationParams(ExpandAnimationParameters params) {
+ mExpandOffset = params != null ? params.getTopChange() : 0;
+ updateQsExpansion();
+ if (params != null) {
+ boolean hideIcons = params.getProgress(
+ ActivityLaunchAnimator.ANIMATION_DELAY_ICON_FADE_IN, 100) == 0.0f;
+ if (hideIcons != mHideIconsDuringNotificationLaunch) {
+ mHideIconsDuringNotificationLaunch = hideIcons;
+ if (!hideIcons) {
+ mStatusBar.recomputeDisableFlags(true /* animate */);
+ }
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index afe5c917a856..6daabede7f32 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -16,8 +16,6 @@
package com.android.systemui.statusbar.phone;
-import static com.android.systemui.statusbar.notification.NotificationUtils.isHapticFeedbackDisabled;
-
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
@@ -25,7 +23,9 @@ import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.database.ContentObserver;
import android.os.AsyncTask;
+import android.os.Handler;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.VibrationEffect;
@@ -42,7 +42,7 @@ import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.keyguard.LatencyTracker;
+import com.android.internal.util.LatencyTracker;
import com.android.systemui.DejankUtils;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
@@ -50,8 +50,7 @@ import com.android.systemui.classifier.FalsingManager;
import com.android.systemui.doze.DozeLog;
import com.android.systemui.statusbar.FlingAnimationUtils;
import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.NotificationUtils;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -61,18 +60,22 @@ public abstract class PanelView extends FrameLayout {
public static final String TAG = PanelView.class.getSimpleName();
private static final int INITIAL_OPENING_PEEK_DURATION = 200;
private static final int PEEK_ANIMATION_DURATION = 360;
+ private static final int NO_FIXED_DURATION = -1;
private long mDownTime;
private float mMinExpandHeight;
private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
private boolean mPanelUpdateWhenAnimatorEnds;
private boolean mVibrateOnOpening;
+ private boolean mVibrationEnabled;
+ protected boolean mLaunchingNotification;
+ private int mFixedDuration = NO_FIXED_DURATION;
private final void logf(String fmt, Object... args) {
Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
}
protected StatusBar mStatusBar;
- protected HeadsUpManager mHeadsUpManager;
+ protected HeadsUpManagerPhone mHeadsUpManager;
private float mPeekHeight;
private float mHintDistance;
@@ -108,6 +111,12 @@ public abstract class PanelView extends FrameLayout {
private FlingAnimationUtils mFlingAnimationUtilsDismissing;
private FalsingManager mFalsingManager;
private final Vibrator mVibrator;
+ final private ContentObserver mVibrationObserver = new ContentObserver(Handler.getMain()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ updateHapticFeedBackEnabled();
+ }
+ };
/**
* Whether an instant expand request is currently pending and we are just waiting for layout.
@@ -212,6 +221,15 @@ public abstract class PanelView extends FrameLayout {
mVibrator = mContext.getSystemService(Vibrator.class);
mVibrateOnOpening = mContext.getResources().getBoolean(
R.bool.config_vibrateOnIconAnimation);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.System.getUriFor(Settings.System.HAPTIC_FEEDBACK_ENABLED), true,
+ mVibrationObserver);
+ mVibrationObserver.onChange(false /* selfChange */);
+ }
+
+ public void updateHapticFeedBackEnabled() {
+ mVibrationEnabled = Settings.System.getIntForUser(mContext.getContentResolver(),
+ Settings.System.HAPTIC_FEEDBACK_ENABLED, 0, UserHandle.USER_CURRENT) != 0;
}
protected void loadDimens() {
@@ -403,7 +421,7 @@ public abstract class PanelView extends FrameLayout {
runPeekAnimation(INITIAL_OPENING_PEEK_DURATION, getOpeningHeight(),
false /* collapseWhenFinished */);
notifyBarPanelExpansionChanged();
- if (mVibrateOnOpening && !isHapticFeedbackDisabled(mContext)) {
+ if (mVibrateOnOpening && mVibrationEnabled) {
AsyncTask.execute(() ->
mVibrator.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_TICK, false)));
}
@@ -770,6 +788,9 @@ public abstract class PanelView extends FrameLayout {
if (vel == 0) {
animator.setDuration((long) (animator.getDuration() / collapseSpeedUpFactor));
}
+ if (mFixedDuration != NO_FIXED_DURATION) {
+ animator.setDuration(mFixedDuration);
+ }
}
animator.addListener(new AnimatorListenerAdapter() {
private boolean mCancelled;
@@ -929,7 +950,7 @@ public abstract class PanelView extends FrameLayout {
}
public boolean isCollapsing() {
- return mClosing;
+ return mClosing || mLaunchingNotification;
}
public boolean isTracking() {
@@ -1231,7 +1252,17 @@ public abstract class PanelView extends FrameLayout {
*/
protected abstract int getClearAllHeight();
- public void setHeadsUpManager(HeadsUpManager headsUpManager) {
+ public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
mHeadsUpManager = headsUpManager;
}
+
+ public void setLaunchingNotification(boolean launchingNotification) {
+ mLaunchingNotification = launchingNotification;
+ }
+
+ public void collapseWithDuration(int animationDuration) {
+ mFixedDuration = animationDuration;
+ collapse(false /* delayed */, 1.0f /* speedUpFactor */);
+ mFixedDuration = NO_FIXED_DURATION;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 4ae13936d1eb..6444cc816663 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -16,8 +16,12 @@
package com.android.systemui.statusbar.phone;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+
import android.app.ActivityManager;
-import android.app.ActivityManager.StackId;
import android.app.ActivityManager.StackInfo;
import android.app.AlarmManager;
import android.app.AlarmManager.AlarmClockInfo;
@@ -35,7 +39,6 @@ import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
import android.graphics.drawable.Icon;
import android.media.AudioManager;
import android.net.Uri;
@@ -61,8 +64,8 @@ import com.android.systemui.SysUiServiceProvider;
import com.android.systemui.UiOffloadThread;
import com.android.systemui.qs.tiles.DndTile;
import com.android.systemui.qs.tiles.RotationLockTile;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.CommandQueue.Callbacks;
import com.android.systemui.statusbar.policy.BluetoothController;
@@ -141,7 +144,6 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks,
private boolean mDockedStackExists;
private boolean mManagedProfileIconVisible = false;
- private boolean mManagedProfileInQuietMode = false;
private BluetoothController mBluetooth;
@@ -245,7 +247,7 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks,
mLocationController.addCallback(this);
SysUiServiceProvider.getComponent(mContext, CommandQueue.class).addCallbacks(this);
- SystemServicesProxy.getInstance(mContext).registerTaskStackListener(mTaskListener);
+ ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskListener);
// Clear out all old notifications on startup (only present in the case where sysui dies)
NotificationManager noMan = mContext.getSystemService(NotificationManager.class);
@@ -405,17 +407,17 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks,
int iconId = R.drawable.stat_sys_data_bluetooth;
String contentDescription =
mContext.getString(R.string.accessibility_quick_settings_bluetooth_on);
- boolean bluetoothEnabled = false;
+ boolean bluetoothVisible = false;
if (mBluetooth != null) {
- bluetoothEnabled = mBluetooth.isBluetoothEnabled();
if (mBluetooth.isBluetoothConnected()) {
iconId = R.drawable.stat_sys_data_bluetooth_connected;
contentDescription = mContext.getString(R.string.accessibility_bluetooth_connected);
+ bluetoothVisible = mBluetooth.isBluetoothEnabled();
}
}
mIconController.setIcon(mSlotBluetooth, iconId, contentDescription);
- mIconController.setIconVisibility(mSlotBluetooth, bluetoothEnabled);
+ mIconController.setIconVisibility(mSlotBluetooth, bluetoothVisible);
}
private final void updateTTY() {
@@ -469,17 +471,6 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks,
}
}
- private void updateQuietState() {
- mManagedProfileInQuietMode = false;
- int currentUserId = ActivityManager.getCurrentUser();
- for (UserInfo ui : mUserManager.getEnabledProfiles(currentUserId)) {
- if (ui.isManagedProfile() && ui.isQuietModeEnabled()) {
- mManagedProfileInQuietMode = true;
- return;
- }
- }
- }
-
private void updateManagedProfile() {
// getLastResumedActivityUserId needds to acquire the AM lock, which may be contended in
// some cases. Since it doesn't really matter here whether it's updated in this frame
@@ -497,11 +488,6 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks,
mIconController.setIcon(mSlotManagedProfile,
R.drawable.stat_sys_managed_profile_status,
mContext.getString(R.string.accessibility_managed_profile));
- } else if (mManagedProfileInQuietMode) {
- showIcon = true;
- mIconController.setIcon(mSlotManagedProfile,
- R.drawable.stat_sys_managed_profile_status_off,
- mContext.getString(R.string.accessibility_managed_profile));
} else {
showIcon = false;
}
@@ -523,12 +509,18 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks,
mCurrentNotifs.clear();
mUiOffloadThread.submit(() -> {
try {
- int focusedId = ActivityManager.getService().getFocusedStackId();
- if (focusedId == StackId.FULLSCREEN_WORKSPACE_STACK_ID) {
- checkStack(StackId.FULLSCREEN_WORKSPACE_STACK_ID, notifs, noMan, pm);
+ final StackInfo focusedStack = ActivityManager.getService().getFocusedStackInfo();
+ if (focusedStack != null) {
+ final int windowingMode =
+ focusedStack.configuration.windowConfiguration.getWindowingMode();
+ if (windowingMode == WINDOWING_MODE_FULLSCREEN
+ || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
+ checkStack(focusedStack, notifs, noMan, pm);
+ }
}
if (mDockedStackExists) {
- checkStack(StackId.DOCKED_STACK_ID, notifs, noMan, pm);
+ checkStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED,
+ notifs, noMan, pm);
}
} catch (RemoteException e) {
e.rethrowFromSystemServer();
@@ -539,10 +531,19 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks,
});
}
- private void checkStack(int stackId, ArraySet<Pair<String, Integer>> notifs,
+ private void checkStack(int windowingMode, int activityType,
+ ArraySet<Pair<String, Integer>> notifs, NotificationManager noMan, IPackageManager pm) {
+ try {
+ final StackInfo info =
+ ActivityManager.getService().getStackInfo(windowingMode, activityType);
+ checkStack(info, notifs, noMan, pm);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ private void checkStack(StackInfo info, ArraySet<Pair<String, Integer>> notifs,
NotificationManager noMan, IPackageManager pm) {
try {
- StackInfo info = ActivityManager.getService().getStackInfo(stackId);
if (info == null || info.topActivity == null) return;
String pkg = info.topActivity.getPackageName();
if (!hasNotif(notifs, pkg, info.userId)) {
@@ -595,6 +596,7 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks,
.addCategory(Intent.CATEGORY_BROWSABLE)
.addCategory("unique:" + System.currentTimeMillis())
.putExtra(Intent.EXTRA_PACKAGE_NAME, appInfo.packageName)
+ .putExtra(Intent.EXTRA_VERSION_CODE, (int) (appInfo.versionCode & 0x7fffffff))
.putExtra(Intent.EXTRA_VERSION_CODE, appInfo.versionCode)
.putExtra(Intent.EXTRA_EPHEMERAL_FAILURE, pendingIntent);
@@ -620,12 +622,17 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks,
}
private Intent getTaskIntent(int taskId, int userId) {
- List<ActivityManager.RecentTaskInfo> tasks = mContext.getSystemService(ActivityManager.class)
- .getRecentTasksForUser(NUM_TASKS_FOR_INSTANT_APP_INFO, 0, userId);
- for (int i = 0; i < tasks.size(); i++) {
- if (tasks.get(i).id == taskId) {
- return tasks.get(i).baseIntent;
+ try {
+ final List<ActivityManager.RecentTaskInfo> tasks =
+ ActivityManager.getService().getRecentTasks(
+ NUM_TASKS_FOR_INSTANT_APP_INFO, 0, userId).getList();
+ for (int i = 0; i < tasks.size(); i++) {
+ if (tasks.get(i).id == taskId) {
+ return tasks.get(i).baseIntent;
+ }
}
+ } catch (RemoteException e) {
+ // Fall through
}
return null;
}
@@ -650,7 +657,6 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks,
public void onUserSwitchComplete(int newUserId) throws RemoteException {
mHandler.post(() -> {
updateAlarm();
- updateQuietState();
updateManagedProfile();
updateForegroundInstantApps();
});
@@ -659,7 +665,7 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks,
private final HotspotController.Callback mHotspotCallback = new HotspotController.Callback() {
@Override
- public void onHotspotChanged(boolean enabled) {
+ public void onHotspotChanged(boolean enabled, int numDevices) {
mIconController.setIconVisibility(mSlotHotspot, enabled);
}
};
@@ -698,7 +704,6 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks,
if (mCurrentUserSetup == userSetup) return;
mCurrentUserSetup = userSetup;
updateAlarm();
- updateQuietState();
}
@Override
@@ -744,7 +749,7 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks,
mIconController.setIconVisibility(mSlotDataSaver, isDataSaving);
}
- private final TaskStackListener mTaskListener = new TaskStackListener() {
+ private final SysUiTaskStackChangeListener mTaskListener = new SysUiTaskStackChangeListener() {
@Override
public void onTaskStackChanged() {
// Listen for changes to stacks and then check which instant apps are foreground.
@@ -767,7 +772,6 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks,
} else if (action.equals(Intent.ACTION_MANAGED_PROFILE_AVAILABLE) ||
action.equals(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE) ||
action.equals(Intent.ACTION_MANAGED_PROFILE_REMOVED)) {
- updateQuietState();
updateManagedProfile();
} else if (action.equals(AudioManager.ACTION_HEADSET_PLUG)) {
updateHeadsetPlug(intent);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 970d1de251d5..cc5a93ce71b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -16,26 +16,34 @@
package com.android.systemui.statusbar.phone;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+
+import android.annotation.Nullable;
import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.EventLog;
+import android.view.DisplayCutout;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
-import com.android.systemui.BatteryMeterView;
-import com.android.systemui.DejankUtils;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
import com.android.systemui.Dependency;
import com.android.systemui.EventLogTags;
import com.android.systemui.R;
import com.android.systemui.statusbar.policy.DarkIconDispatcher;
import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.util.leak.RotationUtils;
public class PhoneStatusBarView extends PanelBar {
private static final String TAG = "PhoneStatusBarView";
private static final boolean DEBUG = StatusBar.DEBUG;
private static final boolean DEBUG_GESTURES = false;
+ private static final int NO_VALUE = Integer.MIN_VALUE;
StatusBar mBar;
@@ -53,6 +61,11 @@ public class PhoneStatusBarView extends PanelBar {
}
};
private DarkReceiver mBattery;
+ private int mLastOrientation;
+ @Nullable
+ private View mCutoutSpace;
+ @Nullable
+ private DisplayCutout mDisplayCutout;
public PhoneStatusBarView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -76,6 +89,7 @@ public class PhoneStatusBarView extends PanelBar {
public void onFinishInflate() {
mBarTransitions.init();
mBattery = findViewById(R.id.battery);
+ mCutoutSpace = findViewById(R.id.cutout_space_view);
}
@Override
@@ -83,12 +97,51 @@ public class PhoneStatusBarView extends PanelBar {
super.onAttachedToWindow();
// Always have Battery meters in the status bar observe the dark/light modes.
Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mBattery);
+ if (updateOrientationAndCutout(getResources().getConfiguration().orientation)) {
+ postUpdateLayoutForCutout();
+ }
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
Dependency.get(DarkIconDispatcher.class).removeDarkReceiver(mBattery);
+ mDisplayCutout = null;
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+
+ // May trigger cutout space layout-ing
+ if (updateOrientationAndCutout(newConfig.orientation)) {
+ postUpdateLayoutForCutout();
+ }
+ }
+
+ /**
+ *
+ * @param newOrientation may pass NO_VALUE for no change
+ * @return boolean indicating if we need to update the cutout location / margins
+ */
+ private boolean updateOrientationAndCutout(int newOrientation) {
+ boolean changed = false;
+ if (newOrientation != NO_VALUE) {
+ if (mLastOrientation != newOrientation) {
+ changed = true;
+ mLastOrientation = newOrientation;
+ }
+ }
+
+ if (mDisplayCutout == null) {
+ DisplayCutout cutout = getRootWindowInsets().getDisplayCutout();
+ if (cutout != null) {
+ changed = true;
+ mDisplayCutout = cutout;
+ }
+ }
+
+ return changed;
}
@Override
@@ -197,6 +250,9 @@ public class PhoneStatusBarView extends PanelBar {
super.panelExpansionChanged(frac, expanded);
mPanelFraction = frac;
updateScrimFraction();
+ if ((frac == 0 || frac == 1) && mBar.getNavigationBarView() != null) {
+ mBar.getNavigationBarView().onPanelExpandedChange(expanded);
+ }
}
private void updateScrimFraction() {
@@ -214,4 +270,80 @@ public class PhoneStatusBarView extends PanelBar {
R.dimen.status_bar_height);
setLayoutParams(layoutParams);
}
+
+ private void updateLayoutForCutout() {
+ updateCutoutLocation();
+ updateSafeInsets();
+ }
+
+ private void postUpdateLayoutForCutout() {
+ Runnable r = new Runnable() {
+ @Override
+ public void run() {
+ updateLayoutForCutout();
+ }
+ };
+ // Let the cutout emulation draw first
+ postDelayed(r, 0);
+ }
+
+ private void updateCutoutLocation() {
+ // Not all layouts have a cutout (e.g., Car)
+ if (mCutoutSpace == null) {
+ return;
+ }
+
+ if (mDisplayCutout == null || mDisplayCutout.isEmpty()
+ || mLastOrientation != ORIENTATION_PORTRAIT) {
+ mCutoutSpace.setVisibility(View.GONE);
+ return;
+ }
+
+ mCutoutSpace.setVisibility(View.VISIBLE);
+ LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mCutoutSpace.getLayoutParams();
+ lp.width = mDisplayCutout.getBoundingRect().width();
+ lp.height = mDisplayCutout.getBoundingRect().height();
+ }
+
+ private void updateSafeInsets() {
+ // Depending on our rotation, we may have to work around a cutout in the middle of the view,
+ // or letterboxing from the right or left sides.
+
+ FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
+ if (mDisplayCutout == null || mDisplayCutout.isEmpty()) {
+ lp.leftMargin = 0;
+ lp.rightMargin = 0;
+ return;
+ }
+
+ int leftMargin = 0;
+ int rightMargin = 0;
+ switch (RotationUtils.getRotation(getContext())) {
+ /*
+ * Landscape: <-|
+ * Seascape: |->
+ */
+ case RotationUtils.ROTATION_LANDSCAPE:
+ leftMargin = getDisplayCutoutHeight();
+ break;
+ case RotationUtils.ROTATION_SEASCAPE:
+ rightMargin = getDisplayCutoutHeight();
+ break;
+ default:
+ break;
+ }
+
+ lp.leftMargin = leftMargin;
+ lp.rightMargin = rightMargin;
+ }
+
+ //TODO: Find a better way
+ private int getDisplayCutoutHeight() {
+ if (mDisplayCutout == null || mDisplayCutout.isEmpty()) {
+ return 0;
+ }
+
+ Rect r = mDisplayCutout.getBoundingRect();
+ return r.bottom - r.top;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubController.java
new file mode 100644
index 000000000000..dc0835e4371a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubController.java
@@ -0,0 +1,431 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ArgbEvaluator;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.util.Log;
+import android.util.Slog;
+import android.view.Display;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.support.annotation.DimenRes;
+import com.android.systemui.Dependency;
+import com.android.systemui.OverviewProxyService;
+import com.android.systemui.R;
+import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper;
+import com.android.systemui.shared.recents.IOverviewProxy;
+import com.android.systemui.shared.recents.utilities.Utilities;
+
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_BOTTOM;
+import static com.android.systemui.OverviewProxyService.DEBUG_OVERVIEW_PROXY;
+import static com.android.systemui.OverviewProxyService.TAG_OPS;
+
+/**
+ * Class to detect gestures on the navigation bar and implement quick scrub and switch.
+ */
+public class QuickScrubController extends GestureDetector.SimpleOnGestureListener implements
+ GestureHelper {
+
+ private static final String TAG = "QuickScrubController";
+ private static final int QUICK_SWITCH_FLING_VELOCITY = 0;
+ private static final int ANIM_DURATION_MS = 200;
+ private static final long LONG_PRESS_DELAY_MS = 225;
+
+ /**
+ * For quick step, set a damping value to allow the button to stick closer its origin position
+ * when dragging before quick scrub is active.
+ */
+ private static final int SWITCH_STICKINESS = 4;
+
+ private NavigationBarView mNavigationBarView;
+ private GestureDetector mGestureDetector;
+
+ private boolean mDraggingActive;
+ private boolean mQuickScrubActive;
+ private boolean mAllowQuickSwitch;
+ private float mDownOffset;
+ private float mTranslation;
+ private int mTouchDownX;
+ private int mTouchDownY;
+ private boolean mDragPositive;
+ private boolean mIsVertical;
+ private boolean mIsRTL;
+ private float mTrackAlpha;
+ private int mLightTrackColor;
+ private int mDarkTrackColor;
+ private float mDarkIntensity;
+
+ private final Handler mHandler = new Handler();
+ private final Interpolator mQuickScrubEndInterpolator = new DecelerateInterpolator();
+ private final Rect mTrackRect = new Rect();
+ private final Rect mHomeButtonRect = new Rect();
+ private final Paint mTrackPaint = new Paint();
+ private final int mScrollTouchSlop;
+ private final OverviewProxyService mOverviewEventSender;
+ private final int mTrackThickness;
+ private final int mTrackPadding;
+ private final ValueAnimator mTrackAnimator;
+ private final ValueAnimator mButtonAnimator;
+ private final AnimatorSet mQuickScrubEndAnimator;
+ private final Context mContext;
+ private final ArgbEvaluator mTrackColorEvaluator = new ArgbEvaluator();
+
+ private final AnimatorUpdateListener mTrackAnimatorListener = valueAnimator -> {
+ mTrackAlpha = (float) valueAnimator.getAnimatedValue();
+ mNavigationBarView.invalidate();
+ };
+
+ private final AnimatorUpdateListener mButtonTranslationListener = animator -> {
+ int pos = (int) animator.getAnimatedValue();
+ if (!mQuickScrubActive) {
+ pos = mDragPositive ? Math.min((int) mTranslation, pos) : Math.max((int) mTranslation, pos);
+ }
+ final View homeView = mNavigationBarView.getHomeButton().getCurrentView();
+ if (mIsVertical) {
+ homeView.setTranslationY(pos);
+ } else {
+ homeView.setTranslationX(pos);
+ }
+ };
+
+ private AnimatorListenerAdapter mQuickScrubEndListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mNavigationBarView.getHomeButton().setClickable(true);
+ mQuickScrubActive = false;
+ mTranslation = 0;
+ }
+ };
+
+ private Runnable mLongPressRunnable = this::startQuickScrub;
+
+ private final GestureDetector.SimpleOnGestureListener mGestureListener =
+ new GestureDetector.SimpleOnGestureListener() {
+ @Override
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velX, float velY) {
+ if (!isQuickScrubEnabled() || mQuickScrubActive || !mAllowQuickSwitch ||
+ !mHomeButtonRect.contains(mTouchDownX, mTouchDownY)) {
+ return false;
+ }
+ float velocityX = mIsRTL ? -velX : velX;
+ float absVelY = Math.abs(velY);
+ final boolean isValidFling = velocityX > QUICK_SWITCH_FLING_VELOCITY &&
+ mIsVertical ? (absVelY > velocityX) : (velocityX > absVelY);
+ if (isValidFling) {
+ mDraggingActive = false;
+ mButtonAnimator.setIntValues((int) mTranslation, 0);
+ mButtonAnimator.start();
+ mHandler.removeCallbacks(mLongPressRunnable);
+ try {
+ final IOverviewProxy overviewProxy = mOverviewEventSender.getProxy();
+ overviewProxy.onQuickSwitch();
+ if (DEBUG_OVERVIEW_PROXY) {
+ Log.d(TAG_OPS, "Quick Switch");
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to send start of quick switch.", e);
+ }
+ return true;
+ }
+ return false;
+ }
+ };
+
+ public QuickScrubController(Context context) {
+ mContext = context;
+ mScrollTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+ mOverviewEventSender = Dependency.get(OverviewProxyService.class);
+ mGestureDetector = new GestureDetector(mContext, mGestureListener);
+ mTrackThickness = getDimensionPixelSize(mContext, R.dimen.nav_quick_scrub_track_thickness);
+ mTrackPadding = getDimensionPixelSize(mContext, R.dimen.nav_quick_scrub_track_edge_padding);
+ mTrackPaint.setAlpha(0);
+
+ mTrackAnimator = ObjectAnimator.ofFloat();
+ mTrackAnimator.addUpdateListener(mTrackAnimatorListener);
+ mButtonAnimator = ObjectAnimator.ofInt();
+ mButtonAnimator.addUpdateListener(mButtonTranslationListener);
+ mQuickScrubEndAnimator = new AnimatorSet();
+ mQuickScrubEndAnimator.playTogether(mTrackAnimator, mButtonAnimator);
+ mQuickScrubEndAnimator.setDuration(ANIM_DURATION_MS);
+ mQuickScrubEndAnimator.addListener(mQuickScrubEndListener);
+ mQuickScrubEndAnimator.setInterpolator(mQuickScrubEndInterpolator);
+ }
+
+ public void setComponents(NavigationBarView navigationBarView) {
+ mNavigationBarView = navigationBarView;
+ }
+
+ /**
+ * @return true if we want to intercept touch events for quick scrub/switch and prevent proxying
+ * the event to the overview service.
+ */
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent event) {
+ final IOverviewProxy overviewProxy = mOverviewEventSender.getProxy();
+ final ButtonDispatcher homeButton = mNavigationBarView.getHomeButton();
+ if (overviewProxy == null) {
+ homeButton.setDelayTouchFeedback(false);
+ return false;
+ }
+
+ return handleTouchEvent(event);
+ }
+
+ /**
+ * @return true if we want to handle touch events for quick scrub/switch and prevent proxying
+ * the event to the overview service.
+ */
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ return handleTouchEvent(event);
+ }
+
+ private boolean handleTouchEvent(MotionEvent event) {
+ final IOverviewProxy overviewProxy = mOverviewEventSender.getProxy();
+ final ButtonDispatcher homeButton = mNavigationBarView.getHomeButton();
+ if (mGestureDetector.onTouchEvent(event)) {
+ // If the fling has been handled on UP, then skip proxying the UP
+ return true;
+ }
+ int action = event.getAction();
+ switch (action & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_DOWN: {
+ int x = (int) event.getX();
+ int y = (int) event.getY();
+ if (isQuickScrubEnabled() && mHomeButtonRect.contains(x, y)) {
+ mTouchDownX = x;
+ mTouchDownY = y;
+ homeButton.setDelayTouchFeedback(true);
+ mHandler.postDelayed(mLongPressRunnable, LONG_PRESS_DELAY_MS);
+ } else {
+ homeButton.setDelayTouchFeedback(false);
+ mTouchDownX = mTouchDownY = -1;
+ }
+ mAllowQuickSwitch = true;
+ break;
+ }
+ case MotionEvent.ACTION_MOVE: {
+ if (mTouchDownX != -1) {
+ int x = (int) event.getX();
+ int y = (int) event.getY();
+ int xDiff = Math.abs(x - mTouchDownX);
+ int yDiff = Math.abs(y - mTouchDownY);
+ boolean exceededTouchSlopX = xDiff > mScrollTouchSlop && xDiff > yDiff;
+ boolean exceededTouchSlopY = yDiff > mScrollTouchSlop && yDiff > xDiff;
+ boolean exceededTouchSlop, exceededPerpendicularTouchSlop;
+ int pos, touchDown, offset, trackSize;
+
+ if (mIsVertical) {
+ exceededTouchSlop = exceededTouchSlopY;
+ exceededPerpendicularTouchSlop = exceededTouchSlopX;
+ pos = y;
+ touchDown = mTouchDownY;
+ offset = pos - mTrackRect.top;
+ trackSize = mTrackRect.height();
+ } else {
+ exceededTouchSlop = exceededTouchSlopX;
+ exceededPerpendicularTouchSlop = exceededTouchSlopY;
+ pos = x;
+ touchDown = mTouchDownX;
+ offset = pos - mTrackRect.left;
+ trackSize = mTrackRect.width();
+ }
+ // Do not start scrubbing when dragging in the perpendicular direction if we
+ // haven't already started quickscrub
+ if (!mDraggingActive && !mQuickScrubActive && exceededPerpendicularTouchSlop) {
+ mHandler.removeCallbacksAndMessages(null);
+ return false;
+ }
+ if (!mDragPositive) {
+ offset -= mIsVertical ? mTrackRect.height() : mTrackRect.width();
+ }
+
+ // Control the button movement
+ if (!mDraggingActive && exceededTouchSlop) {
+ boolean allowDrag = !mDragPositive
+ ? offset < 0 && pos < touchDown : offset >= 0 && pos > touchDown;
+ if (allowDrag) {
+ mDownOffset = offset;
+ homeButton.setClickable(false);
+ mDraggingActive = true;
+ }
+ }
+ if (mDraggingActive && (mDragPositive && offset >= 0
+ || !mDragPositive && offset <= 0)) {
+ float scrubFraction =
+ Utilities.clamp(Math.abs(offset) * 1f / trackSize, 0, 1);
+ mTranslation = !mDragPositive
+ ? Utilities.clamp(offset - mDownOffset, -trackSize, 0)
+ : Utilities.clamp(offset - mDownOffset, 0, trackSize);
+ if (mQuickScrubActive) {
+ try {
+ overviewProxy.onQuickScrubProgress(scrubFraction);
+ if (DEBUG_OVERVIEW_PROXY) {
+ Log.d(TAG_OPS, "Quick Scrub Progress:" + scrubFraction);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to send progress of quick scrub.", e);
+ }
+ } else {
+ mTranslation /= SWITCH_STICKINESS;
+ }
+ if (mIsVertical) {
+ homeButton.getCurrentView().setTranslationY(mTranslation);
+ } else {
+ homeButton.getCurrentView().setTranslationX(mTranslation);
+ }
+ }
+ }
+ break;
+ }
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ endQuickScrub();
+ break;
+ }
+ return mDraggingActive || mQuickScrubActive;
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ int color = (int) mTrackColorEvaluator.evaluate(mDarkIntensity, mLightTrackColor,
+ mDarkTrackColor);
+ mTrackPaint.setColor(color);
+ mTrackPaint.setAlpha((int) (mTrackPaint.getAlpha() * mTrackAlpha));
+ canvas.drawRect(mTrackRect, mTrackPaint);
+ }
+
+ @Override
+ public void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ final int width = right - left;
+ final int height = bottom - top;
+ final int x1, x2, y1, y2;
+ if (mIsVertical) {
+ x1 = (width - mTrackThickness) / 2;
+ x2 = x1 + mTrackThickness;
+ y1 = mDragPositive ? height / 2 : mTrackPadding;
+ y2 = y1 + height / 2 - mTrackPadding;
+ } else {
+ y1 = (height - mTrackThickness) / 2;
+ y2 = y1 + mTrackThickness;
+ x1 = mDragPositive ? width / 2 : mTrackPadding;
+ x2 = x1 + width / 2 - mTrackPadding;
+ }
+ mTrackRect.set(x1, y1, x2, y2);
+
+ // Get the touch rect of the home button location
+ View homeView = mNavigationBarView.getHomeButton().getCurrentView();
+ if (homeView != null) {
+ int[] globalHomePos = homeView.getLocationOnScreen();
+ int[] globalNavBarPos = mNavigationBarView.getLocationOnScreen();
+ int homeX = globalHomePos[0] - globalNavBarPos[0];
+ int homeY = globalHomePos[1] - globalNavBarPos[1];
+ mHomeButtonRect.set(homeX, homeY, homeX + homeView.getMeasuredWidth(),
+ homeY + homeView.getMeasuredHeight());
+ }
+ }
+
+ @Override
+ public void onDarkIntensityChange(float intensity) {
+ mDarkIntensity = intensity;
+ mNavigationBarView.invalidate();
+ }
+
+ @Override
+ public void setBarState(boolean isVertical, boolean isRTL) {
+ mIsVertical = isVertical;
+ mIsRTL = isRTL;
+ try {
+ int navbarPos = WindowManagerGlobal.getWindowManagerService().getNavBarPosition();
+ mDragPositive = navbarPos == NAV_BAR_LEFT || navbarPos == NAV_BAR_BOTTOM;
+ if (isRTL) {
+ mDragPositive = !mDragPositive;
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to get nav bar position.", e);
+ }
+ }
+
+ boolean isQuickScrubEnabled() {
+ return SystemProperties.getBoolean("persist.quickstep.scrub.enabled", false);
+ }
+
+ private void startQuickScrub() {
+ if (!mQuickScrubActive) {
+ mQuickScrubActive = true;
+ mLightTrackColor = mContext.getColor(R.color.quick_step_track_background_light);
+ mDarkTrackColor = mContext.getColor(R.color.quick_step_track_background_dark);
+ mTrackAnimator.setFloatValues(0, 1);
+ mTrackAnimator.start();
+ try {
+ mOverviewEventSender.getProxy().onQuickScrubStart();
+ if (DEBUG_OVERVIEW_PROXY) {
+ Log.d(TAG_OPS, "Quick Scrub Start");
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to send start of quick scrub.", e);
+ }
+ }
+ }
+
+ private void endQuickScrub() {
+ mHandler.removeCallbacks(mLongPressRunnable);
+ if (mDraggingActive || mQuickScrubActive) {
+ mButtonAnimator.setIntValues((int) mTranslation, 0);
+ mTrackAnimator.setFloatValues(mTrackAlpha, 0);
+ mQuickScrubEndAnimator.start();
+ try {
+ mOverviewEventSender.getProxy().onQuickScrubEnd();
+ if (DEBUG_OVERVIEW_PROXY) {
+ Log.d(TAG_OPS, "Quick Scrub End");
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to send end of quick scrub.", e);
+ }
+ }
+ mDraggingActive = false;
+ }
+
+ public void cancelQuickSwitch() {
+ mAllowQuickSwitch = false;
+ mHandler.removeCallbacks(mLongPressRunnable);
+ }
+
+ private int getDimensionPixelSize(Context context, @DimenRes int resId) {
+ return context.getResources().getDimensionPixelSize(resId);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenPinningNotify.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenPinningNotify.java
new file mode 100644
index 000000000000..0d07ad9cdf2e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenPinningNotify.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import android.content.Context;
+import android.os.SystemClock;
+import android.util.Slog;
+import android.view.WindowManager;
+import android.widget.Toast;
+
+import com.android.systemui.R;
+import com.android.systemui.SysUIToast;
+
+/**
+ * Helper to manage showing/hiding a image to notify them that they are entering or exiting screen
+ * pinning mode. All exposed methods should be called from a handler thread.
+ */
+public class ScreenPinningNotify {
+ private static final String TAG = "ScreenPinningNotify";
+ private static final long SHOW_TOAST_MINIMUM_INTERVAL = 1000;
+
+ private final Context mContext;
+ private Toast mLastToast;
+ private long mLastShowToastTime;
+
+ public ScreenPinningNotify(Context context) {
+ mContext = context;
+ }
+
+ /** Show "Screen pinned" toast. */
+ void showPinningStartToast() {
+ makeAllUserToastAndShow(R.string.screen_pinning_start);
+ }
+
+ /** Show "Screen unpinned" toast. */
+ void showPinningExitToast() {
+ makeAllUserToastAndShow(R.string.screen_pinning_exit);
+ }
+
+ /** Show a toast that describes the gesture the user should use to escape pinned mode. */
+ void showEscapeToast(boolean isRecentsButtonVisible) {
+ long showToastTime = SystemClock.elapsedRealtime();
+ if ((showToastTime - mLastShowToastTime) < SHOW_TOAST_MINIMUM_INTERVAL) {
+ Slog.i(TAG, "Ignore toast since it is requested in very short interval.");
+ return;
+ }
+ if (mLastToast != null) {
+ mLastToast.cancel();
+ }
+ mLastToast = makeAllUserToastAndShow(isRecentsButtonVisible
+ ? R.string.screen_pinning_toast
+ : R.string.screen_pinning_toast_recents_invisible);
+ mLastShowToastTime = showToastTime;
+ }
+
+ private Toast makeAllUserToastAndShow(int resId) {
+ Toast toast = SysUIToast.makeText(mContext, resId, Toast.LENGTH_LONG);
+ toast.show();
+ return toast;
+ }
+}
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 702afa3a38b1..94ebc1bac2b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -20,13 +20,17 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
+import android.app.AlarmManager;
import android.app.WallpaperManager;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.os.Handler;
import android.os.Trace;
+import android.util.Log;
import android.util.MathUtils;
+import android.view.Choreographer;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
@@ -34,12 +38,14 @@ import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.colorextraction.ColorExtractor;
import com.android.internal.colorextraction.ColorExtractor.GradientColors;
import com.android.internal.colorextraction.ColorExtractor.OnColorsChangedListener;
import com.android.internal.graphics.ColorUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.Dependency;
+import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.statusbar.ExpandableNotificationRow;
@@ -47,7 +53,11 @@ import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.ScrimView;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.statusbar.stack.ViewState;
+import com.android.systemui.util.AlarmTimeout;
+import com.android.systemui.util.wakelock.DelayedWakeLock;
+import com.android.systemui.util.wakelock.WakeLock;
+import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.function.Consumer;
@@ -56,33 +66,67 @@ import java.util.function.Consumer;
* security method gets shown).
*/
public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
- OnHeadsUpChangedListener, OnColorsChangedListener {
+ OnHeadsUpChangedListener, OnColorsChangedListener, Dumpable {
+
+ private static final String TAG = "ScrimController";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
public static final long ANIMATION_DURATION = 220;
public static final Interpolator KEYGUARD_FADE_OUT_INTERPOLATOR
= new PathInterpolator(0f, 0, 0.7f, 1f);
public static final Interpolator KEYGUARD_FADE_OUT_INTERPOLATOR_LOCKED
= new PathInterpolator(0.3f, 0f, 0.8f, 1f);
- // Default alpha value for most scrims, if unsure use this constant
+
+ /**
+ * When both scrims have 0 alpha.
+ */
+ public static final int VISIBILITY_FULLY_TRANSPARENT = 0;
+ /**
+ * When scrims aren't transparent (alpha 0) but also not opaque (alpha 1.)
+ */
+ public static final int VISIBILITY_SEMI_TRANSPARENT = 1;
+ /**
+ * When at least 1 scrim is fully opaque (alpha set to 1.)
+ */
+ public static final int VISIBILITY_FULLY_OPAQUE = 2;
+ /**
+ * Default alpha value for most scrims.
+ */
public static final float GRADIENT_SCRIM_ALPHA = 0.45f;
- // A scrim varies its opacity based on a busyness factor, for example
- // how many notifications are currently visible.
+ /**
+ * A scrim varies its opacity based on a busyness factor, for example
+ * how many notifications are currently visible.
+ */
public static final float GRADIENT_SCRIM_ALPHA_BUSY = 0.70f;
+ /**
+ * The most common scrim, the one under the keyguard.
+ */
protected static final float SCRIM_BEHIND_ALPHA_KEYGUARD = GRADIENT_SCRIM_ALPHA;
+ /**
+ * We fade out the bottom scrim when the bouncer is visible.
+ */
protected static final float SCRIM_BEHIND_ALPHA_UNLOCKING = 0.2f;
- private static final float SCRIM_IN_FRONT_ALPHA = GRADIENT_SCRIM_ALPHA_BUSY;
- private static final float SCRIM_IN_FRONT_ALPHA_LOCKED = GRADIENT_SCRIM_ALPHA_BUSY;
- private static final int TAG_KEY_ANIM = R.id.scrim;
+ /**
+ * Opacity of the scrim behind the bouncer (the one doing actual background protection.)
+ */
+ protected static final float SCRIM_IN_FRONT_ALPHA_LOCKED = GRADIENT_SCRIM_ALPHA_BUSY;
+
+ static final int TAG_KEY_ANIM = R.id.scrim;
private static final int TAG_KEY_ANIM_TARGET = R.id.scrim_target;
private static final int TAG_START_ALPHA = R.id.scrim_alpha_start;
private static final int TAG_END_ALPHA = R.id.scrim_alpha_end;
private static final float NOT_INITIALIZED = -1;
- private final LightBarController mLightBarController;
+ private ScrimState mState = ScrimState.UNINITIALIZED;
+ private final Context mContext;
protected final ScrimView mScrimBehind;
protected final ScrimView mScrimInFront;
- private final UnlockMethodCache mUnlockMethodCache;
private final View mHeadsUpScrim;
+ private final LightBarController mLightBarController;
+ private final UnlockMethodCache mUnlockMethodCache;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final DozeParameters mDozeParameters;
+ private final AlarmTimeout mTimeTicker;
private final SysuiColorExtractor mColorExtractor;
private GradientColors mLockColors;
@@ -94,61 +138,63 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
protected float mScrimBehindAlphaKeyguard = SCRIM_BEHIND_ALPHA_KEYGUARD;
protected float mScrimBehindAlphaUnlocking = SCRIM_BEHIND_ALPHA_UNLOCKING;
- protected boolean mKeyguardShowing;
- private float mFraction;
+ // Assuming the shade is expanded during initialization
+ private float mExpansionFraction = 1f;
private boolean mDarkenWhileDragging;
- protected boolean mBouncerShowing;
- protected boolean mBouncerIsKeyguard = false;
- private boolean mWakeAndUnlocking;
protected boolean mAnimateChange;
private boolean mUpdatePending;
private boolean mTracking;
private boolean mAnimateKeyguardFadingOut;
- protected long mDurationOverride = -1;
+ protected long mAnimationDuration = -1;
private long mAnimationDelay;
private Runnable mOnAnimationFinished;
private boolean mDeferFinishedListener;
private final Interpolator mInterpolator = new DecelerateInterpolator();
- private boolean mDozing;
- private float mDozeInFrontAlpha;
- private float mDozeBehindAlpha;
private float mCurrentInFrontAlpha = NOT_INITIALIZED;
private float mCurrentBehindAlpha = NOT_INITIALIZED;
- private float mCurrentHeadsUpAlpha = NOT_INITIALIZED;
+ private int mCurrentInFrontTint;
+ private int mCurrentBehindTint;
+ private boolean mWallpaperVisibilityTimedOut;
private int mPinnedHeadsUpCount;
private float mTopHeadsUpDragAmount;
private View mDraggedHeadsUpView;
- private boolean mForceHideScrims;
- private boolean mSkipFirstFrame;
- private boolean mDontAnimateBouncerChanges;
private boolean mKeyguardFadingOutInProgress;
- private boolean mAnimatingDozeUnlock;
private ValueAnimator mKeyguardFadeoutAnimation;
- /** Wake up from AOD transition is starting; need fully opaque front scrim */
- private boolean mWakingUpFromAodStarting;
- /** Wake up from AOD transition is in progress; need black tint */
- private boolean mWakingUpFromAodInProgress;
- /** Wake up from AOD transition is animating; need to reset when animation finishes */
- private boolean mWakingUpFromAodAnimationRunning;
- private boolean mScrimsVisble;
- private final Consumer<Boolean> mScrimVisibleListener;
+ private int mScrimsVisibility;
+ private final Consumer<Integer> mScrimVisibleListener;
+ private boolean mBlankScreen;
+ private boolean mScreenBlankingCallbackCalled;
+ private Callback mCallback;
+ private boolean mWallpaperSupportsAmbientMode;
+ private boolean mScreenOn;
+
+ // Scrim blanking callbacks
+ private Choreographer.FrameCallback mPendingFrameCallback;
+ private Runnable mBlankingTransitionRunnable;
+
+ private final WakeLock mWakeLock;
+ private boolean mWakeLockHeld;
public ScrimController(LightBarController lightBarController, ScrimView scrimBehind,
- ScrimView scrimInFront, View headsUpScrim,
- Consumer<Boolean> scrimVisibleListener) {
+ ScrimView scrimInFront, View headsUpScrim, Consumer<Integer> scrimVisibleListener,
+ DozeParameters dozeParameters, AlarmManager alarmManager) {
mScrimBehind = scrimBehind;
mScrimInFront = scrimInFront;
mHeadsUpScrim = headsUpScrim;
mScrimVisibleListener = scrimVisibleListener;
- final Context context = scrimBehind.getContext();
- mUnlockMethodCache = UnlockMethodCache.getInstance(context);
- mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(context);
+ mContext = scrimBehind.getContext();
+ mUnlockMethodCache = UnlockMethodCache.getInstance(mContext);
+ mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
mLightBarController = lightBarController;
- mScrimBehindAlphaResValue = context.getResources().getFloat(R.dimen.scrim_behind_alpha);
+ mScrimBehindAlphaResValue = mContext.getResources().getFloat(R.dimen.scrim_behind_alpha);
+ mTimeTicker = new AlarmTimeout(alarmManager, this::onHideWallpaperTimeout,
+ "hide_aod_wallpaper", new Handler());
+ mWakeLock = createWakeLock();
// Scrim alpha is initially set to the value on the resource but might be changed
// to make sure that text on top of it is legible.
mScrimBehindAlpha = mScrimBehindAlphaResValue;
+ mDozeParameters = dozeParameters;
mColorExtractor = Dependency.get(SysuiColorExtractor.class);
mColorExtractor.addOnColorsChangedListener(this);
@@ -158,159 +204,215 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
ColorExtractor.TYPE_DARK, true /* ignoreVisibility */);
mNeedsDrawableColorUpdate = true;
+ final ScrimState[] states = ScrimState.values();
+ for (int i = 0; i < states.length; i++) {
+ states[i].init(mScrimInFront, mScrimBehind, mDozeParameters);
+ states[i].setScrimBehindAlphaKeyguard(mScrimBehindAlphaKeyguard);
+ }
+ mState = ScrimState.UNINITIALIZED;
+
updateHeadsUpScrim(false);
updateScrims();
}
- public void setKeyguardShowing(boolean showing) {
- mKeyguardShowing = showing;
-
- // Showing/hiding the keyguard means that scrim colors have to be switched
- mNeedsDrawableColorUpdate = true;
- scheduleUpdate();
- }
-
- protected void setScrimBehindValues(float scrimBehindAlphaKeyguard,
- float scrimBehindAlphaUnlocking) {
- mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard;
- mScrimBehindAlphaUnlocking = scrimBehindAlphaUnlocking;
- scheduleUpdate();
- }
-
- public void onTrackingStarted() {
- mTracking = true;
- mDarkenWhileDragging = !mUnlockMethodCache.canSkipBouncer();
+ public void transitionTo(ScrimState state) {
+ transitionTo(state, null);
}
- public void onExpandingFinished() {
- mTracking = false;
- }
-
- public void setPanelExpansion(float fraction) {
- if (mFraction != fraction) {
- mFraction = fraction;
- scheduleUpdate();
- if (mPinnedHeadsUpCount != 0) {
- updateHeadsUpScrim(false);
- }
- if (mKeyguardFadeoutAnimation != null && mTracking) {
- mKeyguardFadeoutAnimation.cancel();
+ public void transitionTo(ScrimState state, Callback callback) {
+ if (state == mState) {
+ // Call the callback anyway, unless it's already enqueued
+ if (callback != null && mCallback != callback) {
+ callback.onFinished();
}
+ return;
+ } else if (DEBUG) {
+ Log.d(TAG, "State changed to: " + state);
}
- }
- public void setBouncerShowing(boolean showing) {
- mBouncerShowing = showing;
- mAnimateChange = !mTracking && !mDontAnimateBouncerChanges && !mKeyguardFadingOutInProgress;
- scheduleUpdate();
- }
+ if (state == ScrimState.UNINITIALIZED) {
+ throw new IllegalArgumentException("Cannot change to UNINITIALIZED.");
+ }
- /** Prepares the wakeUpFromAod animation (while turning on screen); Forces black scrims. */
- public void prepareWakeUpFromAod() {
- if (mWakingUpFromAodInProgress) {
- return;
+ final ScrimState oldState = mState;
+ mState = state;
+ Trace.traceCounter(Trace.TRACE_TAG_APP, "scrim_state", mState.getIndex());
+
+ if (mCallback != null) {
+ mCallback.onCancelled();
}
- mWakingUpFromAodInProgress = true;
- mWakingUpFromAodStarting = true;
- mAnimateChange = false;
- scheduleUpdate();
- onPreDraw();
- }
+ mCallback = callback;
- /** Starts the wakeUpFromAod animation (once screen is on); animate to transparent scrims. */
- public void wakeUpFromAod() {
- if (mWakeAndUnlocking || mAnimateKeyguardFadingOut) {
- // Wake and unlocking has a separate transition that must not be interfered with.
- mWakingUpFromAodStarting = false;
- mWakingUpFromAodInProgress = false;
- return;
+ state.prepare(oldState);
+ mScreenBlankingCallbackCalled = false;
+ mAnimationDelay = 0;
+ mBlankScreen = state.getBlanksScreen();
+ mAnimateChange = state.getAnimateChange();
+ mAnimationDuration = state.getAnimationDuration();
+ mCurrentInFrontTint = state.getFrontTint();
+ mCurrentBehindTint = state.getBehindTint();
+ mCurrentInFrontAlpha = state.getFrontAlpha();
+ mCurrentBehindAlpha = state.getBehindAlpha();
+ applyExpansionToAlpha();
+
+ // Cancel blanking transitions that were pending before we requested a new state
+ if (mPendingFrameCallback != null) {
+ Choreographer.getInstance().removeFrameCallback(mPendingFrameCallback);
+ mPendingFrameCallback = null;
}
- if (mWakingUpFromAodStarting) {
- mWakingUpFromAodInProgress = true;
- mWakingUpFromAodStarting = false;
- mAnimateChange = true;
- scheduleUpdate();
+ if (getHandler().hasCallbacks(mBlankingTransitionRunnable)) {
+ getHandler().removeCallbacks(mBlankingTransitionRunnable);
+ mBlankingTransitionRunnable = null;
}
- }
- public void setWakeAndUnlocking() {
- mWakeAndUnlocking = true;
- mAnimatingDozeUnlock = true;
- mWakingUpFromAodStarting = false;
- mWakingUpFromAodInProgress = false;
- scheduleUpdate();
- }
+ // Showing/hiding the keyguard means that scrim colors have to be switched, not necessary
+ // to do the same when you're just showing the brightness mirror.
+ mNeedsDrawableColorUpdate = state != ScrimState.BRIGHTNESS_MIRROR;
- public void animateKeyguardFadingOut(long delay, long duration, Runnable onAnimationFinished,
- boolean skipFirstFrame) {
- mWakeAndUnlocking = false;
- mAnimateKeyguardFadingOut = true;
- mDurationOverride = duration;
- mAnimationDelay = delay;
- mAnimateChange = true;
- mSkipFirstFrame = skipFirstFrame;
- mOnAnimationFinished = onAnimationFinished;
+ if (mKeyguardFadeoutAnimation != null) {
+ mKeyguardFadeoutAnimation.cancel();
+ }
- if (!mKeyguardUpdateMonitor.needsSlowUnlockTransition()) {
- scheduleUpdate();
+ // The device might sleep if it's entering AOD, we need to make sure that
+ // the animation plays properly until the last frame.
+ // It's important to avoid holding the wakelock unless necessary because
+ // WakeLock#aqcuire will trigger an IPC and will cause jank.
+ if (mState == ScrimState.AOD) {
+ holdWakeLock();
+ }
- // No need to wait for the next frame to be drawn for this case - onPreDraw will execute
- // the changes we just scheduled.
- onPreDraw();
+ // AOD wallpapers should fade away after a while
+ if (mWallpaperSupportsAmbientMode && mDozeParameters.getAlwaysOn()
+ && (mState == ScrimState.AOD || mState == ScrimState.PULSING)) {
+ if (!mWallpaperVisibilityTimedOut) {
+ mTimeTicker.schedule(mDozeParameters.getWallpaperAodDuration(),
+ AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
+ }
} else {
+ mTimeTicker.cancel();
+ mWallpaperVisibilityTimedOut = false;
+ }
+ if (mKeyguardUpdateMonitor.needsSlowUnlockTransition() && mState == ScrimState.UNLOCKED) {
// In case the user isn't unlocked, make sure to delay a bit because the system is hosed
- // with too many things in this case, in order to not skip the initial frames.
+ // with too many things at this case, in order to not skip the initial frames.
mScrimInFront.postOnAnimationDelayed(this::scheduleUpdate, 16);
+ mAnimationDelay = StatusBar.FADE_KEYGUARD_START_DELAY;
+ } else if (!mDozeParameters.getAlwaysOn() && oldState == ScrimState.AOD) {
+ // Execute first frame immediately when display was completely off.
+ // Scheduling a frame isn't enough because the system may aggressively enter doze,
+ // delaying callbacks or never triggering them until the power button is pressed.
+ onPreDraw();
+ } else {
+ scheduleUpdate();
}
}
- public void abortKeyguardFadingOut() {
- if (mAnimateKeyguardFadingOut) {
- endAnimateKeyguardFadingOut(true /* force */);
- }
+ public ScrimState getState() {
+ return mState;
}
- public void animateKeyguardUnoccluding(long duration) {
- mAnimateChange = false;
- setScrimBehindAlpha(0f);
- mAnimateChange = true;
+ protected void setScrimBehindValues(float scrimBehindAlphaKeyguard,
+ float scrimBehindAlphaUnlocking) {
+ mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard;
+ mScrimBehindAlphaUnlocking = scrimBehindAlphaUnlocking;
+ ScrimState[] states = ScrimState.values();
+ for (int i = 0; i < states.length; i++) {
+ states[i].setScrimBehindAlphaKeyguard(scrimBehindAlphaKeyguard);
+ }
scheduleUpdate();
- mDurationOverride = duration;
}
- public void animateGoingToFullShade(long delay, long duration) {
- mDurationOverride = duration;
- mAnimationDelay = delay;
+ public void onTrackingStarted() {
+ mTracking = true;
+ mDarkenWhileDragging = !mUnlockMethodCache.canSkipBouncer();
+ }
+
+ public void onExpandingFinished() {
+ mTracking = false;
+ }
+
+ @VisibleForTesting
+ protected void onHideWallpaperTimeout() {
+ if (mState != ScrimState.AOD && mState != ScrimState.PULSING) {
+ return;
+ }
+
+ holdWakeLock();
+ mWallpaperVisibilityTimedOut = true;
mAnimateChange = true;
+ mAnimationDuration = mDozeParameters.getWallpaperFadeOutDuration();
scheduleUpdate();
}
- public void setDozing(boolean dozing) {
- if (mDozing != dozing) {
- mDozing = dozing;
- scheduleUpdate();
+ private void holdWakeLock() {
+ if (!mWakeLockHeld) {
+ if (mWakeLock != null) {
+ mWakeLockHeld = true;
+ mWakeLock.acquire();
+ } else {
+ Log.w(TAG, "Cannot hold wake lock, it has not been set yet");
+ }
}
}
- public void setDozeInFrontAlpha(float alpha) {
- mDozeInFrontAlpha = alpha;
- updateScrimColor(mScrimInFront);
- }
+ /**
+ * Current state of the shade expansion when pulling it from the top.
+ * This value is 1 when on top of the keyguard and goes to 0 as the user drags up.
+ *
+ * The expansion fraction is tied to the scrim opacity.
+ *
+ * @param fraction From 0 to 1 where 0 means collapse and 1 expanded.
+ */
+ public void setPanelExpansion(float fraction) {
+ if (mExpansionFraction != fraction) {
+ mExpansionFraction = fraction;
- public void setDozeBehindAlpha(float alpha) {
- mDozeBehindAlpha = alpha;
- updateScrimColor(mScrimBehind);
- }
+ if (!(mState == ScrimState.UNLOCKED || mState == ScrimState.KEYGUARD)) {
+ return;
+ }
- public float getDozeBehindAlpha() {
- return mDozeBehindAlpha;
+ applyExpansionToAlpha();
+
+ if (mUpdatePending) {
+ return;
+ }
+
+ if (mPinnedHeadsUpCount != 0) {
+ updateHeadsUpScrim(false);
+ }
+ updateScrim(false /* animate */, mScrimInFront, mCurrentInFrontAlpha);
+ updateScrim(false /* animate */, mScrimBehind, mCurrentBehindAlpha);
+ }
}
- public float getDozeInFrontAlpha() {
- return mDozeInFrontAlpha;
+ private void applyExpansionToAlpha() {
+ if (mState == ScrimState.UNLOCKED) {
+ // Darken scrim as you pull down the shade when unlocked
+ float behindFraction = getInterpolatedFraction();
+ behindFraction = (float) Math.pow(behindFraction, 0.8f);
+ mCurrentBehindAlpha = behindFraction * mScrimBehindAlphaKeyguard;
+ mCurrentInFrontAlpha = 0;
+ } else if (mState == ScrimState.KEYGUARD) {
+ // Either darken of make the scrim transparent when you
+ // pull down the shade
+ float interpolatedFract = getInterpolatedFraction();
+ if (mDarkenWhileDragging) {
+ mCurrentBehindAlpha = MathUtils.lerp(mScrimBehindAlphaUnlocking,
+ mScrimBehindAlphaKeyguard, interpolatedFract);
+ mCurrentInFrontAlpha = (1f - interpolatedFract) * SCRIM_IN_FRONT_ALPHA_LOCKED;
+ } else {
+ mCurrentBehindAlpha = MathUtils.lerp(0 /* start */, mScrimBehindAlphaKeyguard,
+ interpolatedFract);
+ mCurrentInFrontAlpha = 0;
+ }
+ }
}
+ /**
+ * Keyguard and shade scrim opacity varies according to how many notifications are visible.
+ * @param notificationCount Number of visible notifications.
+ */
public void setNotificationCount(int notificationCount) {
final float maxNotificationDensity = 3;
float notificationDensity = Math.min(notificationCount / maxNotificationDensity, 1f);
@@ -319,15 +421,11 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
notificationDensity);
if (mScrimBehindAlphaKeyguard != newAlpha) {
mScrimBehindAlphaKeyguard = newAlpha;
- mAnimateChange = true;
- scheduleUpdate();
- }
- }
- private float getScrimInFrontAlpha() {
- return mKeyguardUpdateMonitor.needsSlowUnlockTransition()
- ? SCRIM_IN_FRONT_ALPHA_LOCKED
- : SCRIM_IN_FRONT_ALPHA;
+ if (mState == ScrimState.KEYGUARD || mState == ScrimState.BOUNCER) {
+ scheduleUpdate();
+ }
+ }
}
/**
@@ -352,7 +450,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
if (mNeedsDrawableColorUpdate) {
mNeedsDrawableColorUpdate = false;
final GradientColors currentScrimColors;
- if (mKeyguardShowing) {
+ if (mState == ScrimState.KEYGUARD || mState == ScrimState.BOUNCER) {
// Always animate color changes if we're seeing the keyguard
mScrimInFront.setColors(mLockColors, true /* animated */);
mScrimBehind.setColors(mLockColors, true /* animated */);
@@ -375,77 +473,45 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
mLightBarController.setScrimColor(mScrimInFront.getColors());
}
- if (mAnimateKeyguardFadingOut || mForceHideScrims) {
- setScrimInFrontAlpha(0f);
- setScrimBehindAlpha(0f);
- } else if (mWakeAndUnlocking) {
- // During wake and unlock, we first hide everything behind a black scrim, which then
- // gets faded out from animateKeyguardFadingOut. This must never be animated.
- mAnimateChange = false;
- if (mDozing) {
- setScrimInFrontAlpha(0f);
- setScrimBehindAlpha(1f);
- } else {
- setScrimInFrontAlpha(1f);
- setScrimBehindAlpha(0f);
- }
- } else if (!mKeyguardShowing && !mBouncerShowing && !mWakingUpFromAodStarting) {
- updateScrimNormal();
- setScrimInFrontAlpha(0);
- } else {
- updateScrimKeyguard();
+ // We want to override the back scrim opacity for AOD and PULSING
+ // when it's time to fade the wallpaper away.
+ boolean overrideBackScrimAlpha = (mState == ScrimState.PULSING || mState == ScrimState.AOD)
+ && mWallpaperVisibilityTimedOut;
+ if (overrideBackScrimAlpha) {
+ mCurrentBehindAlpha = 1;
}
- mAnimateChange = false;
+
+ setScrimInFrontAlpha(mCurrentInFrontAlpha);
+ setScrimBehindAlpha(mCurrentBehindAlpha);
+
dispatchScrimsVisible();
}
private void dispatchScrimsVisible() {
- boolean scrimsVisible = mScrimBehind.getViewAlpha() > 0 || mScrimInFront.getViewAlpha() > 0;
-
- if (mScrimsVisble != scrimsVisible) {
- mScrimsVisble = scrimsVisible;
-
- mScrimVisibleListener.accept(scrimsVisible);
+ final int currentScrimVisibility;
+ if (mScrimInFront.getViewAlpha() == 1 || mScrimBehind.getViewAlpha() == 1) {
+ currentScrimVisibility = VISIBILITY_FULLY_OPAQUE;
+ } else if (mScrimInFront.getViewAlpha() == 0 && mScrimBehind.getViewAlpha() == 0) {
+ currentScrimVisibility = VISIBILITY_FULLY_TRANSPARENT;
+ } else {
+ currentScrimVisibility = VISIBILITY_SEMI_TRANSPARENT;
}
- }
- private void updateScrimKeyguard() {
- if (mTracking && mDarkenWhileDragging) {
- float behindFraction = Math.max(0, Math.min(mFraction, 1));
- float fraction = 1 - behindFraction;
- fraction = (float) Math.pow(fraction, 0.8f);
- behindFraction = (float) Math.pow(behindFraction, 0.8f);
- setScrimInFrontAlpha(fraction * getScrimInFrontAlpha());
- setScrimBehindAlpha(behindFraction * mScrimBehindAlphaKeyguard);
- } else if (mBouncerShowing && !mBouncerIsKeyguard) {
- setScrimInFrontAlpha(getScrimInFrontAlpha());
- updateScrimNormal();
- } else if (mBouncerShowing) {
- setScrimInFrontAlpha(0f);
- setScrimBehindAlpha(mScrimBehindAlpha);
- } else {
- float fraction = Math.max(0, Math.min(mFraction, 1));
- if (mWakingUpFromAodStarting) {
- setScrimInFrontAlpha(1f);
- } else {
- setScrimInFrontAlpha(0f);
- }
- setScrimBehindAlpha(fraction
- * (mScrimBehindAlphaKeyguard - mScrimBehindAlphaUnlocking)
- + mScrimBehindAlphaUnlocking);
+ if (mScrimsVisibility != currentScrimVisibility) {
+ mScrimsVisibility = currentScrimVisibility;
+ mScrimVisibleListener.accept(currentScrimVisibility);
}
}
- private void updateScrimNormal() {
- float frac = mFraction;
+ private float getInterpolatedFraction() {
+ float frac = mExpansionFraction;
// let's start this 20% of the way down the screen
frac = frac * 1.2f - 0.2f;
if (frac <= 0) {
- setScrimBehindAlpha(0);
+ return 0;
} else {
// woo, special effects
- final float k = (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2f))));
- setScrimBehindAlpha(k * mScrimBehindAlpha);
+ return (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2f))));
}
}
@@ -455,102 +521,76 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
private void setScrimInFrontAlpha(float alpha) {
setScrimAlpha(mScrimInFront, alpha);
- if (alpha == 0f) {
- mScrimInFront.setClickable(false);
- } else {
- // Eat touch events (unless dozing).
- mScrimInFront.setClickable(!mDozing);
- }
}
private void setScrimAlpha(View scrim, float alpha) {
- updateScrim(mAnimateChange, scrim, alpha, getCurrentScrimAlpha(scrim));
- }
-
- protected float getDozeAlpha(View scrim) {
- return scrim == mScrimBehind ? mDozeBehindAlpha : mDozeInFrontAlpha;
- }
-
- protected float getCurrentScrimAlpha(View scrim) {
- return scrim == mScrimBehind ? mCurrentBehindAlpha
- : scrim == mScrimInFront ? mCurrentInFrontAlpha
- : mCurrentHeadsUpAlpha;
- }
-
- private void setCurrentScrimAlpha(View scrim, float alpha) {
- if (scrim == mScrimBehind) {
- mCurrentBehindAlpha = alpha;
- mLightBarController.setScrimAlpha(mCurrentBehindAlpha);
- } else if (scrim == mScrimInFront) {
- mCurrentInFrontAlpha = alpha;
+ if (alpha == 0f) {
+ scrim.setClickable(false);
} else {
- alpha = Math.max(0.0f, Math.min(1.0f, alpha));
- mCurrentHeadsUpAlpha = alpha;
+ // Eat touch events (unless dozing).
+ scrim.setClickable(!(mState == ScrimState.AOD));
}
+ updateScrim(mAnimateChange, scrim, alpha);
}
- private void updateScrimColor(View scrim) {
- float alpha1 = getCurrentScrimAlpha(scrim);
+ private void updateScrimColor(View scrim, float alpha, int tint) {
+ alpha = Math.max(0, Math.min(1.0f, alpha));
if (scrim instanceof ScrimView) {
ScrimView scrimView = (ScrimView) scrim;
- float dozeAlpha = getDozeAlpha(scrim);
- float alpha = 1 - (1 - alpha1) * (1 - dozeAlpha);
- alpha = Math.max(0, Math.min(1.0f, alpha));
- scrimView.setViewAlpha(alpha);
Trace.traceCounter(Trace.TRACE_TAG_APP,
scrim == mScrimInFront ? "front_scrim_alpha" : "back_scrim_alpha",
(int) (alpha * 255));
- int dozeTint = Color.TRANSPARENT;
-
- boolean dozing = mAnimatingDozeUnlock || mDozing;
- boolean frontScrimDozing = mWakingUpFromAodInProgress;
- if (dozing || frontScrimDozing && scrim == mScrimInFront) {
- dozeTint = Color.BLACK;
- }
Trace.traceCounter(Trace.TRACE_TAG_APP,
scrim == mScrimInFront ? "front_scrim_tint" : "back_scrim_tint",
- dozeTint == Color.BLACK ? 1 : 0);
+ Color.alpha(tint));
- scrimView.setTint(dozeTint);
+ scrimView.setTint(tint);
+ scrimView.setViewAlpha(alpha);
} else {
- scrim.setAlpha(alpha1);
+ scrim.setAlpha(alpha);
}
dispatchScrimsVisible();
}
- private void startScrimAnimation(final View scrim, float target) {
- float current = getCurrentScrimAlpha(scrim);
- ValueAnimator anim = ValueAnimator.ofFloat(current, target);
+ private int getCurrentScrimTint(View scrim) {
+ return scrim == mScrimInFront ? mCurrentInFrontTint : mCurrentBehindTint;
+ }
+
+ private void startScrimAnimation(final View scrim, float current, float target) {
+ ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
+ final int initialScrimTint = scrim instanceof ScrimView ? ((ScrimView) scrim).getTint() :
+ Color.TRANSPARENT;
anim.addUpdateListener(animation -> {
- float alpha = (float) animation.getAnimatedValue();
- setCurrentScrimAlpha(scrim, alpha);
- updateScrimColor(scrim);
+ final float animAmount = (float) animation.getAnimatedValue();
+ final int finalScrimTint = scrim == mScrimInFront ?
+ mCurrentInFrontTint : mCurrentBehindTint;
+ float alpha = MathUtils.lerp(current, target, animAmount);
+ int tint = ColorUtils.blendARGB(initialScrimTint, finalScrimTint, animAmount);
+ updateScrimColor(scrim, alpha, tint);
dispatchScrimsVisible();
});
anim.setInterpolator(getInterpolator());
anim.setStartDelay(mAnimationDelay);
- anim.setDuration(mDurationOverride != -1 ? mDurationOverride : ANIMATION_DURATION);
+ anim.setDuration(mAnimationDuration);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- if (!mDeferFinishedListener && mOnAnimationFinished != null) {
- mOnAnimationFinished.run();
- mOnAnimationFinished = null;
- }
if (mKeyguardFadingOutInProgress) {
mKeyguardFadeoutAnimation = null;
mKeyguardFadingOutInProgress = false;
- mAnimatingDozeUnlock = false;
- }
- if (mWakingUpFromAodAnimationRunning && !mDeferFinishedListener) {
- mWakingUpFromAodAnimationRunning = false;
- mWakingUpFromAodInProgress = false;
}
+ onFinished();
+
scrim.setTag(TAG_KEY_ANIM, null);
scrim.setTag(TAG_KEY_ANIM_TARGET, null);
dispatchScrimsVisible();
+
+ if (!mDeferFinishedListener && mOnAnimationFinished != null) {
+ mOnAnimationFinished.run();
+ mOnAnimationFinished = null;
+ }
}
});
anim.start();
@@ -558,12 +598,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
mKeyguardFadingOutInProgress = true;
mKeyguardFadeoutAnimation = anim;
}
- if (mWakingUpFromAodInProgress) {
- mWakingUpFromAodAnimationRunning = true;
- }
- if (mSkipFirstFrame) {
- anim.setCurrentPlayTime(16);
- }
scrim.setTag(TAG_KEY_ANIM, anim);
scrim.setTag(TAG_KEY_ANIM_TARGET, target);
}
@@ -582,19 +616,33 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
public boolean onPreDraw() {
mScrimBehind.getViewTreeObserver().removeOnPreDrawListener(this);
mUpdatePending = false;
- if (mDontAnimateBouncerChanges) {
- mDontAnimateBouncerChanges = false;
+ if (mCallback != null) {
+ mCallback.onStart();
}
updateScrims();
- mDurationOverride = -1;
- mAnimationDelay = 0;
- mSkipFirstFrame = false;
// Make sure that we always call the listener even if we didn't start an animation.
endAnimateKeyguardFadingOut(false /* force */);
return true;
}
+ private void onFinished() {
+ if (mWakeLockHeld) {
+ mWakeLock.release();
+ mWakeLockHeld = false;
+ }
+ if (mCallback != null) {
+ mCallback.onFinished();
+ mCallback = null;
+ }
+ // When unlocking with fingerprint, we'll fade the scrims from black to transparent.
+ // At the end of the animation we need to remove the tint.
+ if (mState == ScrimState.UNLOCKED) {
+ mCurrentInFrontTint = Color.TRANSPARENT;
+ mCurrentBehindTint = Color.TRANSPARENT;
+ }
+ }
+
private void endAnimateKeyguardFadingOut(boolean force) {
mAnimateKeyguardFadingOut = false;
if (force || (!isAnimating(mScrimInFront) && !isAnimating(mScrimBehind))) {
@@ -603,8 +651,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
mOnAnimationFinished = null;
}
mKeyguardFadingOutInProgress = false;
- if (!mWakeAndUnlocking || force)
- mAnimatingDozeUnlock = false;
}
}
@@ -641,16 +687,19 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
}
private void updateHeadsUpScrim(boolean animate) {
- updateScrim(animate, mHeadsUpScrim, calculateHeadsUpAlpha(), mCurrentHeadsUpAlpha);
+ updateScrim(animate, mHeadsUpScrim, calculateHeadsUpAlpha());
}
- private void updateScrim(boolean animate, View scrim, float alpha, float currentAlpha) {
- if (mKeyguardFadingOutInProgress && mKeyguardFadeoutAnimation.getCurrentPlayTime() != 0) {
- return;
- }
+ @VisibleForTesting
+ void setOnAnimationFinished(Runnable onAnimationFinished) {
+ mOnAnimationFinished = onAnimationFinished;
+ }
+
+ private void updateScrim(boolean animate, View scrim, float alpha) {
+ final float currentAlpha = scrim instanceof ScrimView ? ((ScrimView) scrim).getViewAlpha()
+ : scrim.getAlpha();
- ValueAnimator previousAnimator = ViewState.getChildTag(scrim,
- TAG_KEY_ANIM);
+ ValueAnimator previousAnimator = ViewState.getChildTag(scrim, TAG_KEY_ANIM);
float animEndValue = -1;
if (previousAnimator != null) {
if (animate || alpha == currentAlpha) {
@@ -664,9 +713,38 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
animEndValue = ViewState.getChildTag(scrim, TAG_END_ALPHA);
}
}
- if (alpha != currentAlpha && alpha != animEndValue) {
+
+ if (mPendingFrameCallback != null) {
+ // Display is off and we're waiting.
+ return;
+ } else if (mBlankScreen) {
+ // Need to blank the display before continuing.
+ blankDisplay();
+ return;
+ } else if (!mScreenBlankingCallbackCalled) {
+ // Not blanking the screen. Letting the callback know that we're ready
+ // to replace what was on the screen before.
+ if (mCallback != null) {
+ mCallback.onDisplayBlanked();
+ mScreenBlankingCallbackCalled = true;
+ }
+ }
+
+ // TODO factor mLightBarController out of this class
+ if (scrim == mScrimBehind) {
+ mLightBarController.setScrimAlpha(alpha);
+ }
+
+ final ScrimView scrimView = scrim instanceof ScrimView ? (ScrimView) scrim : null;
+ final boolean wantsAlphaUpdate = alpha != currentAlpha && alpha != animEndValue;
+ final boolean wantsTintUpdate = scrimView != null
+ && scrimView.getTint() != getCurrentScrimTint(scrimView);
+
+ if (wantsAlphaUpdate || wantsTintUpdate) {
if (animate) {
- startScrimAnimation(scrim, alpha);
+ final float fromAlpha = scrimView == null ? scrim.getAlpha()
+ : scrimView.getViewAlpha();
+ startScrimAnimation(scrim, fromAlpha, alpha);
scrim.setTag(TAG_START_ALPHA, currentAlpha);
scrim.setTag(TAG_END_ALPHA, alpha);
} else {
@@ -685,13 +763,55 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
} else {
// update the alpha directly
- setCurrentScrimAlpha(scrim, alpha);
- updateScrimColor(scrim);
+ updateScrimColor(scrim, alpha, getCurrentScrimTint(scrim));
+ onFinished();
}
}
+ } else {
+ onFinished();
}
}
+ private void blankDisplay() {
+ updateScrimColor(mScrimInFront, 1, Color.BLACK);
+
+ // Notify callback that the screen is completely black and we're
+ // ready to change the display power mode
+ mPendingFrameCallback = frameTimeNanos -> {
+ if (mCallback != null) {
+ mCallback.onDisplayBlanked();
+ mScreenBlankingCallbackCalled = true;
+ }
+
+ mBlankingTransitionRunnable = () -> {
+ mBlankingTransitionRunnable = null;
+ mPendingFrameCallback = null;
+ mBlankScreen = false;
+ // Try again.
+ updateScrims();
+ };
+
+ // Setting power states can happen after we push out the frame. Make sure we
+ // stay fully opaque until the power state request reaches the lower levels.
+ final int delay = mScreenOn ? 16 : 500;
+ if (DEBUG) {
+ Log.d(TAG, "Fading out scrims with delay: " + delay);
+ }
+ getHandler().postDelayed(mBlankingTransitionRunnable, delay);
+ };
+ doOnTheNextFrame(mPendingFrameCallback);
+ }
+
+ @VisibleForTesting
+ protected void doOnTheNextFrame(Choreographer.FrameCallback callback) {
+ Choreographer.getInstance().postFrameCallback(callback);
+ }
+
+ @VisibleForTesting
+ protected Handler getHandler() {
+ return Handler.getMain();
+ }
+
/**
* Set the amount the current top heads up view is dragged. The range is from 0 to 1 and 0 means
* the heads up is in its resting space and 1 means it's fully dragged out.
@@ -714,28 +834,18 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
} else {
alpha = 1.0f - mTopHeadsUpDragAmount;
}
- float expandFactor = (1.0f - mFraction);
+ float expandFactor = (1.0f - mExpansionFraction);
expandFactor = Math.max(expandFactor, 0.0f);
return alpha * expandFactor;
}
- public void forceHideScrims(boolean hide, boolean animated) {
- mForceHideScrims = hide;
- mAnimateChange = animated;
- scheduleUpdate();
- }
-
- public void dontAnimateBouncerChangesUntilNextFrame() {
- mDontAnimateBouncerChanges = true;
- }
-
public void setExcludedBackgroundArea(Rect area) {
mScrimBehind.setExcludedArea(area);
}
public int getBackgroundColor() {
int color = mLockColors.getMainColor();
- return Color.argb((int) (mScrimBehind.getAlpha() * Color.alpha(color)),
+ return Color.argb((int) (mScrimBehind.getViewAlpha() * Color.alpha(color)),
Color.red(color), Color.green(color), Color.blue(color));
}
@@ -764,27 +874,68 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
}
if ((which & WallpaperManager.FLAG_SYSTEM) != 0) {
mSystemColors = mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM,
- ColorExtractor.TYPE_DARK, mKeyguardShowing);
+ ColorExtractor.TYPE_DARK, mState != ScrimState.UNLOCKED);
mNeedsDrawableColorUpdate = true;
scheduleUpdate();
}
}
- public void dump(PrintWriter pw) {
- pw.println(" ScrimController:");
+ @VisibleForTesting
+ protected WakeLock createWakeLock() {
+ return new DelayedWakeLock(getHandler(),
+ WakeLock.createPartial(mContext, "Doze"));
+ }
- pw.print(" frontScrim:"); pw.print(" viewAlpha="); pw.print(mScrimInFront.getViewAlpha());
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println(" ScrimController: ");
+ pw.print(" state: "); pw.println(mState);
+ pw.print(" frontScrim:"); pw.print(" viewAlpha="); pw.print(mScrimInFront.getViewAlpha());
pw.print(" alpha="); pw.print(mCurrentInFrontAlpha);
- pw.print(" dozeAlpha="); pw.print(mDozeInFrontAlpha);
pw.print(" tint=0x"); pw.println(Integer.toHexString(mScrimInFront.getTint()));
- pw.print(" backScrim:"); pw.print(" viewAlpha="); pw.print(mScrimBehind.getViewAlpha());
+ pw.print(" backScrim:"); pw.print(" viewAlpha="); pw.print(mScrimBehind.getViewAlpha());
pw.print(" alpha="); pw.print(mCurrentBehindAlpha);
- pw.print(" dozeAlpha="); pw.print(mDozeBehindAlpha);
pw.print(" tint=0x"); pw.println(Integer.toHexString(mScrimBehind.getTint()));
- pw.print(" mBouncerShowing="); pw.println(mBouncerShowing);
pw.print(" mTracking="); pw.println(mTracking);
- pw.print(" mForceHideScrims="); pw.println(mForceHideScrims);
+ }
+
+ public void setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode) {
+ mWallpaperSupportsAmbientMode = wallpaperSupportsAmbientMode;
+ ScrimState[] states = ScrimState.values();
+ for (int i = 0; i < states.length; i++) {
+ states[i].setWallpaperSupportsAmbientMode(wallpaperSupportsAmbientMode);
+ }
+ }
+
+ /**
+ * Interrupts blanking transitions once the display notifies that it's already on.
+ */
+ public void onScreenTurnedOn() {
+ mScreenOn = true;
+ final Handler handler = getHandler();
+ if (handler.hasCallbacks(mBlankingTransitionRunnable)) {
+ if (DEBUG) {
+ Log.d(TAG, "Shorter blanking because screen turned on. All good.");
+ }
+ handler.removeCallbacks(mBlankingTransitionRunnable);
+ mBlankingTransitionRunnable.run();
+ }
+ }
+
+ public void onScreenTurnedOff() {
+ mScreenOn = false;
+ }
+
+ public interface Callback {
+ default void onStart() {
+ }
+ default void onDisplayBlanked() {
+ }
+ default void onFinished() {
+ }
+ default void onCancelled() {
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
new file mode 100644
index 000000000000..381e4af31853
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -0,0 +1,234 @@
+/*
+ * 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.statusbar.phone;
+
+import android.graphics.Color;
+import android.os.Trace;
+
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.statusbar.ScrimView;
+import com.android.systemui.statusbar.stack.StackStateAnimator;
+
+/**
+ * Possible states of the ScrimController state machine.
+ */
+public enum ScrimState {
+
+ /**
+ * Initial state.
+ */
+ UNINITIALIZED(-1),
+
+ /**
+ * On the lock screen.
+ */
+ KEYGUARD(0) {
+
+ @Override
+ public void prepare(ScrimState previousState) {
+ mBlankScreen = false;
+ if (previousState == ScrimState.AOD) {
+ mAnimationDuration = StackStateAnimator.ANIMATION_DURATION_WAKEUP;
+ if (mDisplayRequiresBlanking) {
+ // DisplayPowerManager will blank the screen, we'll just
+ // set our scrim to black in this frame to avoid flickering and
+ // fade it out afterwards.
+ mBlankScreen = true;
+ }
+ } else {
+ mAnimationDuration = ScrimController.ANIMATION_DURATION;
+ }
+ mCurrentBehindAlpha = mScrimBehindAlphaKeyguard;
+ mCurrentInFrontAlpha = 0;
+ }
+ },
+
+ /**
+ * Showing password challenge.
+ */
+ BOUNCER(1) {
+ @Override
+ public void prepare(ScrimState previousState) {
+ mCurrentBehindAlpha = ScrimController.SCRIM_BEHIND_ALPHA_UNLOCKING;
+ mCurrentInFrontAlpha = ScrimController.SCRIM_IN_FRONT_ALPHA_LOCKED;
+ }
+ },
+
+ /**
+ * Changing screen brightness from quick settings.
+ */
+ BRIGHTNESS_MIRROR(2) {
+ @Override
+ public void prepare(ScrimState previousState) {
+ mCurrentBehindAlpha = 0;
+ mCurrentInFrontAlpha = 0;
+ }
+ },
+
+ /**
+ * Always on display or screen off.
+ */
+ AOD(3) {
+ @Override
+ public void prepare(ScrimState previousState) {
+ final boolean alwaysOnEnabled = mDozeParameters.getAlwaysOn();
+ final boolean wasPulsing = previousState == ScrimState.PULSING;
+ mBlankScreen = wasPulsing && !mCanControlScreenOff;
+ mCurrentBehindAlpha = mWallpaperSupportsAmbientMode
+ && !mKeyguardUpdateMonitor.hasLockscreenWallpaper() ? 0f : 1f;
+ mCurrentInFrontAlpha = alwaysOnEnabled ? mAodFrontScrimAlpha : 1f;
+ mCurrentInFrontTint = Color.BLACK;
+ mCurrentBehindTint = Color.BLACK;
+ // DisplayPowerManager will blank the screen for us, we just need
+ // to set our state.
+ mAnimateChange = mCanControlScreenOff;
+ }
+ },
+
+ /**
+ * When phone wakes up because you received a notification.
+ */
+ PULSING(4) {
+ @Override
+ public void prepare(ScrimState previousState) {
+ mCurrentInFrontAlpha = 0;
+ mCurrentInFrontTint = Color.BLACK;
+ mCurrentBehindAlpha = mWallpaperSupportsAmbientMode
+ && !mKeyguardUpdateMonitor.hasLockscreenWallpaper() ? 0f : 1f;
+ mCurrentBehindTint = Color.BLACK;
+ mBlankScreen = mDisplayRequiresBlanking;
+ }
+ },
+
+ /**
+ * Unlocked on top of an app (launcher or any other activity.)
+ */
+ UNLOCKED(5) {
+ @Override
+ public void prepare(ScrimState previousState) {
+ mCurrentBehindAlpha = 0;
+ mCurrentInFrontAlpha = 0;
+ mAnimationDuration = StatusBar.FADE_KEYGUARD_DURATION;
+
+ if (previousState == ScrimState.AOD) {
+ // Fade from black to transparent when coming directly from AOD
+ updateScrimColor(mScrimInFront, 1, Color.BLACK);
+ updateScrimColor(mScrimBehind, 1, Color.BLACK);
+ // Scrims should still be black at the end of the transition.
+ mCurrentInFrontTint = Color.BLACK;
+ mCurrentBehindTint = Color.BLACK;
+ mBlankScreen = true;
+ } else {
+ // Scrims should still be black at the end of the transition.
+ mCurrentInFrontTint = Color.TRANSPARENT;
+ mCurrentBehindTint = Color.TRANSPARENT;
+ mBlankScreen = false;
+ }
+ }
+ };
+
+ boolean mBlankScreen = false;
+ long mAnimationDuration = ScrimController.ANIMATION_DURATION;
+ int mCurrentInFrontTint = Color.TRANSPARENT;
+ int mCurrentBehindTint = Color.TRANSPARENT;
+ boolean mAnimateChange = true;
+ float mCurrentInFrontAlpha;
+ float mCurrentBehindAlpha;
+ float mAodFrontScrimAlpha;
+ float mScrimBehindAlphaKeyguard;
+ ScrimView mScrimInFront;
+ ScrimView mScrimBehind;
+ DozeParameters mDozeParameters;
+ boolean mDisplayRequiresBlanking;
+ boolean mCanControlScreenOff;
+ boolean mWallpaperSupportsAmbientMode;
+ KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ int mIndex;
+
+ ScrimState(int index) {
+ mIndex = index;
+ }
+
+ public void init(ScrimView scrimInFront, ScrimView scrimBehind, DozeParameters dozeParameters) {
+ mScrimInFront = scrimInFront;
+ mScrimBehind = scrimBehind;
+ mDozeParameters = dozeParameters;
+ mDisplayRequiresBlanking = dozeParameters.getDisplayNeedsBlanking();
+ mCanControlScreenOff = dozeParameters.getCanControlScreenOffAnimation();
+ mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(scrimInFront.getContext());
+ }
+
+ public void prepare(ScrimState previousState) {
+ }
+
+ public int getIndex() {
+ return mIndex;
+ }
+
+ public float getFrontAlpha() {
+ return mCurrentInFrontAlpha;
+ }
+
+ public float getBehindAlpha() {
+ return mCurrentBehindAlpha;
+ }
+
+ public int getFrontTint() {
+ return mCurrentInFrontTint;
+ }
+
+ public int getBehindTint() {
+ return mCurrentBehindTint;
+ }
+
+ public long getAnimationDuration() {
+ return mAnimationDuration;
+ }
+
+ public boolean getBlanksScreen() {
+ return mBlankScreen;
+ }
+
+ public void updateScrimColor(ScrimView scrim, float alpha, int tint) {
+ Trace.traceCounter(Trace.TRACE_TAG_APP,
+ scrim == mScrimInFront ? "front_scrim_alpha" : "back_scrim_alpha",
+ (int) (alpha * 255));
+
+ Trace.traceCounter(Trace.TRACE_TAG_APP,
+ scrim == mScrimInFront ? "front_scrim_tint" : "back_scrim_tint",
+ Color.alpha(tint));
+
+ scrim.setTint(tint);
+ scrim.setViewAlpha(alpha);
+ }
+
+ public boolean getAnimateChange() {
+ return mAnimateChange;
+ }
+
+ public void setAodFrontScrimAlpha(float aodFrontScrimAlpha) {
+ mAodFrontScrimAlpha = aodFrontScrimAlpha;
+ }
+
+ public void setScrimBehindAlphaKeyguard(float scrimBehindAlphaKeyguard) {
+ mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard;
+ }
+
+ public void setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode) {
+ mWallpaperSupportsAmbientMode = wallpaperSupportsAmbientMode;
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java
index 6220fcbdf13b..1130b6de2544 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java
@@ -32,6 +32,8 @@ import com.android.systemui.Interpolators;
public class SettingsButton extends AlphaOptimizedImageButton {
+ private static final boolean TUNER_ENABLE_AVAILABLE = false;
+
private static final long LONG_PRESS_LENGTH = 1000;
private static final long ACCEL_LENGTH = 750;
private static final long FULL_SPEED_LENGTH = 375;
@@ -59,7 +61,7 @@ public class SettingsButton extends AlphaOptimizedImageButton {
public boolean onTouchEvent(MotionEvent event) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
- postDelayed(mLongPressCallback, LONG_PRESS_LENGTH);
+ if (TUNER_ENABLE_AVAILABLE) postDelayed(mLongPressCallback, LONG_PRESS_LENGTH);
break;
case MotionEvent.ACTION_UP:
if (mUpToSpeed) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SignalDrawable.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SignalDrawable.java
deleted file mode 100644
index 15ef742af02e..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SignalDrawable.java
+++ /dev/null
@@ -1,517 +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.statusbar.phone;
-
-import android.animation.ArgbEvaluator;
-import android.annotation.IntRange;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.ColorFilter;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.Path.Direction;
-import android.graphics.Path.FillType;
-import android.graphics.Path.Op;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.util.LayoutDirection;
-
-import com.android.settingslib.R;
-import com.android.settingslib.Utils;
-import com.android.systemui.qs.SlashDrawable;
-
-public class SignalDrawable extends Drawable {
-
- private static final String TAG = "SignalDrawable";
-
- private static final int NUM_DOTS = 3;
-
- private static final float VIEWPORT = 24f;
- private static final float PAD = 2f / VIEWPORT;
- private static final float CUT_OUT = 7.9f / VIEWPORT;
-
- private static final float DOT_SIZE = 3f / VIEWPORT;
- private static final float DOT_PADDING = 1f / VIEWPORT;
- private static final float DOT_CUT_WIDTH = (DOT_SIZE * 3) + (DOT_PADDING * 5);
- private static final float DOT_CUT_HEIGHT = (DOT_SIZE * 1) + (DOT_PADDING * 1);
-
- private static final float[] FIT = {2.26f, -3.02f, 1.76f};
-
- // All of these are masks to push all of the drawable state into one int for easy callbacks
- // and flow through sysui.
- private static final int LEVEL_MASK = 0xff;
- private static final int NUM_LEVEL_SHIFT = 8;
- private static final int NUM_LEVEL_MASK = 0xff << NUM_LEVEL_SHIFT;
- private static final int STATE_SHIFT = 16;
- private static final int STATE_MASK = 0xff << STATE_SHIFT;
- private static final int STATE_NONE = 0;
- private static final int STATE_EMPTY = 1;
- private static final int STATE_CUT = 2;
- private static final int STATE_CARRIER_CHANGE = 3;
- private static final int STATE_AIRPLANE = 4;
-
- private static final long DOT_DELAY = 1000;
-
- private static float[][] X_PATH = new float[][]{
- {21.9f / VIEWPORT, 17.0f / VIEWPORT},
- {-1.1f / VIEWPORT, -1.1f / VIEWPORT},
- {-1.9f / VIEWPORT, 1.9f / VIEWPORT},
- {-1.9f / VIEWPORT, -1.9f / VIEWPORT},
- {-1.1f / VIEWPORT, 1.1f / VIEWPORT},
- {1.9f / VIEWPORT, 1.9f / VIEWPORT},
- {-1.9f / VIEWPORT, 1.9f / VIEWPORT},
- {1.1f / VIEWPORT, 1.1f / VIEWPORT},
- {1.9f / VIEWPORT, -1.9f / VIEWPORT},
- {1.9f / VIEWPORT, 1.9f / VIEWPORT},
- {1.1f / VIEWPORT, -1.1f / VIEWPORT},
- {-1.9f / VIEWPORT, -1.9f / VIEWPORT},
- };
-
- // Rounded corners are achieved by arcing a circle of radius `R` from its tangent points along
- // the curve (curve ≡ triangle). On the top and left corners of the triangle, the tangents are
- // as follows:
- // 1) Along the straight lines (y = 0 and x = width):
- // Ps = circleOffset + R
- // 2) Along the diagonal line (y = x):
- // Pd = √((Ps^2) / 2)
- // or (remember: sin(π/4) ≈ 0.7071)
- // Pd = (circleOffset + R - 0.7071, height - R - 0.7071)
- // Where Pd is the (x,y) coords of the point that intersects the circle at the bottom
- // left of the triangle
- private static final float RADIUS_RATIO = 0.75f / 17f;
- private static final float DIAG_OFFSET_MULTIPLIER = 0.707107f;
- // How far the circle defining the corners is inset from the edges
- private final float mAppliedCornerInset;
-
- private static final float INV_TAN = 1f / (float) Math.tan(Math.PI / 8f);
- private static final float CUT_WIDTH_DP = 1f / 12f;
-
- // Where the top and left points of the triangle would be if not for rounding
- private final PointF mVirtualTop = new PointF();
- private final PointF mVirtualLeft = new PointF();
-
- private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- private final Paint mForegroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- private final int mDarkModeBackgroundColor;
- private final int mDarkModeFillColor;
- private final int mLightModeBackgroundColor;
- private final int mLightModeFillColor;
- private final Path mFullPath = new Path();
- private final Path mForegroundPath = new Path();
- private final Path mXPath = new Path();
- // Cut out when STATE_EMPTY
- private final Path mCutPath = new Path();
- // Draws the slash when in airplane mode
- private final SlashArtist mSlash = new SlashArtist();
- private final Handler mHandler;
- private float mOldDarkIntensity = -1;
- private float mNumLevels = 1;
- private int mIntrinsicSize;
- private int mLevel;
- private int mState;
- private boolean mVisible;
- private boolean mAnimating;
- private int mCurrentDot;
-
- public SignalDrawable(Context context) {
- mDarkModeBackgroundColor =
- Utils.getDefaultColor(context, R.color.dark_mode_icon_color_dual_tone_background);
- mDarkModeFillColor =
- Utils.getDefaultColor(context, R.color.dark_mode_icon_color_dual_tone_fill);
- mLightModeBackgroundColor =
- Utils.getDefaultColor(context, R.color.light_mode_icon_color_dual_tone_background);
- mLightModeFillColor =
- Utils.getDefaultColor(context, R.color.light_mode_icon_color_dual_tone_fill);
- mIntrinsicSize = context.getResources().getDimensionPixelSize(R.dimen.signal_icon_size);
-
- mHandler = new Handler();
- setDarkIntensity(0);
-
- mAppliedCornerInset = context.getResources()
- .getDimensionPixelSize(R.dimen.stat_sys_mobile_signal_circle_inset);
- }
-
- public void setIntrinsicSize(int size) {
- mIntrinsicSize = size;
- }
-
- @Override
- public int getIntrinsicWidth() {
- return mIntrinsicSize;
- }
-
- @Override
- public int getIntrinsicHeight() {
- return mIntrinsicSize;
- }
-
- public void setNumLevels(int levels) {
- if (levels == mNumLevels) return;
- mNumLevels = levels;
- invalidateSelf();
- }
-
- private void setSignalState(int state) {
- if (state == mState) return;
- mState = state;
- updateAnimation();
- invalidateSelf();
- }
-
- private void updateAnimation() {
- boolean shouldAnimate = (mState == STATE_CARRIER_CHANGE) && mVisible;
- if (shouldAnimate == mAnimating) return;
- mAnimating = shouldAnimate;
- if (shouldAnimate) {
- mChangeDot.run();
- } else {
- mHandler.removeCallbacks(mChangeDot);
- }
- }
-
- @Override
- protected boolean onLevelChange(int state) {
- setNumLevels(getNumLevels(state));
- setSignalState(getState(state));
- int level = getLevel(state);
- if (level != mLevel) {
- mLevel = level;
- invalidateSelf();
- }
- return true;
- }
-
- public void setColors(int background, int foreground) {
- mPaint.setColor(background);
- mForegroundPaint.setColor(foreground);
- }
-
- public void setDarkIntensity(float darkIntensity) {
- if (darkIntensity == mOldDarkIntensity) {
- return;
- }
- mPaint.setColor(getBackgroundColor(darkIntensity));
- mForegroundPaint.setColor(getFillColor(darkIntensity));
- mOldDarkIntensity = darkIntensity;
- invalidateSelf();
- }
-
- private int getFillColor(float darkIntensity) {
- return getColorForDarkIntensity(
- darkIntensity, mLightModeFillColor, mDarkModeFillColor);
- }
-
- private int getBackgroundColor(float darkIntensity) {
- return getColorForDarkIntensity(
- darkIntensity, mLightModeBackgroundColor, mDarkModeBackgroundColor);
- }
-
- private int getColorForDarkIntensity(float darkIntensity, int lightColor, int darkColor) {
- return (int) ArgbEvaluator.getInstance().evaluate(darkIntensity, lightColor, darkColor);
- }
-
- @Override
- protected void onBoundsChange(Rect bounds) {
- super.onBoundsChange(bounds);
- invalidateSelf();
- }
-
- @Override
- public void draw(@NonNull Canvas canvas) {
- final float width = getBounds().width();
- final float height = getBounds().height();
-
- boolean isRtl = getLayoutDirection() == LayoutDirection.RTL;
- if (isRtl) {
- canvas.save();
- // Mirror the drawable
- canvas.translate(width, 0);
- canvas.scale(-1.0f, 1.0f);
- }
- mFullPath.reset();
- mFullPath.setFillType(FillType.WINDING);
-
- final float padding = Math.round(PAD * width);
- final float cornerRadius = RADIUS_RATIO * height;
- // Offset from circle where the hypotenuse meets the circle
- final float diagOffset = DIAG_OFFSET_MULTIPLIER * cornerRadius;
-
- // 1 - Bottom right, above corner
- mFullPath.moveTo(width - padding, height - padding - cornerRadius);
- // 2 - Line to top right, below corner
- mFullPath.lineTo(width - padding, padding + cornerRadius + mAppliedCornerInset);
- // 3 - Arc to top right, on hypotenuse
- mFullPath.arcTo(
- width - padding - (2 * cornerRadius),
- padding + mAppliedCornerInset,
- width - padding,
- padding + mAppliedCornerInset + (2 * cornerRadius),
- 0.f, -135.f, false
- );
- // 4 - Line to bottom left, on hypotenuse
- mFullPath.lineTo(padding + mAppliedCornerInset + cornerRadius - diagOffset,
- height - padding - cornerRadius - diagOffset);
- // 5 - Arc to bottom left, on leg
- mFullPath.arcTo(
- padding + mAppliedCornerInset,
- height - padding - (2 * cornerRadius),
- padding + mAppliedCornerInset + ( 2 * cornerRadius),
- height - padding,
- -135.f, -135.f, false
- );
- // 6 - Line to bottom rght, before corner
- mFullPath.lineTo(width - padding - cornerRadius, height - padding);
- // 7 - Arc to beginning (bottom right, above corner)
- mFullPath.arcTo(
- width - padding - (2 * cornerRadius),
- height - padding - (2 * cornerRadius),
- width - padding,
- height - padding,
- 90.f, -90.f, false
- );
-
- if (mState == STATE_CARRIER_CHANGE) {
- float cutWidth = (DOT_CUT_WIDTH * width);
- float cutHeight = (DOT_CUT_HEIGHT * width);
- float dotSize = (DOT_SIZE * height);
- float dotPadding = (DOT_PADDING * height);
-
- mFullPath.moveTo(width - padding, height - padding);
- mFullPath.rLineTo(-cutWidth, 0);
- mFullPath.rLineTo(0, -cutHeight);
- mFullPath.rLineTo(cutWidth, 0);
- mFullPath.rLineTo(0, cutHeight);
- float dotSpacing = dotPadding * 2 + dotSize;
- float x = width - padding - dotSize;
- float y = height - padding - dotSize;
- mForegroundPath.reset();
- drawDot(mFullPath, mForegroundPath, x, y, dotSize, 2);
- drawDot(mFullPath, mForegroundPath, x - dotSpacing, y, dotSize, 1);
- drawDot(mFullPath, mForegroundPath, x - dotSpacing * 2, y, dotSize, 0);
- } else if (mState == STATE_CUT) {
- float cut = (CUT_OUT * width);
- mFullPath.moveTo(width - padding, height - padding);
- mFullPath.rLineTo(-cut, 0);
- mFullPath.rLineTo(0, -cut);
- mFullPath.rLineTo(cut, 0);
- mFullPath.rLineTo(0, cut);
- }
-
- if (mState == STATE_EMPTY) {
- // Where the corners would be if this were a real triangle
- mVirtualTop.set(
- width - padding,
- (padding + cornerRadius + mAppliedCornerInset) - (INV_TAN * cornerRadius));
- mVirtualLeft.set(
- (padding + cornerRadius + mAppliedCornerInset) - (INV_TAN * cornerRadius),
- height - padding);
-
- final float cutWidth = CUT_WIDTH_DP * height;
- final float cutDiagInset = cutWidth * INV_TAN;
-
- // Cut out a smaller triangle from the center of mFullPath
- mCutPath.reset();
- mCutPath.setFillType(FillType.WINDING);
- mCutPath.moveTo(width - padding - cutWidth, height - padding - cutWidth);
- mCutPath.lineTo(width - padding - cutWidth, mVirtualTop.y + cutDiagInset);
- mCutPath.lineTo(mVirtualLeft.x + cutDiagInset, height - padding - cutWidth);
- mCutPath.lineTo(width - padding - cutWidth, height - padding - cutWidth);
-
- // Draw empty state as only background
- mForegroundPath.reset();
- mFullPath.op(mCutPath, Path.Op.DIFFERENCE);
- } else if (mState == STATE_AIRPLANE) {
- // Airplane mode is slashed, fully drawn background
- mForegroundPath.reset();
- mSlash.draw((int) height, (int) width, canvas, mPaint);
- } else if (mState != STATE_CARRIER_CHANGE) {
- mForegroundPath.reset();
- int sigWidth = Math.round(calcFit(mLevel / (mNumLevels - 1)) * (width - 2 * padding));
- mForegroundPath.addRect(padding, padding, padding + sigWidth, height - padding,
- Direction.CW);
- mForegroundPath.op(mFullPath, Op.INTERSECT);
- }
-
- canvas.drawPath(mFullPath, mPaint);
- canvas.drawPath(mForegroundPath, mForegroundPaint);
- if (mState == STATE_CUT) {
- mXPath.reset();
- mXPath.moveTo(X_PATH[0][0] * width, X_PATH[0][1] * height);
- for (int i = 1; i < X_PATH.length; i++) {
- mXPath.rLineTo(X_PATH[i][0] * width, X_PATH[i][1] * height);
- }
- canvas.drawPath(mXPath, mForegroundPaint);
- }
- if (isRtl) {
- canvas.restore();
- }
- }
-
- private void drawDot(Path fullPath, Path foregroundPath, float x, float y, float dotSize,
- int i) {
- Path p = (i == mCurrentDot) ? foregroundPath : fullPath;
- p.addRect(x, y, x + dotSize, y + dotSize, Direction.CW);
- }
-
- // This is a fit line based on previous values of provided in assets, but if
- // you look at the a plot of this actual fit, it makes a lot of sense, what it does
- // is compress the areas that are very visually easy to see changes (the middle sections)
- // and spread out the sections that are hard to see (each end of the icon).
- // The current fit is cubic, but pretty easy to change the way the code is written (just add
- // terms to the end of FIT).
- private float calcFit(float v) {
- float ret = 0;
- float t = v;
- for (int i = 0; i < FIT.length; i++) {
- ret += FIT[i] * t;
- t *= v;
- }
- return ret;
- }
-
- @Override
- public int getAlpha() {
- return mPaint.getAlpha();
- }
-
- @Override
- public void setAlpha(@IntRange(from = 0, to = 255) int alpha) {
- mPaint.setAlpha(alpha);
- mForegroundPaint.setAlpha(alpha);
- }
-
- @Override
- public void setColorFilter(@Nullable ColorFilter colorFilter) {
- mPaint.setColorFilter(colorFilter);
- mForegroundPaint.setColorFilter(colorFilter);
- }
-
- @Override
- public int getOpacity() {
- return 255;
- }
-
- @Override
- public boolean setVisible(boolean visible, boolean restart) {
- mVisible = visible;
- updateAnimation();
- return super.setVisible(visible, restart);
- }
-
- private final Runnable mChangeDot = new Runnable() {
- @Override
- public void run() {
- if (++mCurrentDot == NUM_DOTS) {
- mCurrentDot = 0;
- }
- invalidateSelf();
- mHandler.postDelayed(mChangeDot, DOT_DELAY);
- }
- };
-
- public static int getLevel(int fullState) {
- return fullState & LEVEL_MASK;
- }
-
- public static int getState(int fullState) {
- return (fullState & STATE_MASK) >> STATE_SHIFT;
- }
-
- public static int getNumLevels(int fullState) {
- return (fullState & NUM_LEVEL_MASK) >> NUM_LEVEL_SHIFT;
- }
-
- public static int getState(int level, int numLevels, boolean cutOut) {
- return ((cutOut ? STATE_CUT : 0) << STATE_SHIFT)
- | (numLevels << NUM_LEVEL_SHIFT)
- | level;
- }
-
- public static int getCarrierChangeState(int numLevels) {
- return (STATE_CARRIER_CHANGE << STATE_SHIFT) | (numLevels << NUM_LEVEL_SHIFT);
- }
-
- public static int getEmptyState(int numLevels) {
- return (STATE_EMPTY << STATE_SHIFT) | (numLevels << NUM_LEVEL_SHIFT);
- }
-
- public static int getAirplaneModeState(int numLevels) {
- return (STATE_AIRPLANE << STATE_SHIFT) | (numLevels << NUM_LEVEL_SHIFT);
- }
-
- private final class SlashArtist {
- // These values are derived in un-rotated (vertical) orientation
- private static final float SLASH_WIDTH = 1.8384776f;
- private static final float SLASH_HEIGHT = 22f;
- private static final float CENTER_X = 10.65f;
- private static final float CENTER_Y = 15.869239f;
- private static final float SCALE = 24f;
-
- // Bottom is derived during animation
- private static final float LEFT = (CENTER_X - (SLASH_WIDTH / 2)) / SCALE;
- private static final float TOP = (CENTER_Y - (SLASH_HEIGHT / 2)) / SCALE;
- private static final float RIGHT = (CENTER_X + (SLASH_WIDTH / 2)) / SCALE;
- private static final float BOTTOM = (CENTER_Y + (SLASH_HEIGHT / 2)) / SCALE;
- // Draw the slash washington-monument style; rotate to no-u-turn style
- private static final float ROTATION = -45f;
-
- private final Path mPath = new Path();
- private final RectF mSlashRect = new RectF();
-
- void draw(int height, int width, @NonNull Canvas canvas, Paint paint) {
- Matrix m = new Matrix();
- final float radius = scale(SlashDrawable.CORNER_RADIUS, width);
- updateRect(
- scale(LEFT, width),
- scale(TOP, height),
- scale(RIGHT, width),
- scale(BOTTOM, height));
-
- mPath.reset();
- // Draw the slash vertically
- mPath.addRoundRect(mSlashRect, radius, radius, Direction.CW);
- m.setRotate(ROTATION, width / 2, height / 2);
- mPath.transform(m);
- canvas.drawPath(mPath, paint);
-
- // Rotate back to vertical, and draw the cut-out rect next to this one
- m.setRotate(-ROTATION, width / 2, height / 2);
- mPath.transform(m);
- m.setTranslate(mSlashRect.width(), 0);
- mPath.transform(m);
- mPath.addRoundRect(mSlashRect, radius, radius, Direction.CW);
- m.setRotate(ROTATION, width / 2, height / 2);
- mPath.transform(m);
- canvas.clipOutPath(mPath);
- }
-
- void updateRect(float left, float top, float right, float bottom) {
- mSlashRect.left = left;
- mSlashRect.top = top;
- mSlashRect.right = right;
- mSlashRect.bottom = bottom;
- }
-
- private float scale(float frac, int width) {
- return frac * width;
- }
- }
-}
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 42a35c1d409d..3b63d6c39146 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -19,11 +19,15 @@ package com.android.systemui.statusbar.phone;
import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
import static android.app.StatusBarManager.windowStateToString;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
-import static com.android.systemui.statusbar.notification.NotificationInflater.InflationCallback;
+import static com.android.systemui.statusbar.NotificationLockscreenUserManager
+ .NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION;
+import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF;
+import static com.android.systemui.statusbar.NotificationMediaManager.DEBUG_MEDIA;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE;
@@ -37,18 +41,16 @@ import android.animation.AnimatorListenerAdapter;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
-import android.app.ActivityManager.StackId;
import android.app.ActivityOptions;
-import android.app.INotificationManager;
+import android.app.AlarmManager;
import android.app.KeyguardManager;
import android.app.Notification;
-import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
-import android.app.RemoteInput;
import android.app.StatusBarManager;
import android.app.TaskStackBuilder;
import android.app.WallpaperColors;
+import android.app.WallpaperInfo;
import android.app.WallpaperManager;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
@@ -60,18 +62,13 @@ import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.om.IOverlayManager;
import android.content.om.OverlayInfo;
-import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.database.ContentObserver;
import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.ColorFilter;
-import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.PorterDuff;
@@ -82,17 +79,13 @@ import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.media.AudioAttributes;
import android.media.MediaMetadata;
-import android.media.session.MediaController;
-import android.media.session.MediaSession;
-import android.media.session.MediaSessionManager;
-import android.media.session.PlaybackState;
import android.metrics.LogMaker;
import android.net.Uri;
import android.os.AsyncTask;
-import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteException;
@@ -104,41 +97,32 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.os.Vibrator;
import android.provider.Settings;
-import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.StatusBarNotification;
import android.service.vr.IVrManager;
import android.service.vr.IVrStateCallbacks;
import android.text.TextUtils;
-import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.SparseBooleanArray;
import android.view.Display;
-import android.view.HapticFeedbackConstants;
import android.view.IWindowManager;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.ThreadedRenderer;
import android.view.View;
-import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
import android.view.ViewParent;
-import android.view.ViewStub;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityManager;
import android.view.animation.AccelerateInterpolator;
-import android.view.animation.Interpolator;
import android.widget.DateTimeView;
import android.widget.ImageView;
-import android.widget.RemoteViews;
import android.widget.TextView;
-import android.widget.Toast;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.colorextraction.ColorExtractor;
@@ -146,30 +130,28 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.statusbar.IStatusBarService;
-import com.android.internal.statusbar.NotificationVisibility;
import com.android.internal.statusbar.StatusBarIcon;
-import com.android.internal.util.NotificationMessagingUtil;
import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.MessagingGroup;
+import com.android.internal.widget.MessagingMessage;
import com.android.keyguard.KeyguardHostView.OnDismissAction;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.ActivityStarterDelegate;
import com.android.systemui.AutoReinflateContainer;
-import com.android.systemui.DejankUtils;
import com.android.systemui.DemoMode;
import com.android.systemui.Dependency;
import com.android.systemui.EventLogTags;
-import com.android.systemui.ForegroundServiceController;
import com.android.systemui.Interpolators;
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.RecentsComponent;
-import com.android.systemui.SwipeHelper;
import com.android.systemui.SystemUI;
import com.android.systemui.SystemUIFactory;
import com.android.systemui.UiOffloadThread;
import com.android.systemui.assist.AssistManager;
+import com.android.systemui.charging.WirelessChargingAnimation;
import com.android.systemui.classifier.FalsingLog;
import com.android.systemui.classifier.FalsingManager;
import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -183,7 +165,6 @@ import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.QS;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
import com.android.systemui.qs.QSFragment;
import com.android.systemui.qs.QSPanel;
@@ -200,6 +181,7 @@ import com.android.systemui.stackdivider.WindowManagerProxy;
import com.android.systemui.statusbar.ActivatableNotificationView;
import com.android.systemui.statusbar.BackDropView;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.DismissView;
import com.android.systemui.statusbar.DragDownHelper;
import com.android.systemui.statusbar.EmptyShadeView;
@@ -209,18 +191,25 @@ import com.android.systemui.statusbar.KeyboardShortcuts;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.NotificationData.Entry;
-import com.android.systemui.statusbar.NotificationGuts;
+import com.android.systemui.statusbar.NotificationEntryManager;
+import com.android.systemui.statusbar.NotificationGutsManager;
import com.android.systemui.statusbar.NotificationInfo;
+import com.android.systemui.statusbar.NotificationListener;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.NotificationLogger;
+import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.NotificationPresenter;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShelf;
-import com.android.systemui.statusbar.NotificationSnooze;
+import com.android.systemui.statusbar.NotificationViewHierarchyManager;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.ScrimView;
import com.android.systemui.statusbar.SignalClusterView;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.AboveShelfObserver;
-import com.android.systemui.statusbar.notification.InflationException;
-import com.android.systemui.statusbar.notification.RowInflaterTask;
+import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.UnlockMethodCache.OnUnlockMethodChangedListener;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
@@ -232,22 +221,18 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
import com.android.systemui.statusbar.policy.ExtensionController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.policy.HeadsUpUtil;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
import com.android.systemui.statusbar.policy.KeyguardMonitorImpl;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.statusbar.policy.PreviewInflater;
-import com.android.systemui.statusbar.policy.RemoteInputView;
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
-import com.android.systemui.statusbar.stack.NotificationStackScrollLayout
- .OnChildLocationsChangedListener;
-import com.android.systemui.statusbar.stack.StackStateAnimator;
import com.android.systemui.util.NotificationChannels;
-import com.android.systemui.util.leak.LeakDetector;
import com.android.systemui.volume.VolumeComponent;
import java.io.FileDescriptor;
@@ -255,48 +240,24 @@ import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
-import java.util.Locale;
import java.util.Map;
-import java.util.Set;
-import java.util.Stack;
public class StatusBar extends SystemUI implements DemoMode,
DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener,
- OnHeadsUpChangedListener, VisualStabilityManager.Callback, CommandQueue.Callbacks,
- ActivatableNotificationView.OnActivatedListener,
- ExpandableNotificationRow.ExpansionLogger, NotificationData.Environment,
- ExpandableNotificationRow.OnExpandClickListener, InflationCallback,
- ColorExtractor.OnColorsChangedListener, ConfigurationListener {
+ OnHeadsUpChangedListener, CommandQueue.Callbacks,
+ ColorExtractor.OnColorsChangedListener, ConfigurationListener, NotificationPresenter {
public static final boolean MULTIUSER_DEBUG = false;
- public static final boolean ENABLE_REMOTE_INPUT =
- SystemProperties.getBoolean("debug.enable_remote_input", true);
public static final boolean ENABLE_CHILD_NOTIFICATIONS
= SystemProperties.getBoolean("debug.child_notifs", true);
- public static final boolean FORCE_REMOTE_INPUT_HISTORY =
- SystemProperties.getBoolean("debug.force_remoteinput_history", false);
- private static boolean ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT = false;
- protected static final int MSG_SHOW_RECENT_APPS = 1019;
protected static final int MSG_HIDE_RECENT_APPS = 1020;
- protected static final int MSG_TOGGLE_RECENTS_APPS = 1021;
protected static final int MSG_PRELOAD_RECENT_APPS = 1022;
protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023;
protected static final int MSG_TOGGLE_KEYBOARD_SHORTCUTS_MENU = 1026;
protected static final int MSG_DISMISS_KEYBOARD_SHORTCUTS_MENU = 1027;
- protected static final boolean ENABLE_HEADS_UP = true;
- protected static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up";
-
- // Must match constant in Settings. Used to highlight preferences when linking to Settings.
- private static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
-
- private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
-
// Should match the values in PhoneWindowManager
public static final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey";
public static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
@@ -306,14 +267,11 @@ public class StatusBar extends SystemUI implements DemoMode,
"com.android.systemui.statusbar.banner_action_cancel";
private static final String BANNER_ACTION_SETUP =
"com.android.systemui.statusbar.banner_action_setup";
- private static final String NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION
- = "com.android.systemui.statusbar.work_challenge_unlocked_notification_action";
public static final String TAG = "StatusBar";
public static final boolean DEBUG = false;
public static final boolean SPEW = false;
public static final boolean DUMPTRUCK = true; // extra dumpsys info
public static final boolean DEBUG_GESTURES = false;
- public static final boolean DEBUG_MEDIA = false;
public static final boolean DEBUG_MEDIA_FAKE_ARTWORK = false;
public static final boolean DEBUG_CAMERA_LIFT = false;
@@ -335,15 +293,12 @@ public class StatusBar extends SystemUI implements DemoMode,
// Time after we abort the launch transition.
private static final long LAUNCH_TRANSITION_TIMEOUT_MS = 5000;
- private static final boolean CLOSE_PANEL_WHEN_EMPTIED = true;
+ protected static final boolean CLOSE_PANEL_WHEN_EMPTIED = true;
private static final int STATUS_OR_NAV_TRANSIENT =
View.STATUS_BAR_TRANSIENT | View.NAVIGATION_BAR_TRANSIENT;
private static final long AUTOHIDE_TIMEOUT_MS = 2250;
- /** The minimum delay in ms between reports of notification visibility. */
- private static final int VISIBILITY_REPORT_MIN_DELAY_MS = 500;
-
/**
* The delay to reset the hint text when the hint animation is finished running.
*/
@@ -365,18 +320,6 @@ public class StatusBar extends SystemUI implements DemoMode,
/** If true, the lockscreen will show a distinct wallpaper */
private static final boolean ENABLE_LOCKSCREEN_WALLPAPER = true;
- /* If true, the device supports freeform window management.
- * This affects the status bar UI. */
- private static final boolean FREEFORM_WINDOW_MANAGEMENT;
-
- /**
- * How long to wait before auto-dismissing a notification that was kept for remote input, and
- * has now sent a remote input. We auto-dismiss, because the app may not see a reason to cancel
- * these given that they technically don't exist anymore. We wait a bit in case the app issues
- * an update.
- */
- private static final int REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY = 200;
-
/**
* Never let the alpha become zero for surfaces that draw with SRC - otherwise the RenderNode
* won't draw anything and uninitialized memory will show through
@@ -387,19 +330,14 @@ public class StatusBar extends SystemUI implements DemoMode,
static {
boolean onlyCoreApps;
- boolean freeformWindowManagement;
try {
IPackageManager packageManager =
IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
onlyCoreApps = packageManager.isOnlyCoreApps();
- freeformWindowManagement = packageManager.hasSystemFeature(
- PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT, 0);
} catch (RemoteException e) {
onlyCoreApps = false;
- freeformWindowManagement = false;
}
ONLY_CORE_APPS = onlyCoreApps;
- FREEFORM_WINDOW_MANAGEMENT = freeformWindowManagement;
}
/**
@@ -407,20 +345,19 @@ public class StatusBar extends SystemUI implements DemoMode,
*/
protected int mState;
protected boolean mBouncerShowing;
- protected boolean mShowLockscreenNotifications;
- protected boolean mAllowLockscreenRemoteInput;
- PhoneStatusBarPolicy mIconPolicy;
+ private PhoneStatusBarPolicy mIconPolicy;
- VolumeComponent mVolumeComponent;
- BrightnessMirrorController mBrightnessMirrorController;
+ private VolumeComponent mVolumeComponent;
+ private BrightnessMirrorController mBrightnessMirrorController;
+ private boolean mBrightnessMirrorVisible;
protected FingerprintUnlockController mFingerprintUnlockController;
- LightBarController mLightBarController;
+ private LightBarController mLightBarController;
protected LockscreenWallpaper mLockscreenWallpaper;
- int mNaturalBarHeight = -1;
+ private int mNaturalBarHeight = -1;
- Point mCurrentDisplaySize = new Point();
+ private final Point mCurrentDisplaySize = new Point();
protected StatusBarWindowView mStatusBarWindow;
protected PhoneStatusBarView mStatusBarView;
@@ -431,33 +368,22 @@ public class StatusBar extends SystemUI implements DemoMode,
private boolean mWakeUpComingFromTouch;
private PointF mWakeUpTouchLocation;
- int mPixelFormat;
- Object mQueueLock = new Object();
+ private final Object mQueueLock = new Object();
protected StatusBarIconController mIconController;
// expanded notifications
protected NotificationPanelView mNotificationPanel; // the sliding/resizing panel within the notification window
- View mExpandedContents;
- TextView mNotificationPanelDebugText;
-
- /**
- * {@code true} if notifications not part of a group should by default be rendered in their
- * expanded state. If {@code false}, then only the first notification will be expanded if
- * possible.
- */
- private boolean mAlwaysExpandNonGroupedNotification;
+ private TextView mNotificationPanelDebugText;
// settings
private QSPanel mQSPanel;
// top bar
- protected KeyguardStatusBarView mKeyguardStatusBar;
- boolean mLeaveOpenOnKeyguardHide;
+ private KeyguardStatusBarView mKeyguardStatusBar;
+ private boolean mLeaveOpenOnKeyguardHide;
KeyguardIndicationController mKeyguardIndicationController;
- // Keyguard is going away soon.
- private boolean mKeyguardGoingAway;
// Keyguard is actually fading away now.
protected boolean mKeyguardFadingAway;
protected long mKeyguardFadingAwayDelay;
@@ -469,25 +395,31 @@ public class StatusBar extends SystemUI implements DemoMode,
private View mReportRejectedTouch;
- int mMaxAllowedKeyguardNotifications;
+ private int mMaxAllowedKeyguardNotifications;
- boolean mExpandedVisible;
+ private boolean mExpandedVisible;
- // the tracker view
- int mTrackingPosition; // the position of the top of the tracking view.
+ private final int[] mAbsPos = new int[2];
+ private final ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>();
- // Tracking finger for opening/closing.
- boolean mTracking;
+ private NotificationGutsManager mGutsManager;
+ protected NotificationLogger mNotificationLogger;
+ protected NotificationEntryManager mEntryManager;
+ protected NotificationViewHierarchyManager mViewHierarchyManager;
- int[] mAbsPos = new int[2];
- ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>();
+ /**
+ * Helper that is responsible for showing the right toast when a disallowed activity operation
+ * occurred. In pinned mode, we show instructions on how to break out of this mode, whilst in
+ * fully locked mode we only show that unlocking is blocked.
+ */
+ private ScreenPinningNotify mScreenPinningNotify;
// for disabling the status bar
- int mDisabled1 = 0;
- int mDisabled2 = 0;
+ private int mDisabled1 = 0;
+ private int mDisabled2 = 0;
// tracking calls to View.setSystemUiVisibility()
- int mSystemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE;
+ private int mSystemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE;
private final Rect mLastFullscreenStackBounds = new Rect();
private final Rect mLastDockedStackBounds = new Rect();
private final Rect mTmpRect = new Rect();
@@ -495,7 +427,7 @@ public class StatusBar extends SystemUI implements DemoMode,
// last value sent to window manager
private int mLastDispatchedSystemUiVisibility = ~View.SYSTEM_UI_FLAG_VISIBLE;
- DisplayMetrics mDisplayMetrics = new DisplayMetrics();
+ private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
// XXX: gesture research
private final GestureRecorder mGestureRec = DEBUG_GESTURES
@@ -507,14 +439,17 @@ public class StatusBar extends SystemUI implements DemoMode,
private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
// ensure quick settings is disabled until the current user makes it through the setup wizard
- private boolean mUserSetup = false;
- private DeviceProvisionedListener mUserSetupObserver = new DeviceProvisionedListener() {
+ @VisibleForTesting
+ protected boolean mUserSetup = false;
+ private final DeviceProvisionedListener mUserSetupObserver = new DeviceProvisionedListener() {
@Override
public void onUserSetupChanged() {
final boolean userSetup = mDeviceProvisionedController.isUserSetup(
mDeviceProvisionedController.getCurrentUser());
- if (MULTIUSER_DEBUG) Log.d(TAG, String.format("User setup changed: " +
- "userSetup=%s mUserSetup=%s", userSetup, mUserSetup));
+ if (MULTIUSER_DEBUG) {
+ Log.d(TAG, String.format("User setup changed: userSetup=%s mUserSetup=%s",
+ userSetup, mUserSetup));
+ }
if (userSetup != mUserSetup) {
mUserSetup = userSetup;
@@ -528,26 +463,7 @@ public class StatusBar extends SystemUI implements DemoMode,
}
};
- protected H mHandler = createHandler();
- final private ContentObserver mHeadsUpObserver = new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- boolean wasUsing = mUseHeadsUp;
- mUseHeadsUp = ENABLE_HEADS_UP && !mDisableNotificationAlerts
- && Settings.Global.HEADS_UP_OFF != Settings.Global.getInt(
- mContext.getContentResolver(), Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
- Settings.Global.HEADS_UP_OFF);
- mHeadsUpTicker = mUseHeadsUp && 0 != Settings.Global.getInt(
- mContext.getContentResolver(), SETTING_HEADS_UP_TICKER, 0);
- Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled"));
- if (wasUsing != mUseHeadsUp) {
- if (!mUseHeadsUp) {
- Log.d(TAG, "dismissing any existing heads up notification on disable event");
- mHeadsUpManager.releaseAllImmediately();
- }
- }
- }
- };
+ protected final H mHandler = createHandler();
private int mInteractingWindows;
private boolean mAutohideSuspended;
@@ -566,63 +482,37 @@ public class StatusBar extends SystemUI implements DemoMode,
}
};
- private boolean mWaitingForKeyguardExit;
protected boolean mDozing;
private boolean mDozingRequested;
protected boolean mScrimSrcModeEnabled;
- public static final Interpolator ALPHA_IN = Interpolators.ALPHA_IN;
- public static final Interpolator ALPHA_OUT = Interpolators.ALPHA_OUT;
-
protected BackDropView mBackdrop;
protected ImageView mBackdropFront, mBackdropBack;
- protected PorterDuffXfermode mSrcXferMode = new PorterDuffXfermode(PorterDuff.Mode.SRC);
- protected PorterDuffXfermode mSrcOverXferMode =
+ protected final PorterDuffXfermode mSrcXferMode = new PorterDuffXfermode(PorterDuff.Mode.SRC);
+ protected final PorterDuffXfermode mSrcOverXferMode =
new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER);
- private MediaSessionManager mMediaSessionManager;
- private MediaController mMediaController;
- private String mMediaNotificationKey;
- private MediaMetadata mMediaMetadata;
- private MediaController.Callback mMediaListener
- = new MediaController.Callback() {
+ private NotificationMediaManager mMediaManager;
+ protected NotificationLockscreenUserManager mLockscreenUserManager;
+ protected NotificationRemoteInputManager mRemoteInputManager;
+
+ private BroadcastReceiver mWallpaperChangedReceiver = new BroadcastReceiver() {
@Override
- public void onPlaybackStateChanged(PlaybackState state) {
- super.onPlaybackStateChanged(state);
- if (DEBUG_MEDIA) Log.v(TAG, "DEBUG_MEDIA: onPlaybackStateChanged: " + state);
- if (state != null) {
- if (!isPlaybackActive(state.getState())) {
- clearCurrentMediaNotification();
- updateMediaMetaData(true, true);
- }
+ public void onReceive(Context context, Intent intent) {
+ WallpaperManager wallpaperManager = context.getSystemService(WallpaperManager.class);
+ if (wallpaperManager == null) {
+ Log.w(TAG, "WallpaperManager not available");
+ return;
}
- }
+ WallpaperInfo info = wallpaperManager.getWallpaperInfo();
+ final boolean supportsAmbientMode = info != null &&
+ info.getSupportsAmbientMode();
- @Override
- public void onMetadataChanged(MediaMetadata metadata) {
- super.onMetadataChanged(metadata);
- if (DEBUG_MEDIA) Log.v(TAG, "DEBUG_MEDIA: onMetadataChanged: " + metadata);
- mMediaMetadata = metadata;
- updateMediaMetaData(true, true);
+ mStatusBarWindowManager.setWallpaperSupportsAmbientMode(supportsAmbientMode);
+ mScrimController.setWallpaperSupportsAmbientMode(supportsAmbientMode);
}
};
- private final OnChildLocationsChangedListener mOnChildLocationsChangedListener =
- new OnChildLocationsChangedListener() {
- @Override
- public void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout) {
- userActivity();
- }
- };
-
- private int mDisabledUnmodified1;
- private int mDisabledUnmodified2;
-
- /** Keys of notifications currently visible to the user. */
- private final ArraySet<NotificationVisibility> mCurrentlyVisibleNotifications =
- new ArraySet<>();
- private long mLastVisibilityReportUptimeMs;
-
private Runnable mLaunchTransitionEndRunnable;
protected boolean mLaunchTransitionFadingAway;
private ExpandableNotificationRow mDraggedDownRow;
@@ -644,94 +534,29 @@ public class StatusBar extends SystemUI implements DemoMode,
private boolean mWereIconsJustHidden;
private boolean mBouncerWasShowingWhenHidden;
- public boolean isStartedGoingToSleep() {
- return mStartedGoingToSleep;
- }
-
- /**
- * If set, the device has started going to sleep but isn't fully non-interactive yet.
- */
- protected boolean mStartedGoingToSleep;
-
- private final OnChildLocationsChangedListener mNotificationLocationsChangedListener =
- new OnChildLocationsChangedListener() {
- @Override
- public void onChildLocationsChanged(
- NotificationStackScrollLayout stackScrollLayout) {
- if (mHandler.hasCallbacks(mVisibilityReporter)) {
- // Visibilities will be reported when the existing
- // callback is executed.
- return;
- }
- // Calculate when we're allowed to run the visibility
- // reporter. Note that this timestamp might already have
- // passed. That's OK, the callback will just be executed
- // ASAP.
- long nextReportUptimeMs =
- mLastVisibilityReportUptimeMs + VISIBILITY_REPORT_MIN_DELAY_MS;
- mHandler.postAtTime(mVisibilityReporter, nextReportUptimeMs);
- }
- };
-
- // Tracks notifications currently visible in mNotificationStackScroller and
- // emits visibility events via NoMan on changes.
- protected final Runnable mVisibilityReporter = new Runnable() {
- private final ArraySet<NotificationVisibility> mTmpNewlyVisibleNotifications =
- new ArraySet<>();
- private final ArraySet<NotificationVisibility> mTmpCurrentlyVisibleNotifications =
- new ArraySet<>();
- private final ArraySet<NotificationVisibility> mTmpNoLongerVisibleNotifications =
- new ArraySet<>();
-
+ // Notifies StatusBarKeyguardViewManager every time the keyguard transition is over,
+ // this animation is tied to the scrim for historic reasons.
+ // TODO: notify when keyguard has faded away instead of the scrim.
+ private final ScrimController.Callback mUnlockScrimCallback = new ScrimController
+ .Callback() {
@Override
- public void run() {
- mLastVisibilityReportUptimeMs = SystemClock.uptimeMillis();
- final String mediaKey = getCurrentMediaNotificationKey();
-
- // 1. Loop over mNotificationData entries:
- // A. Keep list of visible notifications.
- // B. Keep list of previously hidden, now visible notifications.
- // 2. Compute no-longer visible notifications by removing currently
- // visible notifications from the set of previously visible
- // notifications.
- // 3. Report newly visible and no-longer visible notifications.
- // 4. Keep currently visible notifications for next report.
- ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
- int N = activeNotifications.size();
- for (int i = 0; i < N; i++) {
- Entry entry = activeNotifications.get(i);
- String key = entry.notification.getKey();
- boolean isVisible = mStackScroller.isInVisibleLocation(entry.row);
- NotificationVisibility visObj = NotificationVisibility.obtain(key, i, isVisible);
- boolean previouslyVisible = mCurrentlyVisibleNotifications.contains(visObj);
- if (isVisible) {
- // Build new set of visible notifications.
- mTmpCurrentlyVisibleNotifications.add(visObj);
- if (!previouslyVisible) {
- mTmpNewlyVisibleNotifications.add(visObj);
- }
- } else {
- // release object
- visObj.recycle();
- }
+ public void onFinished() {
+ if (mStatusBarKeyguardViewManager == null) {
+ Log.w(TAG, "Tried to notify keyguard visibility when "
+ + "mStatusBarKeyguardViewManager was null");
+ return;
}
- mTmpNoLongerVisibleNotifications.addAll(mCurrentlyVisibleNotifications);
- mTmpNoLongerVisibleNotifications.removeAll(mTmpCurrentlyVisibleNotifications);
-
- logNotificationVisibilityChanges(
- mTmpNewlyVisibleNotifications, mTmpNoLongerVisibleNotifications);
-
- recycleAllVisibilityObjects(mCurrentlyVisibleNotifications);
- mCurrentlyVisibleNotifications.addAll(mTmpCurrentlyVisibleNotifications);
+ if (mKeyguardFadingAway) {
+ mStatusBarKeyguardViewManager.onKeyguardFadedAway();
+ }
+ }
- recycleAllVisibilityObjects(mTmpNoLongerVisibleNotifications);
- mTmpCurrentlyVisibleNotifications.clear();
- mTmpNewlyVisibleNotifications.clear();
- mTmpNoLongerVisibleNotifications.clear();
+ @Override
+ public void onCancelled() {
+ onFinished();
}
};
- private NotificationMessagingUtil mMessagingUtil;
private KeyguardUserSwitcher mKeyguardUserSwitcher;
private UserSwitcherController mUserSwitcherController;
private NetworkController mNetworkController;
@@ -743,52 +568,46 @@ public class StatusBar extends SystemUI implements DemoMode,
private boolean mKeyguardRequested;
private boolean mIsKeyguard;
private LogMaker mStatusBarStateLog;
- private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
+ private final LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
protected NotificationIconAreaController mNotificationIconAreaController;
private boolean mReinflateNotificationsOnUserSwitched;
- private HashMap<String, Entry> mPendingNotifications = new HashMap<>();
private boolean mClearAllEnabled;
@Nullable private View mAmbientIndicationContainer;
- private String mKeyToRemoveOnGutsClosed;
private SysuiColorExtractor mColorExtractor;
- private ForegroundServiceController mForegroundServiceController;
private ScreenLifecycle mScreenLifecycle;
@VisibleForTesting WakefulnessLifecycle mWakefulnessLifecycle;
- private void recycleAllVisibilityObjects(ArraySet<NotificationVisibility> array) {
- final int N = array.size();
- for (int i = 0 ; i < N; i++) {
- array.valueAt(i).recycle();
- }
- array.clear();
- }
-
private final View.OnClickListener mGoToLockedShadeListener = v -> {
if (mState == StatusBarState.KEYGUARD) {
wakeUpIfDozing(SystemClock.uptimeMillis(), v);
goToLockedShade(null);
}
};
- private HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>> mTmpChildOrderMap
- = new HashMap<>();
- private RankingMap mLatestRankingMap;
private boolean mNoAnimationOnNextBarModeChange;
private FalsingManager mFalsingManager;
- private KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() {
- @Override
- public void onDreamingStateChanged(boolean dreaming) {
- if (dreaming) {
- maybeEscalateHeadsUp();
- }
- }
- };
+ private final KeyguardUpdateMonitorCallback mUpdateCallback =
+ new KeyguardUpdateMonitorCallback() {
+ @Override
+ public void onDreamingStateChanged(boolean dreaming) {
+ if (dreaming) {
+ maybeEscalateHeadsUp();
+ }
+ }
+ };
private NavigationBarFragment mNavigationBar;
private View mNavigationBarView;
+ protected ActivityLaunchAnimator mActivityLaunchAnimator;
@Override
public void start() {
+ mGroupManager = Dependency.get(NotificationGroupManager.class);
+ mVisualStabilityManager = Dependency.get(VisualStabilityManager.class);
+ mNotificationLogger = Dependency.get(NotificationLogger.class);
+ mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class);
+ mNotificationListener = Dependency.get(NotificationListener.class);
+ mGroupManager = Dependency.get(NotificationGroupManager.class);
mNetworkController = Dependency.get(NetworkController.class);
mUserSwitcherController = Dependency.get(UserSwitcherController.class);
mScreenLifecycle = Dependency.get(ScreenLifecycle.class);
@@ -797,25 +616,25 @@ public class StatusBar extends SystemUI implements DemoMode,
mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
mBatteryController = Dependency.get(BatteryController.class);
mAssistManager = Dependency.get(AssistManager.class);
- mSystemServicesProxy = SystemServicesProxy.getInstance(mContext);
mOverlayManager = IOverlayManager.Stub.asInterface(
ServiceManager.getService(Context.OVERLAY_SERVICE));
+ mLockscreenUserManager = Dependency.get(NotificationLockscreenUserManager.class);
+ mGutsManager = Dependency.get(NotificationGutsManager.class);
+ mMediaManager = Dependency.get(NotificationMediaManager.class);
+ mEntryManager = Dependency.get(NotificationEntryManager.class);
+ mViewHierarchyManager = Dependency.get(NotificationViewHierarchyManager.class);
mColorExtractor = Dependency.get(SysuiColorExtractor.class);
mColorExtractor.addOnColorsChangedListener(this);
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
- mForegroundServiceController = Dependency.get(ForegroundServiceController.class);
-
mDisplay = mWindowManager.getDefaultDisplay();
updateDisplaySize();
Resources res = mContext.getResources();
mScrimSrcModeEnabled = res.getBoolean(R.bool.config_status_bar_scrim_behind_use_src);
mClearAllEnabled = res.getBoolean(R.bool.config_enableNotificationsClearAll);
- mAlwaysExpandNonGroupedNotification =
- res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications);
DateTimeView.setReceiverHandler(Dependency.get(Dependency.TIME_TICK_HANDLER));
putComponent(StatusBar.class, this);
@@ -825,50 +644,23 @@ public class StatusBar extends SystemUI implements DemoMode,
mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService(
Context.DEVICE_POLICY_SERVICE);
- mNotificationData = new NotificationData(this);
- mMessagingUtil = new NotificationMessagingUtil(mContext);
-
mAccessibilityManager = (AccessibilityManager)
mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
- mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
- mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false,
- mSettingsObserver);
- mContext.getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS), false,
- mLockscreenSettingsObserver,
- UserHandle.USER_ALL);
- if (ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT) {
- mContext.getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT),
- false,
- mSettingsObserver,
- UserHandle.USER_ALL);
- }
-
- mContext.getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS),
- true,
- mLockscreenSettingsObserver,
- UserHandle.USER_ALL);
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
mRecents = getComponent(Recents.class);
- final Configuration currentConfig = res.getConfiguration();
- mLocale = currentConfig.locale;
- mLayoutDirection = TextUtils.getLayoutDirectionFromLocale(mLocale);
-
- mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
mLockPatternUtils = new LockPatternUtils(mContext);
+ mMediaManager.setUpWithPresenter(this, mEntryManager);
+
// Connect in to the status bar manager service
mCommandQueue = getComponent(CommandQueue.class);
mCommandQueue.addCallbacks(this);
@@ -888,7 +680,12 @@ public class StatusBar extends SystemUI implements DemoMode,
createAndAddWindows();
- mSettingsObserver.onChange(false); // set up
+ // Make sure we always have the most current wallpaper info.
+ IntentFilter wallpaperChangedFilter = new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED);
+ mContext.registerReceiver(mWallpaperChangedReceiver, wallpaperChangedFilter);
+ mWallpaperChangedReceiver.onReceive(mContext, null);
+
+ mLockscreenUserManager.setUpWithPresenter(this, mEntryManager);
mCommandQueue.disable(switches[0], switches[6], false /* animate */);
setSystemUiVisibility(switches[1], switches[7], switches[8], 0xffffffff,
fullscreenStackBounds, dockedStackBounds);
@@ -903,14 +700,7 @@ public class StatusBar extends SystemUI implements DemoMode,
}
// Set up the initial notification state.
- try {
- mNotificationListener.registerAsSystemService(mContext,
- new ComponentName(mContext.getPackageName(), getClass().getCanonicalName()),
- UserHandle.USER_ALL);
- } catch (RemoteException e) {
- Log.e(TAG, "Unable to register notification listener", e);
- }
-
+ mNotificationListener.setUpWithPresenter(this, mEntryManager);
if (DEBUG) {
Log.d(TAG, String.format(
@@ -923,28 +713,13 @@ public class StatusBar extends SystemUI implements DemoMode,
));
}
- mCurrentUserId = ActivityManager.getCurrentUser();
- setHeadsUpUser(mCurrentUserId);
-
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_USER_SWITCHED);
- filter.addAction(Intent.ACTION_USER_ADDED);
- filter.addAction(Intent.ACTION_USER_PRESENT);
- mContext.registerReceiver(mBaseBroadcastReceiver, filter);
+ setHeadsUpUser(mLockscreenUserManager.getCurrentUserId());
IntentFilter internalFilter = new IntentFilter();
- internalFilter.addAction(NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION);
internalFilter.addAction(BANNER_ACTION_CANCEL);
internalFilter.addAction(BANNER_ACTION_SETUP);
- mContext.registerReceiver(mBaseBroadcastReceiver, internalFilter, PERMISSION_SELF, null);
-
- IntentFilter allUsersFilter = new IntentFilter();
- allUsersFilter.addAction(
- DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
- allUsersFilter.addAction(Intent.ACTION_DEVICE_LOCKED_CHANGED);
- mContext.registerReceiverAsUser(mAllUsersReceiver, UserHandle.ALL, allUsersFilter,
- null, null);
- updateCurrentProfilesCache();
+ mContext.registerReceiver(mBannerActionBroadcastReceiver, internalFilter, PERMISSION_SELF,
+ null);
IVrManager vrManager = IVrManager.Stub.asInterface(ServiceManager.getService(
Context.VR_SERVICE));
@@ -954,29 +729,11 @@ public class StatusBar extends SystemUI implements DemoMode,
Slog.e(TAG, "Failed to register VR mode state listener: " + e);
}
- mNonBlockablePkgs = new HashSet<>();
- Collections.addAll(mNonBlockablePkgs, res.getStringArray(
- com.android.internal.R.array.config_nonBlockableNotificationPackages));
// end old BaseStatusBar.start().
- mMediaSessionManager
- = (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
- // TODO: use MediaSessionManager.SessionListener to hook us up to future updates
- // in session state
-
// Lastly, call to the icon policy to install/update all the icons.
mIconPolicy = new PhoneStatusBarPolicy(mContext, mIconController);
- mSettingsObserver.onChange(false); // set up
- mHeadsUpObserver.onChange(true); // set up
- if (ENABLE_HEADS_UP) {
- mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED), true,
- mHeadsUpObserver);
- mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(SETTING_HEADS_UP_TICKER), true,
- mHeadsUpObserver);
- }
mUnlockMethodCache = UnlockMethodCache.getInstance(mContext);
mUnlockMethodCache.addListener(this);
startKeyguard();
@@ -994,9 +751,6 @@ public class StatusBar extends SystemUI implements DemoMode,
Dependency.get(ConfigurationController.class).addCallback(this);
}
- protected void createIconController() {
- }
-
// ================================================================================
// Constructing the view
// ================================================================================
@@ -1012,16 +766,27 @@ public class StatusBar extends SystemUI implements DemoMode,
// TODO: Deal with the ugliness that comes from having some of the statusbar broken out
// into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
- mNotificationPanel = (NotificationPanelView) mStatusBarWindow.findViewById(
- R.id.notification_panel);
- mStackScroller = (NotificationStackScrollLayout) mStatusBarWindow.findViewById(
- R.id.notification_stack_scroller);
+ mNotificationPanel = mStatusBarWindow.findViewById(R.id.notification_panel);
+ mStackScroller = mStatusBarWindow.findViewById(R.id.notification_stack_scroller);
+ mActivityLaunchAnimator = new ActivityLaunchAnimator(mStatusBarWindow,
+ this,
+ mNotificationPanel,
+ mStackScroller);
+ mGutsManager.setUpWithPresenter(this, mEntryManager, mStackScroller, mCheckSaveListener,
+ key -> {
+ try {
+ mBarService.onNotificationSettingsViewed(key);
+ } catch (RemoteException e) {
+ // if we're here we're dead
+ }
+ });
+ mNotificationLogger.setUpWithEntryManager(mEntryManager, mStackScroller);
mNotificationPanel.setStatusBar(this);
mNotificationPanel.setGroupManager(mGroupManager);
mAboveShelfObserver = new AboveShelfObserver(mStackScroller);
mAboveShelfObserver.setListener(mStatusBarWindow.findViewById(
R.id.notification_container_parent));
- mKeyguardStatusBar = (KeyguardStatusBarView) mStatusBarWindow.findViewById(R.id.keyguard_header);
+ mKeyguardStatusBar = mStatusBarWindow.findViewById(R.id.keyguard_header);
mNotificationIconAreaController = SystemUIFactory.getInstance()
.createNotificationIconAreaController(context, this);
@@ -1047,20 +812,21 @@ public class StatusBar extends SystemUI implements DemoMode,
.commit();
mIconController = Dependency.get(StatusBarIconController.class);
- mHeadsUpManager = new HeadsUpManager(context, mStatusBarWindow, mGroupManager);
- mHeadsUpManager.setBar(this);
+ mHeadsUpManager = new HeadsUpManagerPhone(context, mStatusBarWindow, mGroupManager, this,
+ mVisualStabilityManager);
mHeadsUpManager.addListener(this);
mHeadsUpManager.addListener(mNotificationPanel);
mHeadsUpManager.addListener(mGroupManager);
mHeadsUpManager.addListener(mVisualStabilityManager);
mNotificationPanel.setHeadsUpManager(mHeadsUpManager);
- mNotificationData.setHeadsUpManager(mHeadsUpManager);
mGroupManager.setHeadsUpManager(mHeadsUpManager);
- mHeadsUpManager.setVisualStabilityManager(mVisualStabilityManager);
+ putComponent(HeadsUpManager.class, mHeadsUpManager);
+
+ mEntryManager.setUpWithPresenter(this, mStackScroller, this, mHeadsUpManager);
+ mViewHierarchyManager.setUpWithPresenter(this, mEntryManager, mStackScroller);
if (MULTIUSER_DEBUG) {
- mNotificationPanelDebugText = (TextView) mNotificationPanel.findViewById(
- R.id.header_debug_info);
+ mNotificationPanelDebugText = mNotificationPanel.findViewById(R.id.header_debug_info);
mNotificationPanelDebugText.setVisibility(View.VISIBLE);
}
@@ -1073,11 +839,8 @@ public class StatusBar extends SystemUI implements DemoMode,
} catch (RemoteException ex) {
// no window manager? good luck with that
}
-
- // figure out which pixel-format to use for the status bar.
- mPixelFormat = PixelFormat.OPAQUE;
-
- mStackScroller.setLongPressListener(getNotificationLongClicker());
+ mScreenPinningNotify = new ScreenPinningNotify(mContext);
+ mStackScroller.setLongPressListener(mEntryManager.getNotificationLongClicker());
mStackScroller.setStatusBar(this);
mStackScroller.setGroupManager(mGroupManager);
mStackScroller.setHeadsUpManager(mHeadsUpManager);
@@ -1086,11 +849,10 @@ public class StatusBar extends SystemUI implements DemoMode,
inflateEmptyShadeView();
inflateDismissView();
- mExpandedContents = mStackScroller;
- mBackdrop = (BackDropView) mStatusBarWindow.findViewById(R.id.backdrop);
- mBackdropFront = (ImageView) mBackdrop.findViewById(R.id.backdrop_front);
- mBackdropBack = (ImageView) mBackdrop.findViewById(R.id.backdrop_back);
+ mBackdrop = mStatusBarWindow.findViewById(R.id.backdrop);
+ mBackdropFront = mBackdrop.findViewById(R.id.backdrop_front);
+ mBackdropBack = mBackdrop.findViewById(R.id.backdrop_back);
if (ENABLE_LOCKSCREEN_WALLPAPER) {
mLockscreenWallpaper = new LockscreenWallpaper(mContext, this, mHandler);
@@ -1098,8 +860,8 @@ public class StatusBar extends SystemUI implements DemoMode,
mKeyguardIndicationController =
SystemUIFactory.getInstance().createKeyguardIndicationController(mContext,
- (ViewGroup) mStatusBarWindow.findViewById(R.id.keyguard_indication_area),
- mNotificationPanel.getLockIcon());
+ mStatusBarWindow.findViewById(R.id.keyguard_indication_area),
+ mNotificationPanel.getLockIcon());
mNotificationPanel.setKeyguardIndicationController(mKeyguardIndicationController);
@@ -1130,24 +892,21 @@ public class StatusBar extends SystemUI implements DemoMode,
mNavigationBar.setLightBarController(mLightBarController);
}
- ScrimView scrimBehind = (ScrimView) mStatusBarWindow.findViewById(R.id.scrim_behind);
- ScrimView scrimInFront = (ScrimView) mStatusBarWindow.findViewById(R.id.scrim_in_front);
+ ScrimView scrimBehind = mStatusBarWindow.findViewById(R.id.scrim_behind);
+ ScrimView scrimInFront = mStatusBarWindow.findViewById(R.id.scrim_in_front);
View headsUpScrim = mStatusBarWindow.findViewById(R.id.heads_up_scrim);
mScrimController = SystemUIFactory.getInstance().createScrimController(mLightBarController,
scrimBehind, scrimInFront, headsUpScrim, mLockscreenWallpaper,
scrimsVisible -> {
if (mStatusBarWindowManager != null) {
- mStatusBarWindowManager.setScrimsVisible(scrimsVisible);
+ mStatusBarWindowManager.setScrimsVisibility(scrimsVisible);
}
- });
+ }, new DozeParameters(mContext), mContext.getSystemService(AlarmManager.class));
if (mScrimSrcModeEnabled) {
- Runnable runnable = new Runnable() {
- @Override
- public void run() {
- boolean asSrc = mBackdrop.getVisibility() != View.VISIBLE;
- mScrimController.setDrawBehindAsSrc(asSrc);
- mStackScroller.setDrawBackgroundAsSrc(asSrc);
- }
+ Runnable runnable = () -> {
+ boolean asSrc = mBackdrop.getVisibility() != View.VISIBLE;
+ mScrimController.setDrawBehindAsSrc(asSrc);
+ mStackScroller.setDrawBackgroundAsSrc(asSrc);
};
mBackdrop.setOnVisibilityChangedRunnable(runnable);
runnable.run();
@@ -1169,16 +928,19 @@ public class StatusBar extends SystemUI implements DemoMode,
if (container != null) {
FragmentHostManager fragmentHostManager = FragmentHostManager.get(container);
ExtensionFragmentListener.attachExtensonToFragment(container, QS.TAG, R.id.qs_frame,
- Dependency.get(ExtensionController.class).newExtension(QS.class)
+ Dependency.get(ExtensionController.class)
+ .newExtension(QS.class)
.withPlugin(QS.class)
- .withFeature(
- PackageManager.FEATURE_AUTOMOTIVE, () -> new CarQSFragment())
- .withDefault(() -> new QSFragment())
+ .withFeature(PackageManager.FEATURE_AUTOMOTIVE, CarQSFragment::new)
+ .withDefault(QSFragment::new)
.build());
final QSTileHost qsh = SystemUIFactory.getInstance().createQSTileHost(mContext, this,
mIconController);
mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindow,
- mScrimController);
+ (visible) -> {
+ mBrightnessMirrorVisible = visible;
+ updateScrimController();
+ });
fragmentHostManager.addTagListener(QS.TAG, (tag, f) -> {
QS qs = (QS) f;
if (qs instanceof QSFragment) {
@@ -1275,8 +1037,8 @@ public class StatusBar extends SystemUI implements DemoMode,
*/
protected View.OnTouchListener getStatusBarWindowTouchListener() {
return (v, event) -> {
- checkUserAutohide(v, event);
- checkRemoteInputOutside(event);
+ checkUserAutohide(event);
+ mRemoteInputManager.checkRemoteInputOutside(event);
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (mExpandedVisible) {
animateCollapsePanels();
@@ -1297,9 +1059,11 @@ public class StatusBar extends SystemUI implements DemoMode,
}
public void onDensityOrFontScaleChanged() {
+ MessagingMessage.dropCache();
+ MessagingGroup.dropCache();
// start old BaseStatusBar.onDensityOrFontScaleChanged().
if (!KeyguardUpdateMonitor.getInstance(mContext).isSwitchingUser()) {
- updateNotificationsOnDensityOrFontScaleChanged();
+ mEntryManager.updateNotificationsOnDensityOrFontScaleChanged();
} else {
mReinflateNotificationsOnUserSwitched = true;
}
@@ -1323,13 +1087,13 @@ public class StatusBar extends SystemUI implements DemoMode,
reevaluateStyles();
}
- private void reinflateViews() {
+ private void onThemeChanged() {
reevaluateStyles();
// Clock and bottom icons
- mNotificationPanel.onOverlayChanged();
+ mNotificationPanel.onThemeChanged();
// The status bar on the keyguard is a special layout.
- if (mKeyguardStatusBar != null) mKeyguardStatusBar.onOverlayChanged();
+ if (mKeyguardStatusBar != null) mKeyguardStatusBar.onThemeChanged();
// Recreate Indication controller because internal references changed
mKeyguardIndicationController =
SystemUIFactory.getInstance().createKeyguardIndicationController(mContext,
@@ -1340,11 +1104,8 @@ public class StatusBar extends SystemUI implements DemoMode,
.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
mKeyguardIndicationController.setVisible(mState == StatusBarState.KEYGUARD);
mKeyguardIndicationController.setDozing(mDozing);
- if (mBrightnessMirrorController != null) {
- mBrightnessMirrorController.onOverlayChanged();
- }
if (mStatusBarKeyguardViewManager != null) {
- mStatusBarKeyguardViewManager.onOverlayChanged();
+ mStatusBarKeyguardViewManager.onThemeChanged();
}
if (mAmbientIndicationContainer instanceof AutoReinflateContainer) {
((AutoReinflateContainer) mAmbientIndicationContainer).inflateLayout();
@@ -1359,17 +1120,10 @@ public class StatusBar extends SystemUI implements DemoMode,
updateEmptyShadeView();
}
- private void updateNotificationsOnDensityOrFontScaleChanged() {
- ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
- for (int i = 0; i < activeNotifications.size(); i++) {
- Entry entry = activeNotifications.get(i);
- boolean exposedGuts = mNotificationGutsExposed != null
- && entry.row.getGuts() == mNotificationGutsExposed;
- entry.row.onDensityOrFontScaleChanged();
- if (exposedGuts) {
- mNotificationGutsExposed = entry.row.getGuts();
- bindGuts(entry.row, mGutsMenuItem);
- }
+ @Override
+ public void onOverlayChanged() {
+ if (mBrightnessMirrorController != null) {
+ mBrightnessMirrorController.onOverlayChanged();
}
}
@@ -1379,8 +1133,7 @@ public class StatusBar extends SystemUI implements DemoMode,
public static SignalClusterView reinflateSignalCluster(View view) {
Context context = view.getContext();
- SignalClusterView signalCluster =
- (SignalClusterView) view.findViewById(R.id.signal_cluster);
+ SignalClusterView signalCluster = view.findViewById(R.id.signal_cluster);
if (signalCluster != null) {
ViewParent parent = signalCluster.getParent();
if (parent instanceof ViewGroup) {
@@ -1420,20 +1173,17 @@ public class StatusBar extends SystemUI implements DemoMode,
mDismissView = (DismissView) LayoutInflater.from(mContext).inflate(
R.layout.status_bar_notification_dismiss_all, mStackScroller, false);
- mDismissView.setOnButtonClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES);
- clearAllNotifications();
- }
+ mDismissView.setOnButtonClickListener(v -> {
+ mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES);
+ clearAllNotifications();
});
mStackScroller.setDismissView(mDismissView);
}
protected void createUserSwitcher() {
mKeyguardUserSwitcher = new KeyguardUserSwitcher(mContext,
- (ViewStub) mStatusBarWindow.findViewById(R.id.keyguard_user_switcher),
- mKeyguardStatusBar, mNotificationPanel);
+ mStatusBarWindow.findViewById(R.id.keyguard_user_switcher), mKeyguardStatusBar,
+ mNotificationPanel);
}
protected void inflateStatusBarWindow(Context context) {
@@ -1446,7 +1196,7 @@ public class StatusBar extends SystemUI implements DemoMode,
// animate-swipe all dismissable notifications, then animate the shade closed
int numChildren = mStackScroller.getChildCount();
- final ArrayList<View> viewsToHide = new ArrayList<View>(numChildren);
+ final ArrayList<View> viewsToHide = new ArrayList<>(numChildren);
final ArrayList<ExpandableNotificationRow> viewsToRemove = new ArrayList<>(numChildren);
for (int i = 0; i < numChildren; i++) {
final View child = mStackScroller.getChildAt(i);
@@ -1486,20 +1236,18 @@ public class StatusBar extends SystemUI implements DemoMode,
return;
}
- addPostCollapseAction(new Runnable() {
- @Override
- public void run() {
- mStackScroller.setDismissAllInProgress(false);
- for (ExpandableNotificationRow rowToRemove : viewsToRemove) {
- if (mStackScroller.canChildBeDismissed(rowToRemove)) {
- removeNotification(rowToRemove.getEntry().key, null);
- } else {
- rowToRemove.resetTranslation();
- }
+ addPostCollapseAction(() -> {
+ mStackScroller.setDismissAllInProgress(false);
+ for (ExpandableNotificationRow rowToRemove : viewsToRemove) {
+ if (mStackScroller.canChildBeDismissed(rowToRemove)) {
+ mEntryManager.removeNotification(rowToRemove.getEntry().key, null);
+ } else {
+ rowToRemove.resetTranslation();
}
- try {
- mBarService.onClearAllNotifications(mCurrentUserId);
- } catch (Exception ex) { }
+ }
+ try {
+ mBarService.onClearAllNotifications(mLockscreenUserManager.getCurrentUserId());
+ } catch (Exception ex) {
}
});
@@ -1508,11 +1256,8 @@ public class StatusBar extends SystemUI implements DemoMode,
}
private void performDismissAllAnimations(ArrayList<View> hideAnimatedList) {
- Runnable animationFinishAction = new Runnable() {
- @Override
- public void run() {
- animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
- }
+ Runnable animationFinishAction = () -> {
+ animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
};
if (hideAnimatedList.isEmpty()) {
@@ -1541,15 +1286,6 @@ public class StatusBar extends SystemUI implements DemoMode,
}
}
- protected void setZenMode(int mode) {
- // start old BaseStatusBar.setZenMode().
- if (isDeviceProvisioned()) {
- mZenMode = mode;
- updateNotifications();
- }
- // end old BaseStatusBar.setZenMode().
- }
-
protected void startKeyguard() {
Trace.beginSection("StatusBar#startKeyguard");
KeyguardViewMediator keyguardViewMediator = getComponent(KeyguardViewMediator.class);
@@ -1557,31 +1293,11 @@ public class StatusBar extends SystemUI implements DemoMode,
mDozeScrimController, keyguardViewMediator,
mScrimController, this, UnlockMethodCache.getInstance(mContext));
mStatusBarKeyguardViewManager = keyguardViewMediator.registerStatusBar(this,
- getBouncerContainer(), mScrimController,
- mFingerprintUnlockController);
+ getBouncerContainer(), mFingerprintUnlockController);
mKeyguardIndicationController
.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
mFingerprintUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
- mRemoteInputController.addCallback(mStatusBarKeyguardViewManager);
-
- mRemoteInputController.addCallback(new RemoteInputController.Callback() {
- @Override
- public void onRemoteInputSent(Entry entry) {
- if (FORCE_REMOTE_INPUT_HISTORY && mKeysKeptForRemoteInput.contains(entry.key)) {
- removeNotification(entry.key, null);
- } else if (mRemoteInputEntriesToRemoveOnCollapse.contains(entry)) {
- // We're currently holding onto this notification, but from the apps point of
- // view it is already canceled, so we'll need to cancel it on the apps behalf
- // after sending - unless the app posts an update in the mean time, so wait a
- // bit.
- mHandler.postDelayed(() -> {
- if (mRemoteInputEntriesToRemoveOnCollapse.remove(entry)) {
- removeNotification(entry.key, null);
- }
- }, REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY);
- }
- }
- });
+ mRemoteInputManager.getController().addCallback(mStatusBarKeyguardViewManager);
mKeyguardViewMediatorCallback = keyguardViewMediator.getViewMediatorCallback();
mLightBarController.setFingerprintUnlockController(mFingerprintUnlockController);
@@ -1615,8 +1331,8 @@ public class StatusBar extends SystemUI implements DemoMode,
}
int dockSide = WindowManagerProxy.getInstance().getDockSide();
if (dockSide == WindowManager.DOCKED_INVALID) {
- return mRecents.dockTopTask(NavigationBarGestureHelper.DRAG_MODE_NONE,
- ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, null, metricsDockAction);
+ return mRecents.splitPrimaryTask(NavigationBarGestureHelper.DRAG_MODE_NONE,
+ ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, null, metricsDockAction);
} else {
Divider divider = getComponent(Divider.class);
if (divider != null && divider.isMinimized() && !divider.isHomeStackResizable()) {
@@ -1632,213 +1348,54 @@ public class StatusBar extends SystemUI implements DemoMode,
return true;
}
- void awakenDreams() {
- SystemServicesProxy.getInstance(mContext).awakenDreamsAsync();
- }
-
- public UserHandle getCurrentUserHandle() {
- return new UserHandle(mCurrentUserId);
+ @Override
+ public void onPerformRemoveNotification(StatusBarNotification n) {
+ if (mStackScroller.hasPulsingNotifications() &&
+ !mHeadsUpManager.hasHeadsUpNotifications()) {
+ // We were showing a pulse for a notification, but no notifications are pulsing anymore.
+ // Finish the pulse.
+ mDozeScrimController.pulseOutNow();
+ }
}
- public void addNotification(StatusBarNotification notification, RankingMap ranking)
- throws InflationException {
- String key = notification.getKey();
- if (DEBUG) Log.d(TAG, "addNotification key=" + key);
-
- mNotificationData.updateRanking(ranking);
- Entry shadeEntry = createNotificationViews(notification);
- boolean isHeadsUped = shouldPeek(shadeEntry);
- if (!isHeadsUped && notification.getNotification().fullScreenIntent != null) {
- if (shouldSuppressFullScreenIntent(key)) {
- if (DEBUG) {
- Log.d(TAG, "No Fullscreen intent: suppressed by DND: " + key);
- }
- } else if (mNotificationData.getImportance(key)
- < NotificationManager.IMPORTANCE_HIGH) {
- if (DEBUG) {
- Log.d(TAG, "No Fullscreen intent: not important enough: "
- + key);
- }
- } else {
- // Stop screensaver if the notification has a full-screen intent.
- // (like an incoming phone call)
- awakenDreams();
+ @Override
+ public void updateNotificationViews() {
+ // The function updateRowStates depends on both of these being non-null, so check them here.
+ // We may be called before they are set from DeviceProvisionedController's callback.
+ if (mStackScroller == null || mScrimController == null) return;
- // not immersive & a full-screen alert should be shown
- if (DEBUG)
- Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent");
- try {
- EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION,
- key);
- notification.getNotification().fullScreenIntent.send();
- shadeEntry.notifyFullScreenIntentLaunched();
- mMetricsLogger.count("note_fullscreen", 1);
- } catch (PendingIntent.CanceledException e) {
- }
- }
+ // Do not modify the notifications during collapse.
+ if (isCollapsing()) {
+ addPostCollapseAction(this::updateNotificationViews);
+ return;
}
- abortExistingInflation(key);
- mForegroundServiceController.addNotification(notification,
- mNotificationData.getImportance(key));
+ mViewHierarchyManager.updateNotificationViews();
- mPendingNotifications.put(key, shadeEntry);
- }
+ updateSpeedBumpIndex();
+ updateClearAll();
+ updateEmptyShadeView();
- private void abortExistingInflation(String key) {
- if (mPendingNotifications.containsKey(key)) {
- Entry entry = mPendingNotifications.get(key);
- entry.abortTask();
- mPendingNotifications.remove(key);
- }
- Entry addedEntry = mNotificationData.get(key);
- if (addedEntry != null) {
- addedEntry.abortTask();
- }
+ updateQsExpansionEnabled();
+
+ // Let's also update the icons
+ mNotificationIconAreaController.updateNotificationIcons(
+ mEntryManager.getNotificationData());
}
- private void addEntry(Entry shadeEntry) {
- boolean isHeadsUped = shouldPeek(shadeEntry);
- if (isHeadsUped) {
- mHeadsUpManager.showNotification(shadeEntry);
- // Mark as seen immediately
- setNotificationShown(shadeEntry.notification);
- }
- addNotificationViews(shadeEntry);
+ @Override
+ public void onNotificationAdded(Entry shadeEntry) {
// Recalculate the position of the sliding windows and the titles.
setAreThereNotifications();
}
@Override
- public void handleInflationException(StatusBarNotification notification, Exception e) {
- handleNotificationError(notification, e.getMessage());
+ public void onNotificationUpdated(StatusBarNotification notification) {
+ setAreThereNotifications();
}
@Override
- public void onAsyncInflationFinished(Entry entry) {
- mPendingNotifications.remove(entry.key);
- // If there was an async task started after the removal, we don't want to add it back to
- // the list, otherwise we might get leaks.
- boolean isNew = mNotificationData.get(entry.key) == null;
- if (isNew && !entry.row.isRemoved()) {
- addEntry(entry);
- } else if (!isNew && entry.row.hasLowPriorityStateUpdated()) {
- mVisualStabilityManager.onLowPriorityUpdated(entry);
- updateNotificationShade();
- }
- entry.row.setLowPriorityStateUpdated(false);
- }
-
- private boolean shouldSuppressFullScreenIntent(String key) {
- if (isDeviceInVrMode()) {
- return true;
- }
-
- if (mPowerManager.isInteractive()) {
- return mNotificationData.shouldSuppressScreenOn(key);
- } else {
- return mNotificationData.shouldSuppressScreenOff(key);
- }
- }
-
- protected void updateNotificationRanking(RankingMap ranking) {
- mNotificationData.updateRanking(ranking);
- updateNotifications();
- }
-
- public void removeNotification(String key, RankingMap ranking) {
- boolean deferRemoval = false;
- abortExistingInflation(key);
- if (mHeadsUpManager.isHeadsUp(key)) {
- // A cancel() in repsonse to a remote input shouldn't be delayed, as it makes the
- // sending look longer than it takes.
- // Also we should not defer the removal if reordering isn't allowed since otherwise
- // some notifications can't disappear before the panel is closed.
- boolean ignoreEarliestRemovalTime = mRemoteInputController.isSpinning(key)
- && !FORCE_REMOTE_INPUT_HISTORY
- || !mVisualStabilityManager.isReorderingAllowed();
- deferRemoval = !mHeadsUpManager.removeNotification(key, ignoreEarliestRemovalTime);
- }
- if (key.equals(mMediaNotificationKey)) {
- clearCurrentMediaNotification();
- updateMediaMetaData(true, true);
- }
- if (FORCE_REMOTE_INPUT_HISTORY && mRemoteInputController.isSpinning(key)) {
- Entry entry = mNotificationData.get(key);
- StatusBarNotification sbn = entry.notification;
-
- Notification.Builder b = Notification.Builder
- .recoverBuilder(mContext, sbn.getNotification().clone());
- CharSequence[] oldHistory = sbn.getNotification().extras
- .getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY);
- CharSequence[] newHistory;
- if (oldHistory == null) {
- newHistory = new CharSequence[1];
- } else {
- newHistory = new CharSequence[oldHistory.length + 1];
- for (int i = 0; i < oldHistory.length; i++) {
- newHistory[i + 1] = oldHistory[i];
- }
- }
- newHistory[0] = String.valueOf(entry.remoteInputText);
- b.setRemoteInputHistory(newHistory);
-
- Notification newNotification = b.build();
-
- // Undo any compatibility view inflation
- newNotification.contentView = sbn.getNotification().contentView;
- newNotification.bigContentView = sbn.getNotification().bigContentView;
- newNotification.headsUpContentView = sbn.getNotification().headsUpContentView;
-
- StatusBarNotification newSbn = new StatusBarNotification(sbn.getPackageName(),
- sbn.getOpPkg(),
- sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(),
- newNotification, sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime());
- boolean updated = false;
- try {
- updateNotification(newSbn, null);
- updated = true;
- } catch (InflationException e) {
- deferRemoval = false;
- }
- if (updated) {
- mKeysKeptForRemoteInput.add(entry.key);
- return;
- }
- }
- if (deferRemoval) {
- mLatestRankingMap = ranking;
- mHeadsUpEntriesToRemoveOnSwitch.add(mHeadsUpManager.getEntry(key));
- return;
- }
- Entry entry = mNotificationData.get(key);
-
- if (entry != null && mRemoteInputController.isRemoteInputActive(entry)
- && (entry.row != null && !entry.row.isDismissed())) {
- mLatestRankingMap = ranking;
- mRemoteInputEntriesToRemoveOnCollapse.add(entry);
- return;
- }
- if (entry != null && mNotificationGutsExposed != null
- && mNotificationGutsExposed == entry.row.getGuts() && entry.row.getGuts() != null
- && !entry.row.getGuts().isLeavebehind()) {
- Log.w(TAG, "Keeping notification because it's showing guts. " + key);
- mLatestRankingMap = ranking;
- mKeyToRemoveOnGutsClosed = key;
- return;
- }
-
- if (entry != null) {
- mForegroundServiceController.removeNotification(entry.notification);
- }
-
- if (entry != null && entry.row != null) {
- entry.row.setRemoved();
- mStackScroller.cleanUpViewState(entry.row);
- }
- // Let's remove the children if this was a summary
- handleGroupSummaryRemoved(key, ranking);
- StatusBarNotification old = removeNotificationViews(key, ranking);
+ public void onNotificationRemoved(String key, StatusBarNotification old) {
if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old);
if (old != null) {
@@ -1855,213 +1412,6 @@ public class StatusBar extends SystemUI implements DemoMode,
}
/**
- * Ensures that the group children are cancelled immediately when the group summary is cancelled
- * instead of waiting for the notification manager to send all cancels. Otherwise this could
- * lead to flickers.
- *
- * This also ensures that the animation looks nice and only consists of a single disappear
- * animation instead of multiple.
- *
- * @param key the key of the notification was removed
- * @param ranking the current ranking
- */
- private void handleGroupSummaryRemoved(String key,
- RankingMap ranking) {
- Entry entry = mNotificationData.get(key);
- if (entry != null && entry.row != null
- && entry.row.isSummaryWithChildren()) {
- if (entry.notification.getOverrideGroupKey() != null && !entry.row.isDismissed()) {
- // We don't want to remove children for autobundled notifications as they are not
- // always cancelled. We only remove them if they were dismissed by the user.
- return;
- }
- List<ExpandableNotificationRow> notificationChildren =
- entry.row.getNotificationChildren();
- ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>();
- for (int i = 0; i < notificationChildren.size(); i++) {
- ExpandableNotificationRow row = notificationChildren.get(i);
- if ((row.getStatusBarNotification().getNotification().flags
- & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
- // the child is a forground service notification which we can't remove!
- continue;
- }
- toRemove.add(row);
- row.setKeepInParent(true);
- // we need to set this state earlier as otherwise we might generate some weird
- // animations
- row.setRemoved();
- }
- }
- }
-
- protected void performRemoveNotification(StatusBarNotification n) {
- Entry entry = mNotificationData.get(n.getKey());
- if (mRemoteInputController.isRemoteInputActive(entry)) {
- mRemoteInputController.removeRemoteInput(entry, null);
- }
- // start old BaseStatusBar.performRemoveNotification.
- final String pkg = n.getPackageName();
- final String tag = n.getTag();
- final int id = n.getId();
- final int userId = n.getUserId();
- try {
- mBarService.onNotificationClear(pkg, tag, id, userId);
- if (FORCE_REMOTE_INPUT_HISTORY
- && mKeysKeptForRemoteInput.contains(n.getKey())) {
- mKeysKeptForRemoteInput.remove(n.getKey());
- }
- removeNotification(n.getKey(), null);
-
- } catch (RemoteException ex) {
- // system process is dead if we're here.
- }
- if (mStackScroller.hasPulsingNotifications() && mHeadsUpManager.getAllEntries().isEmpty()) {
- // We were showing a pulse for a notification, but no notifications are pulsing anymore.
- // Finish the pulse.
- mDozeScrimController.pulseOutNow();
- }
- // end old BaseStatusBar.performRemoveNotification.
- }
-
- private void updateNotificationShade() {
- if (mStackScroller == null) return;
-
- // Do not modify the notifications during collapse.
- if (isCollapsing()) {
- addPostCollapseAction(new Runnable() {
- @Override
- public void run() {
- updateNotificationShade();
- }
- });
- return;
- }
-
- ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
- ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size());
- final int N = activeNotifications.size();
- for (int i=0; i<N; i++) {
- Entry ent = activeNotifications.get(i);
- if (ent.row.isDismissed() || ent.row.isRemoved()) {
- // we don't want to update removed notifications because they could
- // temporarily become children if they were isolated before.
- continue;
- }
- int userId = ent.notification.getUserId();
-
- // Display public version of the notification if we need to redact.
- boolean devicePublic = isLockscreenPublicMode(mCurrentUserId);
- boolean userPublic = devicePublic || isLockscreenPublicMode(userId);
- boolean needsRedaction = needsRedaction(ent);
- boolean sensitive = userPublic && needsRedaction;
- boolean deviceSensitive = devicePublic
- && !userAllowsPrivateNotificationsInPublic(mCurrentUserId);
- ent.row.setSensitive(sensitive, deviceSensitive);
- ent.row.setNeedsRedaction(needsRedaction);
- if (mGroupManager.isChildInGroupWithSummary(ent.row.getStatusBarNotification())) {
- ExpandableNotificationRow summary = mGroupManager.getGroupSummary(
- ent.row.getStatusBarNotification());
- List<ExpandableNotificationRow> orderedChildren =
- mTmpChildOrderMap.get(summary);
- if (orderedChildren == null) {
- orderedChildren = new ArrayList<>();
- mTmpChildOrderMap.put(summary, orderedChildren);
- }
- orderedChildren.add(ent.row);
- } else {
- toShow.add(ent.row);
- }
-
- }
-
- ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>();
- for (int i=0; i< mStackScroller.getChildCount(); i++) {
- View child = mStackScroller.getChildAt(i);
- if (!toShow.contains(child) && child instanceof ExpandableNotificationRow) {
- toRemove.add((ExpandableNotificationRow) child);
- }
- }
-
- for (ExpandableNotificationRow remove : toRemove) {
- if (mGroupManager.isChildInGroupWithSummary(remove.getStatusBarNotification())) {
- // we are only transfering this notification to its parent, don't generate an animation
- mStackScroller.setChildTransferInProgress(true);
- }
- if (remove.isSummaryWithChildren()) {
- remove.removeAllChildren();
- }
- mStackScroller.removeView(remove);
- mStackScroller.setChildTransferInProgress(false);
- }
-
- removeNotificationChildren();
-
- for (int i=0; i<toShow.size(); i++) {
- View v = toShow.get(i);
- if (v.getParent() == null) {
- mVisualStabilityManager.notifyViewAddition(v);
- mStackScroller.addView(v);
- }
- }
-
- addNotificationChildrenAndSort();
-
- // So after all this work notifications still aren't sorted correctly.
- // Let's do that now by advancing through toShow and mStackScroller in
- // lock-step, making sure mStackScroller matches what we see in toShow.
- int j = 0;
- for (int i = 0; i < mStackScroller.getChildCount(); i++) {
- View child = mStackScroller.getChildAt(i);
- if (!(child instanceof ExpandableNotificationRow)) {
- // We don't care about non-notification views.
- continue;
- }
-
- ExpandableNotificationRow targetChild = toShow.get(j);
- if (child != targetChild) {
- // Oops, wrong notification at this position. Put the right one
- // here and advance both lists.
- if (mVisualStabilityManager.canReorderNotification(targetChild)) {
- mStackScroller.changeViewPosition(targetChild, i);
- } else {
- mVisualStabilityManager.addReorderingAllowedCallback(this);
- }
- }
- j++;
-
- }
-
- mVisualStabilityManager.onReorderingFinished();
- // clear the map again for the next usage
- mTmpChildOrderMap.clear();
-
- updateRowStates();
- updateSpeedBumpIndex();
- updateClearAll();
- updateEmptyShadeView();
-
- updateQsExpansionEnabled();
-
- // Let's also update the icons
- mNotificationIconAreaController.updateNotificationIcons(mNotificationData);
- }
-
- /** @return true if the entry needs redaction when on the lockscreen. */
- private boolean needsRedaction(Entry ent) {
- int userId = ent.notification.getUserId();
-
- boolean currentUserWantsRedaction = !userAllowsPrivateNotificationsInPublic(mCurrentUserId);
- boolean notiUserWantsRedaction = !userAllowsPrivateNotificationsInPublic(userId);
- boolean redactedLockscreen = currentUserWantsRedaction || notiUserWantsRedaction;
-
- boolean notificationRequestsRedaction =
- ent.notification.getNotification().visibility == Notification.VISIBILITY_PRIVATE;
- boolean userForcesRedaction = packageHasVisibilityOverride(ent.notification.getKey());
-
- return userForcesRedaction || notificationRequestsRedaction && redactedLockscreen;
- }
-
- /**
* Disable QS if device not provisioned.
* If the user switcher is simple then disable QS during setup because
* the user intends to use the lock screen user switcher, QS in not needed.
@@ -2070,86 +1420,12 @@ public class StatusBar extends SystemUI implements DemoMode,
mNotificationPanel.setQsExpansionEnabled(isDeviceProvisioned()
&& (mUserSetup || mUserSwitcherController == null
|| !mUserSwitcherController.isSimpleUserSwitcher())
+ && ((mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) == 0)
&& ((mDisabled2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) == 0)
&& !mDozing
&& !ONLY_CORE_APPS);
}
- private void addNotificationChildrenAndSort() {
- // Let's now add all notification children which are missing
- boolean orderChanged = false;
- for (int i = 0; i < mStackScroller.getChildCount(); i++) {
- View view = mStackScroller.getChildAt(i);
- if (!(view instanceof ExpandableNotificationRow)) {
- // We don't care about non-notification views.
- continue;
- }
-
- ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
- List<ExpandableNotificationRow> children = parent.getNotificationChildren();
- List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent);
-
- for (int childIndex = 0; orderedChildren != null && childIndex < orderedChildren.size();
- childIndex++) {
- ExpandableNotificationRow childView = orderedChildren.get(childIndex);
- if (children == null || !children.contains(childView)) {
- if (childView.getParent() != null) {
- Log.wtf(TAG, "trying to add a notification child that already has " +
- "a parent. class:" + childView.getParent().getClass() +
- "\n child: " + childView);
- // This shouldn't happen. We can recover by removing it though.
- ((ViewGroup) childView.getParent()).removeView(childView);
- }
- mVisualStabilityManager.notifyViewAddition(childView);
- parent.addChildNotification(childView, childIndex);
- mStackScroller.notifyGroupChildAdded(childView);
- }
- }
-
- // Finally after removing and adding has been beformed we can apply the order.
- orderChanged |= parent.applyChildOrder(orderedChildren, mVisualStabilityManager, this);
- }
- if (orderChanged) {
- mStackScroller.generateChildOrderChangedEvent();
- }
- }
-
- private void removeNotificationChildren() {
- // First let's remove all children which don't belong in the parents
- ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>();
- for (int i = 0; i < mStackScroller.getChildCount(); i++) {
- View view = mStackScroller.getChildAt(i);
- if (!(view instanceof ExpandableNotificationRow)) {
- // We don't care about non-notification views.
- continue;
- }
-
- ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
- List<ExpandableNotificationRow> children = parent.getNotificationChildren();
- List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent);
-
- if (children != null) {
- toRemove.clear();
- for (ExpandableNotificationRow childRow : children) {
- if ((orderedChildren == null
- || !orderedChildren.contains(childRow))
- && !childRow.keepInParent()) {
- toRemove.add(childRow);
- }
- }
- for (ExpandableNotificationRow remove : toRemove) {
- parent.removeChildNotification(remove);
- if (mNotificationData.get(remove.getStatusBarNotification().getKey()) == null) {
- // We only want to add an animation if the view is completely removed
- // otherwise it's just a transfer
- mStackScroller.notifyGroupChildRemoved(remove,
- parent.getChildrenContainer());
- }
- }
- }
- }
- }
-
public void addQsTile(ComponentName tile) {
mQSPanel.getHost().addTile(tile);
}
@@ -2162,10 +1438,6 @@ public class StatusBar extends SystemUI implements DemoMode,
mQSPanel.clickTile(tile);
}
- private boolean packageHasVisibilityOverride(String key) {
- return mNotificationData.getVisibilityOverride(key) == Notification.VISIBILITY_PRIVATE;
- }
-
private void updateClearAll() {
if (!mClearAllEnabled) {
return;
@@ -2195,7 +1467,7 @@ public class StatusBar extends SystemUI implements DemoMode,
private void updateEmptyShadeView() {
boolean showEmptyShadeView =
mState != StatusBarState.KEYGUARD &&
- mNotificationData.getActiveNotifications().size() == 0;
+ mEntryManager.getNotificationData().getActiveNotifications().size() == 0;
mNotificationPanel.showEmptyShadeView(showEmptyShadeView);
}
@@ -2210,7 +1482,8 @@ public class StatusBar extends SystemUI implements DemoMode,
}
ExpandableNotificationRow row = (ExpandableNotificationRow) view;
currentIndex++;
- if (!mNotificationData.isAmbient(row.getStatusBarNotification().getKey())) {
+ if (!mEntryManager.getNotificationData().isAmbient(
+ row.getStatusBarNotification().getKey())) {
speedBumpIndex = currentIndex;
}
}
@@ -2222,14 +1495,9 @@ public class StatusBar extends SystemUI implements DemoMode,
return entry.row.getParent() instanceof NotificationStackScrollLayout;
}
- protected void updateNotifications() {
- mNotificationData.filterAndSort();
-
- updateNotificationShade();
- }
public void requestNotificationUpdate() {
- updateNotifications();
+ mEntryManager.updateNotifications();
}
protected void setAreThereNotifications() {
@@ -2238,7 +1506,7 @@ public class StatusBar extends SystemUI implements DemoMode,
final boolean clearable = hasActiveNotifications() &&
hasActiveClearableNotifications();
Log.d(TAG, "setAreThereNotifications: N=" +
- mNotificationData.getActiveNotifications().size() + " any=" +
+ mEntryManager.getNotificationData().getActiveNotifications().size() + " any=" +
hasActiveNotifications() + " clearable=" + clearable);
}
@@ -2264,144 +1532,14 @@ public class StatusBar extends SystemUI implements DemoMode,
}
}
- findAndUpdateMediaNotifications();
- }
-
- public void findAndUpdateMediaNotifications() {
- boolean metaDataChanged = false;
-
- synchronized (mNotificationData) {
- ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
- final int N = activeNotifications.size();
-
- // Promote the media notification with a controller in 'playing' state, if any.
- Entry mediaNotification = null;
- MediaController controller = null;
- for (int i = 0; i < N; i++) {
- final Entry entry = activeNotifications.get(i);
- if (isMediaNotification(entry)) {
- final MediaSession.Token token =
- entry.notification.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.notification.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) {
- final List<MediaController> sessions
- = mMediaSessionManager.getActiveSessionsForUser(
- null,
- UserHandle.USER_ALL);
-
- for (MediaController aController : sessions) {
- if (PlaybackState.STATE_PLAYING ==
- getMediaControllerPlaybackState(aController)) {
- // now to see if we have one like this
- final String pkg = aController.getPackageName();
-
- for (int i = 0; i < N; i++) {
- final Entry entry = activeNotifications.get(i);
- if (entry.notification.getPackageName().equals(pkg)) {
- if (DEBUG_MEDIA) {
- Log.v(TAG, "DEBUG_MEDIA: found controller matching "
- + entry.notification.getKey());
- }
- controller = aController;
- mediaNotification = entry;
- break;
- }
- }
- }
- }
- }
- }
-
- if (controller != null && !sameSessions(mMediaController, controller)) {
- // We have a new media session
- clearCurrentMediaNotification();
- mMediaController = controller;
- mMediaController.registerCallback(mMediaListener);
- mMediaMetadata = mMediaController.getMetadata();
- if (DEBUG_MEDIA) {
- Log.v(TAG, "DEBUG_MEDIA: insert listener, receive metadata: "
- + mMediaMetadata);
- }
-
- if (mediaNotification != null) {
- mMediaNotificationKey = mediaNotification.notification.getKey();
- if (DEBUG_MEDIA) {
- Log.v(TAG, "DEBUG_MEDIA: Found new media notification: key="
- + mMediaNotificationKey + " controller=" + mMediaController);
- }
- }
- metaDataChanged = true;
- }
- }
-
- if (metaDataChanged) {
- updateNotifications();
- }
- updateMediaMetaData(metaDataChanged, true);
- }
-
- private int getMediaControllerPlaybackState(MediaController controller) {
- if (controller != null) {
- final PlaybackState playbackState = controller.getPlaybackState();
- if (playbackState != null) {
- return playbackState.getState();
- }
- }
- return PlaybackState.STATE_NONE;
- }
-
- private boolean isPlaybackActive(int state) {
- if (state != PlaybackState.STATE_STOPPED
- && state != PlaybackState.STATE_ERROR
- && state != PlaybackState.STATE_NONE) {
- return true;
- }
- return false;
- }
-
- private void clearCurrentMediaNotification() {
- mMediaNotificationKey = null;
- mMediaMetadata = null;
- if (mMediaController != null) {
- if (DEBUG_MEDIA) {
- Log.v(TAG, "DEBUG_MEDIA: Disconnecting from old controller: "
- + mMediaController.getPackageName());
- }
- mMediaController.unregisterCallback(mMediaListener);
- }
- mMediaController = null;
+ mMediaManager.findAndUpdateMediaNotifications();
}
- private boolean sameSessions(MediaController a, MediaController b) {
- if (a == b) return true;
- if (a == null) return false;
- return a.controlsSameSession(b);
- }
/**
* Hide the album artwork that is fading out and release its bitmap.
*/
- protected Runnable mHideBackdropFront = new Runnable() {
+ protected final Runnable mHideBackdropFront = new Runnable() {
@Override
public void run() {
if (DEBUG_MEDIA) {
@@ -2413,9 +1551,11 @@ public class StatusBar extends SystemUI implements DemoMode,
}
};
+ // TODO: Move this to NotificationMediaManager.
/**
* Refresh or remove lockscreen artwork from media metadata or the lockscreen wallpaper.
*/
+ @Override
public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) {
Trace.beginSection("StatusBar#updateMediaMetaData");
if (!SHOW_LOCKSCREEN_MEDIA_ARTWORK) {
@@ -2434,19 +1574,22 @@ public class StatusBar extends SystemUI implements DemoMode,
return;
}
+ MediaMetadata mediaMetadata = mMediaManager.getMediaMetadata();
+
if (DEBUG_MEDIA) {
- Log.v(TAG, "DEBUG_MEDIA: updating album art for notification " + mMediaNotificationKey
- + " metadata=" + mMediaMetadata
+ Log.v(TAG, "DEBUG_MEDIA: updating album art for notification "
+ + mMediaManager.getMediaNotificationKey()
+ + " metadata=" + mediaMetadata
+ " metaDataChanged=" + metaDataChanged
+ " state=" + mState);
}
Drawable artworkDrawable = null;
- if (mMediaMetadata != null) {
+ if (mediaMetadata != null) {
Bitmap artworkBitmap = null;
- artworkBitmap = mMediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ART);
+ artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ART);
if (artworkBitmap == null) {
- artworkBitmap = mMediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
+ artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
// might still be null
}
if (artworkBitmap != null) {
@@ -2471,7 +1614,7 @@ public class StatusBar extends SystemUI implements DemoMode,
final boolean hasArtwork = artworkDrawable != null;
- if ((hasArtwork || DEBUG_MEDIA_FAKE_ARTWORK)
+ if ((hasArtwork || DEBUG_MEDIA_FAKE_ARTWORK) && !mDozing
&& (mState != StatusBarState.SHADE || allowWhenShade)
&& mFingerprintUnlockController.getMode()
!= FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
@@ -2532,15 +1675,16 @@ public class StatusBar extends SystemUI implements DemoMode,
}
}
} else {
- // need to hide the album art, either because we are unlocked or because
- // the metadata isn't there to support it
+ // need to hide the album art, either because we are unlocked, on AOD
+ // or because the metadata isn't there to support it
if (mBackdrop.getVisibility() != View.GONE) {
if (DEBUG_MEDIA) {
Log.v(TAG, "DEBUG_MEDIA: Fading out album artwork");
}
+ boolean cannotAnimateDoze = mDozing && !ScrimState.AOD.getAnimateChange();
if (mFingerprintUnlockController.getMode()
== FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
- || hideBecauseOccluded) {
+ || hideBecauseOccluded || cannotAnimateDoze) {
// We are unlocking directly - no animation!
mBackdrop.setVisibility(View.GONE);
@@ -2553,14 +1697,11 @@ public class StatusBar extends SystemUI implements DemoMode,
.setInterpolator(Interpolators.ACCELERATE_DECELERATE)
.setDuration(300)
.setStartDelay(0)
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- mBackdrop.setVisibility(View.GONE);
- mBackdropFront.animate().cancel();
- mBackdropBack.setImageDrawable(null);
- mHandler.post(mHideBackdropFront);
- }
+ .withEndAction(() -> {
+ mBackdrop.setVisibility(View.GONE);
+ mBackdropFront.animate().cancel();
+ mBackdropBack.setImageDrawable(null);
+ mHandler.post(mHideBackdropFront);
});
if (mKeyguardFadingAway) {
mBackdrop.animate()
@@ -2581,7 +1722,7 @@ public class StatusBar extends SystemUI implements DemoMode,
if (mReportRejectedTouch == null) {
return;
}
- mReportRejectedTouch.setVisibility(mState == StatusBarState.KEYGUARD
+ mReportRejectedTouch.setVisibility(mState == StatusBarState.KEYGUARD && !mDozing
&& mFalsingManager.isReportingEnabled() ? View.VISIBLE : View.INVISIBLE);
}
@@ -2591,8 +1732,6 @@ public class StatusBar extends SystemUI implements DemoMode,
@Override
public void disable(int state1, int state2, boolean animate) {
animate &= mStatusBarWindowState != WINDOW_STATE_HIDDEN;
- mDisabledUnmodified1 = state1;
- mDisabledUnmodified2 = state2;
final int old1 = mDisabled1;
final int diff1 = state1 ^ old1;
mDisabled1 = state1;
@@ -2628,8 +1767,13 @@ public class StatusBar extends SystemUI implements DemoMode,
flagdbg.append(0 != ((diff1 & StatusBarManager.DISABLE_CLOCK)) ? '!' : ' ');
flagdbg.append(0 != ((state1 & StatusBarManager.DISABLE_SEARCH)) ? 'S' : 's');
flagdbg.append(0 != ((diff1 & StatusBarManager.DISABLE_SEARCH)) ? '!' : ' ');
+ flagdbg.append("> disable2<");
flagdbg.append(0 != ((state2 & StatusBarManager.DISABLE2_QUICK_SETTINGS)) ? 'Q' : 'q');
flagdbg.append(0 != ((diff2 & StatusBarManager.DISABLE2_QUICK_SETTINGS)) ? '!' : ' ');
+ flagdbg.append(0 != ((state2 & StatusBarManager.DISABLE2_SYSTEM_ICONS)) ? 'I' : 'i');
+ flagdbg.append(0 != ((diff2 & StatusBarManager.DISABLE2_SYSTEM_ICONS)) ? '!' : ' ');
+ flagdbg.append(0 != ((state2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE)) ? 'N' : 'n');
+ flagdbg.append(0 != ((diff2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE)) ? '!' : ' ');
flagdbg.append('>');
Log.d(TAG, flagdbg.toString());
@@ -2648,14 +1792,20 @@ public class StatusBar extends SystemUI implements DemoMode,
}
if ((diff1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) {
- mDisableNotificationAlerts =
- (state1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0;
- mHeadsUpObserver.onChange(true);
+ mEntryManager.setDisableNotificationAlerts(
+ (state1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0);
}
if ((diff2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) != 0) {
updateQsExpansionEnabled();
}
+
+ if ((diff2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
+ updateQsExpansionEnabled();
+ if ((state1 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
+ animateCollapsePanels();
+ }
+ }
}
/**
@@ -2706,13 +1856,43 @@ public class StatusBar extends SystemUI implements DemoMode,
return getBarState() == StatusBarState.KEYGUARD;
}
+ @Override
public boolean isDozing() {
return mDozing;
}
+ @Override
+ public boolean shouldPeek(Entry entry, StatusBarNotification sbn) {
+ if (mIsOccluded && !isDozing()) {
+ boolean devicePublic = mLockscreenUserManager.
+ isLockscreenPublicMode(mLockscreenUserManager.getCurrentUserId());
+ boolean userPublic = devicePublic
+ || mLockscreenUserManager.isLockscreenPublicMode(sbn.getUserId());
+ boolean needsRedaction = mLockscreenUserManager.needsRedaction(entry);
+ if (userPublic && needsRedaction) {
+ return false;
+ }
+ }
+
+ if (sbn.getNotification().fullScreenIntent != null) {
+ if (mAccessibilityManager.isTouchExplorationEnabled()) {
+ if (DEBUG) Log.d(TAG, "No peeking: accessible fullscreen: " + sbn.getKey());
+ return false;
+ } else if (isDozing()) {
+ // We never want heads up when we are dozing.
+ return false;
+ } else {
+ // we only allow head-up on the lockscreen if it doesn't have a fullscreen intent
+ return !mStatusBarKeyguardViewManager.isShowing()
+ || mStatusBarKeyguardViewManager.isOccluded();
+ }
+ }
+ return true;
+ }
+
@Override // NotificationData.Environment
public String getCurrentMediaNotificationKey() {
- return mMediaNotificationKey;
+ return mMediaManager.getMediaNotificationKey();
}
public boolean isScrimSrcModeEnabled() {
@@ -2743,11 +1923,8 @@ public class StatusBar extends SystemUI implements DemoMode,
// make sure that the window stays small for one frame until the touchableRegion is set.
mNotificationPanel.requestLayout();
mStatusBarWindowManager.setForceWindowCollapsed(true);
- mNotificationPanel.post(new Runnable() {
- @Override
- public void run() {
- mStatusBarWindowManager.setForceWindowCollapsed(false);
- }
+ mNotificationPanel.post(() -> {
+ mStatusBarWindowManager.setForceWindowCollapsed(false);
});
}
} else {
@@ -2759,15 +1936,12 @@ public class StatusBar extends SystemUI implements DemoMode,
// we need to keep the panel open artificially, let's wait until the animation
// is finished.
mHeadsUpManager.setHeadsUpGoingAway(true);
- mStackScroller.runAfterAnimationFinished(new Runnable() {
- @Override
- public void run() {
- if (!mHeadsUpManager.hasPinnedHeadsUp()) {
- mStatusBarWindowManager.setHeadsUpShowing(false);
- mHeadsUpManager.setHeadsUpGoingAway(false);
- }
- removeRemoteInputEntriesKeptUntilCollapsed();
+ mStackScroller.runAfterAnimationFinished(() -> {
+ if (!mHeadsUpManager.hasPinnedHeadsUp()) {
+ mStatusBarWindowManager.setHeadsUpShowing(false);
+ mHeadsUpManager.setHeadsUpGoingAway(false);
}
+ mRemoteInputManager.removeRemoteInputEntriesKeptUntilCollapsed();
});
}
}
@@ -2784,34 +1958,10 @@ public class StatusBar extends SystemUI implements DemoMode,
@Override
public void onHeadsUpStateChanged(Entry entry, boolean isHeadsUp) {
- if (!isHeadsUp && mHeadsUpEntriesToRemoveOnSwitch.contains(entry)) {
- removeNotification(entry.key, mLatestRankingMap);
- mHeadsUpEntriesToRemoveOnSwitch.remove(entry);
- if (mHeadsUpEntriesToRemoveOnSwitch.isEmpty()) {
- mLatestRankingMap = null;
- }
- } else {
- updateNotificationRanking(null);
- if (isHeadsUp) {
- mDozeServiceHost.fireNotificationHeadsUp();
- }
- }
-
- }
+ mEntryManager.onHeadsUpStateChanged(entry, isHeadsUp);
- protected void updateHeadsUp(String key, Entry entry, boolean shouldPeek,
- boolean alertAgain) {
- final boolean wasHeadsUp = isHeadsUp(key);
- if (wasHeadsUp) {
- if (!shouldPeek) {
- // We don't want this to be interrupting anymore, lets remove it
- mHeadsUpManager.removeNotification(key, false /* ignoreEarliestRemovalTime */);
- } else {
- mHeadsUpManager.updateNotification(entry, alertAgain);
- }
- } else if (shouldPeek && alertAgain) {
- // This notification was updated to be a heads-up, show it!
- mHeadsUpManager.showNotification(entry);
+ if (isHeadsUp) {
+ mDozeServiceHost.fireNotificationHeadsUp();
}
}
@@ -2821,14 +1971,6 @@ public class StatusBar extends SystemUI implements DemoMode,
}
}
- public boolean isHeadsUp(String key) {
- return mHeadsUpManager.isHeadsUp(key);
- }
-
- protected boolean isSnoozedPackage(StatusBarNotification sbn) {
- return mHeadsUpManager.isSnoozed(sbn.getPackageName());
- }
-
public boolean isKeyguardCurrentlySecure() {
return !mUnlockMethodCache.canSkipBouncer();
}
@@ -2846,30 +1988,16 @@ public class StatusBar extends SystemUI implements DemoMode,
}
if (!isExpanded) {
- removeRemoteInputEntriesKeptUntilCollapsed();
+ mRemoteInputManager.removeRemoteInputEntriesKeptUntilCollapsed();
}
}
- private void removeRemoteInputEntriesKeptUntilCollapsed() {
- for (int i = 0; i < mRemoteInputEntriesToRemoveOnCollapse.size(); i++) {
- Entry entry = mRemoteInputEntriesToRemoveOnCollapse.valueAt(i);
- mRemoteInputController.removeRemoteInput(entry, null);
- removeNotification(entry.key, mLatestRankingMap);
- }
- mRemoteInputEntriesToRemoveOnCollapse.clear();
- }
-
public NotificationStackScrollLayout getNotificationScrollLayout() {
return mStackScroller;
}
public boolean isPulsing() {
- return mDozeScrimController.isPulsing();
- }
-
- @Override
- public void onReorderingAllowed() {
- updateNotifications();
+ return mDozeScrimController != null && mDozeScrimController.isPulsing();
}
public boolean isLaunchTransitionFadingAway() {
@@ -2889,7 +2017,7 @@ public class StatusBar extends SystemUI implements DemoMode,
OverlayInfo themeInfo = null;
try {
themeInfo = mOverlayManager.getOverlayInfo("com.android.systemui.theme.dark",
- mCurrentUserId);
+ mLockscreenUserManager.getCurrentUserId());
} catch (RemoteException e) {
e.printStackTrace();
}
@@ -2935,6 +2063,12 @@ public class StatusBar extends SystemUI implements DemoMode,
}
}
+ public void onLaunchAnimationCancelled() {
+ if (!isCollapsing()) {
+ onClosingFinished();
+ }
+ }
+
/**
* All changes to the status bar and notifications funnel through here and are batched.
*/
@@ -2966,9 +2100,8 @@ public class StatusBar extends SystemUI implements DemoMode,
}
public void maybeEscalateHeadsUp() {
- Collection<HeadsUpManager.HeadsUpEntry> entries = mHeadsUpManager.getAllEntries();
- for (HeadsUpManager.HeadsUpEntry entry : entries) {
- final StatusBarNotification sbn = entry.entry.notification;
+ mHeadsUpManager.getAllEntries().forEach(entry -> {
+ final StatusBarNotification sbn = entry.notification;
final Notification notification = sbn.getNotification();
if (notification.fullScreenIntent != null) {
if (DEBUG) {
@@ -2978,11 +2111,11 @@ public class StatusBar extends SystemUI implements DemoMode,
EventLog.writeEvent(EventLogTags.SYSUI_HEADS_UP_ESCALATION,
sbn.getKey());
notification.fullScreenIntent.send();
- entry.entry.notifyFullScreenIntentLaunched();
+ entry.notifyFullScreenIntentLaunched();
} catch (PendingIntent.CanceledException e) {
}
}
- }
+ });
mHeadsUpManager.releaseAllImmediately();
}
@@ -3017,8 +2150,25 @@ public class StatusBar extends SystemUI implements DemoMode,
}
+ @Override
+ public void showPinningEnterExitToast(boolean entering) {
+ if (entering) {
+ mScreenPinningNotify.showPinningStartToast();
+ } else {
+ mScreenPinningNotify.showPinningExitToast();
+ }
+ }
+
+ @Override
+ public void showPinningEscapeToast() {
+ mScreenPinningNotify.showEscapeToast(getNavigationBarView() == null
+ || getNavigationBarView().isRecentsButtonVisible());
+ }
+
boolean panelsEnabled() {
- return (mDisabled1 & StatusBarManager.DISABLE_EXPAND) == 0 && !ONLY_CORE_APPS;
+ return (mDisabled1 & StatusBarManager.DISABLE_EXPAND) == 0
+ && (mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) == 0
+ && !ONLY_CORE_APPS;
}
void makeExpandedVisible(boolean force) {
@@ -3034,7 +2184,6 @@ public class StatusBar extends SystemUI implements DemoMode,
mStatusBarWindowManager.setPanelVisible(true);
visibilityChanged(true);
- mWaitingForKeyguardExit = false;
recomputeDisableFlags(!force /* animate */);
setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true);
}
@@ -3043,23 +2192,15 @@ public class StatusBar extends SystemUI implements DemoMode,
animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
}
- private final Runnable mAnimateCollapsePanels = new Runnable() {
- @Override
- public void run() {
- animateCollapsePanels();
- }
- };
+ private final Runnable mAnimateCollapsePanels = this::animateCollapsePanels;
public void postAnimateCollapsePanels() {
mHandler.post(mAnimateCollapsePanels);
}
public void postAnimateForceCollapsePanels() {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
- }
+ mHandler.post(() -> {
+ animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
});
}
@@ -3109,6 +2250,9 @@ public class StatusBar extends SystemUI implements DemoMode,
}
}
+ // TODO(b/62444020): remove when this bug is fixed
+ Log.v(TAG, "mStatusBarWindow: " + mStatusBarWindow + " canPanelBeCollapsed(): "
+ + mNotificationPanel.canPanelBeCollapsed());
if (mStatusBarWindow != null && mNotificationPanel.canPanelBeCollapsed()) {
// release focus immediately to kick off focus change transition
mStatusBarWindowManager.setStatusBarFocusable(false);
@@ -3187,8 +2331,8 @@ public class StatusBar extends SystemUI implements DemoMode,
mStatusBarWindowManager.setForceStatusBarVisible(false);
// Close any guts that might be visible
- closeAndSaveGuts(true /* removeLeavebehind */, true /* force */, true /* removeControls */,
- -1 /* x */, -1 /* y */, true /* resetMenu */);
+ mGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
+ true /* removeControls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
runPostCollapseRunnables();
setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false);
@@ -3214,7 +2358,7 @@ public class StatusBar extends SystemUI implements DemoMode,
if (SPEW) {
Log.d(TAG, "Touch: rawY=" + event.getRawY() + " event=" + event + " mDisabled1="
- + mDisabled1 + " mDisabled2=" + mDisabled2 + " mTracking=" + mTracking);
+ + mDisabled1 + " mDisabled2=" + mDisabled2);
} else if (CHATTY) {
if (event.getAction() != MotionEvent.ACTION_MOVE) {
Log.d(TAG, String.format(
@@ -3299,10 +2443,8 @@ public class StatusBar extends SystemUI implements DemoMode,
sbModeChanged = sbMode != -1;
if (sbModeChanged && sbMode != mStatusBarMode) {
- if (sbMode != mStatusBarMode) {
- mStatusBarMode = sbMode;
- checkBarModes();
- }
+ mStatusBarMode = sbMode;
+ checkBarModes();
touchAutoHide();
}
@@ -3318,6 +2460,29 @@ public class StatusBar extends SystemUI implements DemoMode,
mask, fullscreenStackBounds, dockedStackBounds, sbModeChanged, mStatusBarMode);
}
+ @Override
+ public void showWirelessChargingAnimation(int batteryLevel) {
+ if (mDozing || mKeyguardManager.isKeyguardLocked()) {
+ // on ambient or lockscreen, hide notification panel
+ WirelessChargingAnimation.makeWirelessChargingAnimation(mContext, null,
+ batteryLevel, new WirelessChargingAnimation.Callback() {
+ @Override
+ public void onAnimationStarting() {
+ CrossFadeHelper.fadeOut(mNotificationPanel, 1);
+ }
+
+ @Override
+ public void onAnimationEnded() {
+ CrossFadeHelper.fadeIn(mNotificationPanel);
+ }
+ }).show();
+ } else {
+ // workspace
+ WirelessChargingAnimation.makeWirelessChargingAnimation(mContext, null,
+ batteryLevel, null).show();
+ }
+ }
+
void touchAutoHide() {
// update transient bar autohide
if (mStatusBarMode == MODE_SEMI_TRANSPARENT || (mNavigationBar != null
@@ -3371,12 +2536,8 @@ public class StatusBar extends SystemUI implements DemoMode,
}
void checkBarMode(int mode, int windowState, BarTransitions transitions) {
- final boolean powerSave = mBatteryController.isPowerSave();
final boolean anim = !mNoAnimationOnNextBarModeChange && mDeviceInteractive
- && windowState != WINDOW_STATE_HIDDEN && !powerSave;
- if (powerSave && getBarState() == StatusBarState.SHADE) {
- mode = MODE_WARNING;
- }
+ && windowState != WINDOW_STATE_HIDDEN;
transitions.transitionTo(mode, anim);
}
@@ -3389,12 +2550,7 @@ public class StatusBar extends SystemUI implements DemoMode,
}
}
- private final Runnable mCheckBarModes = new Runnable() {
- @Override
- public void run() {
- checkBarModes();
- }
- };
+ private final Runnable mCheckBarModes = this::checkBarModes;
public void setInteracting(int barWindow, boolean interacting) {
final boolean changing = ((mInteractingWindows & barWindow) != 0) != interacting;
@@ -3453,23 +2609,16 @@ public class StatusBar extends SystemUI implements DemoMode,
}
}
- void checkUserAutohide(View v, MotionEvent event) {
+ void checkUserAutohide(MotionEvent event) {
if ((mSystemUiVisibility & STATUS_OR_NAV_TRANSIENT) != 0 // a transient bar is revealed
&& event.getAction() == MotionEvent.ACTION_OUTSIDE // touch outside the source bar
&& event.getX() == 0 && event.getY() == 0 // a touch outside both bars
- && !mRemoteInputController.isRemoteInputActive()) { // not due to typing in IME
+ && !mRemoteInputManager.getController()
+ .isRemoteInputActive()) { // not due to typing in IME
userAutohide();
}
}
- private void checkRemoteInputOutside(MotionEvent event) {
- if (event.getAction() == MotionEvent.ACTION_OUTSIDE // touch outside the source bar
- && event.getX() == 0 && event.getY() == 0 // a touch outside both bars
- && mRemoteInputController.isRemoteInputActive()) {
- mRemoteInputController.closeRemoteInputs();
- }
- }
-
private void userAutohide() {
cancelAutohide();
mHandler.postDelayed(mAutohide, 350); // longer than app gesture -> flag clear
@@ -3520,23 +2669,13 @@ public class StatusBar extends SystemUI implements DemoMode,
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
synchronized (mQueueLock) {
pw.println("Current Status Bar state:");
- pw.println(" mExpandedVisible=" + mExpandedVisible
- + ", mTrackingPosition=" + mTrackingPosition);
- pw.println(" mTracking=" + mTracking);
+ pw.println(" mExpandedVisible=" + mExpandedVisible);
pw.println(" mDisplayMetrics=" + mDisplayMetrics);
pw.println(" mStackScroller: " + viewInfo(mStackScroller));
pw.println(" mStackScroller: " + viewInfo(mStackScroller)
+ " scroll " + mStackScroller.getScrollX()
+ "," + mStackScroller.getScrollY());
}
- pw.print(" mPendingNotifications=");
- if (mPendingNotifications.size() == 0) {
- pw.println("null");
- } else {
- for (Entry entry : mPendingNotifications.values()) {
- pw.println(entry.notification);
- }
- }
pw.print(" mInteractingWindows="); pw.println(mInteractingWindows);
pw.print(" mStatusBarWindowState=");
@@ -3545,31 +2684,22 @@ public class StatusBar extends SystemUI implements DemoMode,
pw.println(BarTransitions.modeToString(mStatusBarMode));
pw.print(" mDozing="); pw.println(mDozing);
pw.print(" mZenMode=");
- pw.println(Settings.Global.zenModeToString(mZenMode));
- pw.print(" mUseHeadsUp=");
- pw.println(mUseHeadsUp);
- pw.print(" mKeyToRemoveOnGutsClosed=");
- pw.println(mKeyToRemoveOnGutsClosed);
+ pw.println(Settings.Global.zenModeToString(Settings.Global.getInt(
+ mContext.getContentResolver(), Settings.Global.ZEN_MODE,
+ Settings.Global.ZEN_MODE_OFF)));
+
if (mStatusBarView != null) {
dumpBarTransitions(pw, "mStatusBarView", mStatusBarView.getBarTransitions());
}
-
- pw.print(" mMediaSessionManager=");
- pw.println(mMediaSessionManager);
- pw.print(" mMediaNotificationKey=");
- pw.println(mMediaNotificationKey);
- pw.print(" mMediaController=");
- pw.print(mMediaController);
- if (mMediaController != null) {
- pw.print(" state=" + mMediaController.getPlaybackState());
+ pw.println(" StatusBarWindowView: ");
+ if (mStatusBarWindow != null) {
+ mStatusBarWindow.dump(fd, pw, args);
}
- pw.println();
- pw.print(" mMediaMetadata=");
- pw.print(mMediaMetadata);
- if (mMediaMetadata != null) {
- pw.print(" title=" + mMediaMetadata.getText(MediaMetadata.METADATA_KEY_TITLE));
+
+ pw.println(" mMediaManager: ");
+ if (mMediaManager != null) {
+ mMediaManager.dump(fd, pw, args);
}
- pw.println();
pw.println(" Panels: ");
if (mNotificationPanel != null) {
@@ -3598,28 +2728,28 @@ public class StatusBar extends SystemUI implements DemoMode,
mFingerprintUnlockController.dump(pw);
}
+ if (mKeyguardIndicationController != null) {
+ mKeyguardIndicationController.dump(fd, pw, args);
+ }
+
if (mScrimController != null) {
- mScrimController.dump(pw);
+ mScrimController.dump(fd, pw, args);
}
if (DUMPTRUCK) {
- synchronized (mNotificationData) {
- mNotificationData.dump(pw, " ");
+ synchronized (mEntryManager.getNotificationData()) {
+ mEntryManager.getNotificationData().dump(pw, " ");
}
if (false) {
pw.println("see the logcat for a dump of the views we have created.");
// must happen on ui thread
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- mStatusBarView.getLocationOnScreen(mAbsPos);
- Log.d(TAG, "mStatusBarView: ----- (" + mAbsPos[0] + "," + mAbsPos[1]
- + ") " + mStatusBarView.getWidth() + "x"
- + getStatusBarHeight());
- mStatusBarView.debug();
- }
- });
+ mHandler.post(() -> {
+ mStatusBarView.getLocationOnScreen(mAbsPos);
+ Log.d(TAG, "mStatusBarView: ----- (" + mAbsPos[0] + "," + mAbsPos[1] +
+ ") " + mStatusBarView.getWidth() + "x" + getStatusBarHeight());
+ mStatusBarView.debug();
+ });
}
}
@@ -3668,7 +2798,21 @@ public class StatusBar extends SystemUI implements DemoMode,
private void addStatusBarWindow() {
makeStatusBarView();
mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class);
- mRemoteInputController = new RemoteInputController(mHeadsUpManager);
+ mRemoteInputManager.setUpWithPresenter(this, mEntryManager, this,
+ new RemoteInputController.Delegate() {
+ public void setRemoteInputActive(NotificationData.Entry entry,
+ boolean remoteInputActive) {
+ mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
+ }
+ public void lockScrollTo(NotificationData.Entry entry) {
+ mStackScroller.lockScrollTo(entry.row);
+ }
+ public void requestDisallowLongPressAndDismiss() {
+ mStackScroller.requestDisallowLongPress();
+ mStackScroller.requestDisallowDismiss();
+ }
+ });
+ mRemoteInputManager.getController().addCallback(mStatusBarWindowManager);
mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());
}
@@ -3698,50 +2842,45 @@ public class StatusBar extends SystemUI implements DemoMode,
if (onlyProvisioned && !isDeviceProvisioned()) return;
final boolean afterKeyguardGone = PreviewInflater.wouldLaunchResolverActivity(
- mContext, intent, mCurrentUserId);
- Runnable runnable = new Runnable() {
- @Override
- public void run() {
- mAssistManager.hideAssist();
- intent.setFlags(
- Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
- int result = ActivityManager.START_CANCELED;
- ActivityOptions options = new ActivityOptions(getActivityOptions());
- options.setDisallowEnterPictureInPictureWhileLaunching(
- disallowEnterPictureInPictureWhileLaunching);
- if (intent == KeyguardBottomAreaView.INSECURE_CAMERA_INTENT) {
- // Normally an activity will set it's requested rotation
- // animation on its window. However when launching an activity
- // causes the orientation to change this is too late. In these cases
- // the default animation is used. This doesn't look good for
- // the camera (as it rotates the camera contents out of sync
- // with physical reality). So, we ask the WindowManager to
- // force the crossfade animation if an orientation change
- // happens to occur during the launch.
- options.setRotationAnimationHint(
- WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS);
- }
- try {
- result = ActivityManager.getService().startActivityAsUser(
- null, mContext.getBasePackageName(),
- intent,
- intent.resolveTypeIfNeeded(mContext.getContentResolver()),
- null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null,
- options.toBundle(), UserHandle.CURRENT.getIdentifier());
- } catch (RemoteException e) {
- Log.w(TAG, "Unable to start activity", e);
- }
- if (callback != null) {
- callback.onActivityStarted(result);
- }
+ mContext, intent, mLockscreenUserManager.getCurrentUserId());
+ Runnable runnable = () -> {
+ mAssistManager.hideAssist();
+ intent.setFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ int result = ActivityManager.START_CANCELED;
+ ActivityOptions options = new ActivityOptions(getActivityOptions(
+ null /* sourceNotification */));
+ options.setDisallowEnterPictureInPictureWhileLaunching(
+ disallowEnterPictureInPictureWhileLaunching);
+ if (intent == KeyguardBottomAreaView.INSECURE_CAMERA_INTENT) {
+ // Normally an activity will set it's requested rotation
+ // animation on its window. However when launching an activity
+ // causes the orientation to change this is too late. In these cases
+ // the default animation is used. This doesn't look good for
+ // the camera (as it rotates the camera contents out of sync
+ // with physical reality). So, we ask the WindowManager to
+ // force the crossfade animation if an orientation change
+ // happens to occur during the launch.
+ options.setRotationAnimationHint(
+ WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS);
+ }
+ try {
+ result = ActivityManager.getService().startActivityAsUser(
+ null, mContext.getBasePackageName(),
+ intent,
+ intent.resolveTypeIfNeeded(mContext.getContentResolver()),
+ null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null,
+ options.toBundle(), UserHandle.CURRENT.getIdentifier());
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unable to start activity", e);
+ }
+ if (callback != null) {
+ callback.onActivityStarted(result);
}
};
- Runnable cancelRunnable = new Runnable() {
- @Override
- public void run() {
- if (callback != null) {
- callback.onActivityStarted(ActivityManager.START_CANCELED);
- }
+ Runnable cancelRunnable = () -> {
+ if (callback != null) {
+ callback.onActivityStarted(ActivityManager.START_CANCELED);
}
};
executeRunnableDismissingKeyguard(runnable, cancelRunnable, dismissShade,
@@ -3786,17 +2925,17 @@ public class StatusBar extends SystemUI implements DemoMode,
}, cancelAction, afterKeyguardGone);
}
- private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (DEBUG) Log.v(TAG, "onReceive: " + intent);
String action = intent.getAction();
if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {
KeyboardShortcuts.dismiss();
- if (mRemoteInputController != null) {
- mRemoteInputController.closeRemoteInputs();
+ if (mRemoteInputManager.getController() != null) {
+ mRemoteInputManager.getController().closeRemoteInputs();
}
- if (isCurrentProfile(getSendingUserId())) {
+ if (mLockscreenUserManager.isCurrentProfile(getSendingUserId())) {
int flags = CommandQueue.FLAG_EXCLUDE_NONE;
String reason = intent.getStringExtra("reason");
if (reason != null && reason.equals(SYSTEM_DIALOG_REASON_RECENT_APPS)) {
@@ -3815,7 +2954,7 @@ public class StatusBar extends SystemUI implements DemoMode,
}
};
- private BroadcastReceiver mDemoReceiver = new BroadcastReceiver() {
+ private final BroadcastReceiver mDemoReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (DEBUG) Log.v(TAG, "onReceive: " + intent);
@@ -3841,7 +2980,8 @@ public class StatusBar extends SystemUI implements DemoMode,
};
public void resetUserExpandedStates() {
- ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
+ ArrayList<Entry> activeNotifications = mEntryManager.getNotificationData()
+ .getActiveNotifications();
final int notificationCount = activeNotifications.size();
for (int i = 0; i < notificationCount; i++) {
NotificationData.Entry entry = activeNotifications.get(i);
@@ -3884,27 +3024,40 @@ public class StatusBar extends SystemUI implements DemoMode,
Log.v(TAG, "configuration changed: " + mContext.getResources().getConfiguration());
}
- updateRowStates();
+ mViewHierarchyManager.updateRowStates();
mScreenPinningRequest.onConfigurationChanged();
}
- public void userSwitched(int newUserId) {
+ @Override
+ public void onUserSwitched(int newUserId) {
// Begin old BaseStatusBar.userSwitched
setHeadsUpUser(newUserId);
// End old BaseStatusBar.userSwitched
if (MULTIUSER_DEBUG) mNotificationPanelDebugText.setText("USER " + newUserId);
animateCollapsePanels();
updatePublicMode();
- mNotificationData.filterAndSort();
+ mEntryManager.getNotificationData().filterAndSort();
if (mReinflateNotificationsOnUserSwitched) {
- updateNotificationsOnDensityOrFontScaleChanged();
+ mEntryManager.updateNotificationsOnDensityOrFontScaleChanged();
mReinflateNotificationsOnUserSwitched = false;
}
- updateNotificationShade();
- clearCurrentMediaNotification();
+ updateNotificationViews();
+ mMediaManager.clearCurrentMediaNotification();
setLockscreenUser(newUserId);
}
+ @Override
+ public NotificationLockscreenUserManager getNotificationLockscreenUserManager() {
+ return mLockscreenUserManager;
+ }
+
+ @Override
+ public void onBindRow(Entry entry, PackageManager pmUser,
+ StatusBarNotification sbn, ExpandableNotificationRow row) {
+ row.setAboveShelfChangedListener(mAboveShelfObserver);
+ row.setSecureStateProvider(this::isKeyguardCurrentlySecure);
+ }
+
protected void setLockscreenUser(int newUserId) {
mLockscreenWallpaper.setCurrentUser(newUserId);
mScrimController.setCurrentUser(newUserId);
@@ -3954,9 +3107,9 @@ public class StatusBar extends SystemUI implements DemoMode,
protected void handleVisibleToUserChanged(boolean visibleToUser) {
if (visibleToUser) {
handleVisibleToUserChangedImpl(visibleToUser);
- startNotificationLogging();
+ mNotificationLogger.startNotificationLogging();
} else {
- stopNotificationLogging();
+ mNotificationLogger.stopNotificationLogging();
handleVisibleToUserChangedImpl(visibleToUser);
}
}
@@ -3965,7 +3118,8 @@ public class StatusBar extends SystemUI implements DemoMode,
try {
// consider the transition from peek to expanded to be a panel open,
// but not one that clears notification effects.
- int notificationLoad = mNotificationData.getActiveNotifications().size();
+ int notificationLoad = mEntryManager.getNotificationData()
+ .getActiveNotifications().size();
mBarService.onPanelRevealed(false, notificationLoad);
} catch (RemoteException ex) {
// Won't fail unless the world has ended.
@@ -3976,79 +3130,39 @@ public class StatusBar extends SystemUI implements DemoMode,
* The LEDs are turned off when the notification panel is shown, even just a little bit.
* See also StatusBar.setPanelExpanded for another place where we attempt to do this.
*/
- // Old BaseStatusBar.handleVisibileToUserChanged
private void handleVisibleToUserChangedImpl(boolean visibleToUser) {
- try {
- if (visibleToUser) {
- boolean pinnedHeadsUp = mHeadsUpManager.hasPinnedHeadsUp();
- boolean clearNotificationEffects =
- !isPanelFullyCollapsed() &&
- (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED);
- int notificationLoad = mNotificationData.getActiveNotifications().size();
- if (pinnedHeadsUp && isPanelFullyCollapsed()) {
- notificationLoad = 1;
+ if (visibleToUser) {
+ boolean pinnedHeadsUp = mHeadsUpManager.hasPinnedHeadsUp();
+ boolean clearNotificationEffects =
+ !isPresenterFullyCollapsed() &&
+ (mState == StatusBarState.SHADE
+ || mState == StatusBarState.SHADE_LOCKED);
+ int notificationLoad = mEntryManager.getNotificationData().getActiveNotifications()
+ .size();
+ if (pinnedHeadsUp && isPresenterFullyCollapsed()) {
+ notificationLoad = 1;
+ }
+ final int finalNotificationLoad = notificationLoad;
+ mUiOffloadThread.submit(() -> {
+ try {
+ mBarService.onPanelRevealed(clearNotificationEffects,
+ finalNotificationLoad);
+ } catch (RemoteException ex) {
+ // Won't fail unless the world has ended.
}
- mBarService.onPanelRevealed(clearNotificationEffects, notificationLoad);
- } else {
- mBarService.onPanelHidden();
- }
- } catch (RemoteException ex) {
- // Won't fail unless the world has ended.
- }
- }
-
- private void stopNotificationLogging() {
- // Report all notifications as invisible and turn down the
- // reporter.
- if (!mCurrentlyVisibleNotifications.isEmpty()) {
- logNotificationVisibilityChanges(Collections.<NotificationVisibility>emptyList(),
- mCurrentlyVisibleNotifications);
- recycleAllVisibilityObjects(mCurrentlyVisibleNotifications);
- }
- mHandler.removeCallbacks(mVisibilityReporter);
- mStackScroller.setChildLocationsChangedListener(null);
- }
-
- private void startNotificationLogging() {
- mStackScroller.setChildLocationsChangedListener(mNotificationLocationsChangedListener);
- // Some transitions like mVisibleToUser=false -> mVisibleToUser=true don't
- // cause the scroller to emit child location events. Hence generate
- // one ourselves to guarantee that we're reporting visible
- // notifications.
- // (Note that in cases where the scroller does emit events, this
- // additional event doesn't break anything.)
- mNotificationLocationsChangedListener.onChildLocationsChanged(mStackScroller);
- }
-
- private void logNotificationVisibilityChanges(
- Collection<NotificationVisibility> newlyVisible,
- Collection<NotificationVisibility> noLongerVisible) {
- if (newlyVisible.isEmpty() && noLongerVisible.isEmpty()) {
- return;
- }
- NotificationVisibility[] newlyVisibleAr =
- newlyVisible.toArray(new NotificationVisibility[newlyVisible.size()]);
- NotificationVisibility[] noLongerVisibleAr =
- noLongerVisible.toArray(new NotificationVisibility[noLongerVisible.size()]);
- try {
- mBarService.onNotificationVisibilityChanged(newlyVisibleAr, noLongerVisibleAr);
- } catch (RemoteException e) {
- // Ignore.
+ });
+ } else {
+ mUiOffloadThread.submit(() -> {
+ try {
+ mBarService.onPanelHidden();
+ } catch (RemoteException ex) {
+ // Won't fail unless the world has ended.
+ }
+ });
}
- final int N = newlyVisible.size();
- if (N > 0) {
- String[] newlyVisibleKeyAr = new String[N];
- for (int i = 0; i < N; i++) {
- newlyVisibleKeyAr[i] = newlyVisibleAr[i].key;
- }
-
- setNotificationsShown(newlyVisibleKeyAr);
- }
}
- // State logging
-
private void logStateToEventlog() {
boolean isShowing = mStatusBarKeyguardViewManager.isShowing();
boolean isOccluded = mStatusBarKeyguardViewManager.isOccluded();
@@ -4109,7 +3223,7 @@ public class StatusBar extends SystemUI implements DemoMode,
vib.vibrate(250, VIBRATION_ATTRIBUTES);
}
- Runnable mStartTracing = new Runnable() {
+ final Runnable mStartTracing = new Runnable() {
@Override
public void run() {
vibrate();
@@ -4120,13 +3234,10 @@ public class StatusBar extends SystemUI implements DemoMode,
}
};
- Runnable mStopTracing = new Runnable() {
- @Override
- public void run() {
- android.os.Debug.stopMethodTracing();
- Log.d(TAG, "stopTracing");
- vibrate();
- }
+ final Runnable mStopTracing = () -> {
+ android.os.Debug.stopMethodTracing();
+ Log.d(TAG, "stopTracing");
+ vibrate();
};
@Override
@@ -4153,49 +3264,16 @@ public class StatusBar extends SystemUI implements DemoMode,
startActivityDismissingKeyguard(intent, onlyProvisioned, true /* dismissShade */);
}
- private static class FastColorDrawable extends Drawable {
- private final int mColor;
-
- public FastColorDrawable(int color) {
- mColor = 0xff000000 | color;
- }
-
- @Override
- public void draw(Canvas canvas) {
- canvas.drawColor(mColor, PorterDuff.Mode.SRC);
- }
-
- @Override
- public void setAlpha(int alpha) {
- }
-
- @Override
- public void setColorFilter(ColorFilter colorFilter) {
- }
-
- @Override
- public int getOpacity() {
- return PixelFormat.OPAQUE;
- }
-
- @Override
- public void setBounds(int left, int top, int right, int bottom) {
- }
-
- @Override
- public void setBounds(Rect bounds) {
- }
- }
-
public void destroy() {
// Begin old BaseStatusBar.destroy().
- mContext.unregisterReceiver(mBaseBroadcastReceiver);
+ mContext.unregisterReceiver(mBannerActionBroadcastReceiver);
+ mLockscreenUserManager.destroy();
try {
mNotificationListener.unregisterAsSystemService();
} catch (RemoteException e) {
// Ignore.
}
- mDeviceProvisionedController.removeCallback(mDeviceProvisionedListener);
+ mEntryManager.destroy();
// End old BaseStatusBar.destroy().
if (mStatusBarWindow != null) {
mWindowManager.removeViewImmediate(mStatusBarWindow);
@@ -4279,6 +3357,9 @@ public class StatusBar extends SystemUI implements DemoMode,
}
}
}
+ if (modeChange || command.equals(COMMAND_OPERATOR)) {
+ dispatchDemoCommandToView(command, args, R.id.operator_name);
+ }
}
private void dispatchDemoCommandToView(String command, Bundle args, int id) {
@@ -4296,7 +3377,8 @@ public class StatusBar extends SystemUI implements DemoMode,
return mState;
}
- public boolean isPanelFullyCollapsed() {
+ @Override
+ public boolean isPresenterFullyCollapsed() {
return mNotificationPanel.isFullyCollapsed();
}
@@ -4376,12 +3458,11 @@ public class StatusBar extends SystemUI implements DemoMode,
releaseGestureWakeLock();
runLaunchTransitionEndRunnable();
mLaunchTransitionFadingAway = false;
- mScrimController.forceHideScrims(false /* hide */, false /* animated */);
updateMediaMetaData(true /* metaDataChanged */, true);
}
public boolean isCollapsing() {
- return mNotificationPanel.isCollapsing();
+ return mNotificationPanel.isCollapsing() || mActivityLaunchAnimator.isAnimationPending();
}
public void addPostCollapseAction(Runnable r) {
@@ -4404,31 +3485,23 @@ public class StatusBar extends SystemUI implements DemoMode,
Runnable endRunnable) {
mHandler.removeMessages(MSG_LAUNCH_TRANSITION_TIMEOUT);
mLaunchTransitionEndRunnable = endRunnable;
- Runnable hideRunnable = new Runnable() {
- @Override
- public void run() {
- mLaunchTransitionFadingAway = true;
- if (beforeFading != null) {
- beforeFading.run();
- }
- mScrimController.forceHideScrims(true /* hide */, false /* animated */);
- updateMediaMetaData(false, true);
- mNotificationPanel.setAlpha(1);
- mStackScroller.setParentNotFullyVisible(true);
- mNotificationPanel.animate()
- .alpha(0)
- .setStartDelay(FADE_KEYGUARD_START_DELAY)
- .setDuration(FADE_KEYGUARD_DURATION)
- .withLayer()
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- onLaunchTransitionFadingEnded();
- }
- });
- mCommandQueue.appTransitionStarting(SystemClock.uptimeMillis(),
- LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION, true);
- }
+ Runnable hideRunnable = () -> {
+ mLaunchTransitionFadingAway = true;
+ if (beforeFading != null) {
+ beforeFading.run();
+ }
+ updateScrimController();
+ updateMediaMetaData(false, true);
+ mNotificationPanel.setAlpha(1);
+ mStackScroller.setParentNotFullyVisible(true);
+ mNotificationPanel.animate()
+ .alpha(0)
+ .setStartDelay(FADE_KEYGUARD_START_DELAY)
+ .setDuration(FADE_KEYGUARD_DURATION)
+ .withLayer()
+ .withEndAction(this::onLaunchTransitionFadingEnded);
+ mCommandQueue.appTransitionStarting(SystemClock.uptimeMillis(),
+ LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION, true);
};
if (mNotificationPanel.isLaunchTransitionRunning()) {
mNotificationPanel.setLaunchTransitionEndRunnable(hideRunnable);
@@ -4448,14 +3521,16 @@ public class StatusBar extends SystemUI implements DemoMode,
.setStartDelay(0)
.setDuration(FADE_KEYGUARD_DURATION_PULSING)
.setInterpolator(ScrimController.KEYGUARD_FADE_OUT_INTERPOLATOR)
- .start();
+ .withEndAction(()-> {
+ hideKeyguard();
+ mStatusBarKeyguardViewManager.onKeyguardFadedAway();
+ }).start();
}
/**
* Plays the animation when an activity that was occluding Keyguard goes away.
*/
public void animateKeyguardUnoccluding() {
- mScrimController.animateKeyguardUnoccluding(500);
mNotificationPanel.setExpandedFraction(0f);
animateExpandNotificationsPanel();
}
@@ -4557,7 +3632,6 @@ public class StatusBar extends SystemUI implements DemoMode,
// Treat Keyguard exit animation as an app transition to achieve nice transition for status
// bar.
- mKeyguardGoingAway = true;
mKeyguardMonitor.notifyKeyguardGoingAway(true);
mCommandQueue.appTransitionPending(true);
}
@@ -4566,14 +3640,13 @@ public class StatusBar extends SystemUI implements DemoMode,
* Notifies the status bar the Keyguard is fading away with the specified timings.
*
* @param startTime the start time of the animations in uptime millis
- * @param delay the precalculated animation delay in miliseconds
+ * @param delay the precalculated animation delay in milliseconds
* @param fadeoutDuration the duration of the exit animation, in milliseconds
*/
public void setKeyguardFadingAway(long startTime, long delay, long fadeoutDuration) {
mKeyguardFadingAway = true;
mKeyguardFadingAwayDelay = delay;
mKeyguardFadingAwayDuration = fadeoutDuration;
- mWaitingForKeyguardExit = false;
mCommandQueue.appTransitionStarting(startTime + fadeoutDuration
- LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION,
LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION, true);
@@ -4593,26 +3666,24 @@ public class StatusBar extends SystemUI implements DemoMode,
*/
public void finishKeyguardFadingAway() {
mKeyguardFadingAway = false;
- mKeyguardGoingAway = false;
mKeyguardMonitor.notifyKeyguardDoneFading();
}
- public void stopWaitingForKeyguardExit() {
- mWaitingForKeyguardExit = false;
- }
-
+ // TODO: Move this to NotificationLockscreenUserManager.
private void updatePublicMode() {
final boolean showingKeyguard = mStatusBarKeyguardViewManager.isShowing();
final boolean devicePublic = showingKeyguard
- && mStatusBarKeyguardViewManager.isSecure(mCurrentUserId);
+ && mStatusBarKeyguardViewManager.isSecure(
+ mLockscreenUserManager.getCurrentUserId());
// Look for public mode users. Users are considered public in either case of:
// - device keyguard is shown in secure mode;
// - profile is locked with a work challenge.
- for (int i = mCurrentProfiles.size() - 1; i >= 0; i--) {
- final int userId = mCurrentProfiles.valueAt(i).id;
+ SparseArray<UserInfo> currentProfiles = mLockscreenUserManager.getCurrentProfiles();
+ for (int i = currentProfiles.size() - 1; i >= 0; i--) {
+ final int userId = currentProfiles.valueAt(i).id;
boolean isProfilePublic = devicePublic;
- if (!devicePublic && userId != mCurrentUserId) {
+ if (!devicePublic && userId != mLockscreenUserManager.getCurrentUserId()) {
// We can't rely on KeyguardManager#isDeviceLocked() for unified profile challenge
// due to a race condition where this code could be called before
// TrustManagerService updates its internal records, resulting in an incorrect
@@ -4622,7 +3693,7 @@ public class StatusBar extends SystemUI implements DemoMode,
isProfilePublic = mKeyguardManager.isDeviceLocked(userId);
}
}
- setLockscreenPublicMode(isProfilePublic, userId);
+ mLockscreenUserManager.setLockscreenPublicMode(isProfilePublic, userId);
}
}
@@ -4650,18 +3721,14 @@ public class StatusBar extends SystemUI implements DemoMode,
mAmbientIndicationContainer.setVisibility(View.INVISIBLE);
}
}
- if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) {
- mScrimController.setKeyguardShowing(true);
- } else {
- mScrimController.setKeyguardShowing(false);
- }
mNotificationPanel.setBarState(mState, mKeyguardFadingAway, goingToFullShade);
updateTheme();
updateDozingState();
updatePublicMode();
updateStackScrollerState(goingToFullShade, fromShadeLocked);
- updateNotifications();
+ mEntryManager.updateNotifications();
checkBarModes();
+ updateScrimController();
updateMediaMetaData(false, mState != StatusBarState.KEYGUARD);
mKeyguardMonitor.notifyKeyguardState(mStatusBarKeyguardViewManager.isShowing(),
mUnlockMethodCache.isMethodSecure(),
@@ -4681,12 +3748,14 @@ public class StatusBar extends SystemUI implements DemoMode,
final boolean useDarkTheme = systemColors != null
&& (systemColors.getColorHints() & WallpaperColors.HINT_SUPPORTS_DARK_THEME) != 0;
if (isUsingDarkTheme() != useDarkTheme) {
- try {
- mOverlayManager.setEnabled("com.android.systemui.theme.dark",
- useDarkTheme, mCurrentUserId);
- } catch (RemoteException e) {
- Log.w(TAG, "Can't change theme", e);
- }
+ mUiOffloadThread.submit(() -> {
+ try {
+ mOverlayManager.setEnabled("com.android.systemui.theme.dark",
+ useDarkTheme, mLockscreenUserManager.getCurrentUserId());
+ } catch (RemoteException e) {
+ Log.w(TAG, "Can't change theme", e);
+ }
+ });
}
// Lock wallpaper defines the color of the majority of the views, hence we'll use it
@@ -4697,7 +3766,7 @@ public class StatusBar extends SystemUI implements DemoMode,
if (mContext.getThemeResId() != themeResId) {
mContext.setTheme(themeResId);
if (inflated) {
- reinflateViews();
+ onThemeChanged();
}
}
@@ -4720,22 +3789,22 @@ public class StatusBar extends SystemUI implements DemoMode,
private void updateDozingState() {
Trace.traceCounter(Trace.TRACE_TAG_APP, "dozing", mDozing ? 1 : 0);
Trace.beginSection("StatusBar#updateDozingState");
- boolean animate = !mDozing && mDozeServiceHost.shouldAnimateWakeup();
+ boolean animate = (!mDozing && mDozeServiceHost.shouldAnimateWakeup())
+ || (mDozing && mDozeServiceHost.shouldAnimateScreenOff());
mNotificationPanel.setDozing(mDozing, animate);
mStackScroller.setDark(mDozing, animate, mWakeUpTouchLocation);
- mScrimController.setDozing(mDozing);
+ mDozeScrimController.setDozing(mDozing);
mKeyguardIndicationController.setDozing(mDozing);
mNotificationPanel.setDark(mDozing, animate);
updateQsExpansionEnabled();
- mDozeScrimController.setDozing(mDozing, animate);
- updateRowStates();
+ mViewHierarchyManager.updateRowStates();
Trace.endSection();
}
public void updateStackScrollerState(boolean goingToFullShade, boolean fromShadeLocked) {
if (mStackScroller == null) return;
boolean onKeyguard = mState == StatusBarState.KEYGUARD;
- boolean publicMode = isAnyProfilePublicMode();
+ boolean publicMode = mLockscreenUserManager.isAnyProfilePublicMode();
mStackScroller.setHideSensitive(publicMode, goingToFullShade);
mStackScroller.setDimmed(onKeyguard, fromShadeLocked /* animate */);
mStackScroller.setExpandingEnabled(!onKeyguard);
@@ -4814,7 +3883,6 @@ public class StatusBar extends SystemUI implements DemoMode,
}
protected void showBouncer() {
- mWaitingForKeyguardExit = mStatusBarKeyguardViewManager.isShowing();
mStatusBarKeyguardViewManager.dismiss();
}
@@ -4827,11 +3895,12 @@ public class StatusBar extends SystemUI implements DemoMode,
private void instantCollapseNotificationPanel() {
mNotificationPanel.instantCollapse();
+ runPostCollapseRunnables();
}
@Override
public void onActivated(ActivatableNotificationView view) {
- onActivated((View)view);
+ onActivated((View) view);
mStackScroller.setActivatedChild(view);
}
@@ -4859,7 +3928,7 @@ public class StatusBar extends SystemUI implements DemoMode,
clearNotificationEffects();
}
if (state == StatusBarState.KEYGUARD) {
- removeRemoteInputEntriesKeptUntilCollapsed();
+ mRemoteInputManager.removeRemoteInputEntriesKeptUntilCollapsed();
maybeEscalateHeadsUp();
}
mState = state;
@@ -4893,7 +3962,7 @@ public class StatusBar extends SystemUI implements DemoMode,
public void onClosingFinished() {
runPostCollapseRunnables();
- if (!isPanelFullyCollapsed()) {
+ if (!isPresenterFullyCollapsed()) {
// if we set it not to be focusable when collapsing, we have to undo it when we aborted
// the closing
mStatusBarWindowManager.setStatusBarFocusable(true);
@@ -4933,7 +4002,8 @@ public class StatusBar extends SystemUI implements DemoMode,
}
}
- protected int getMaxKeyguardNotifications(boolean recompute) {
+ @Override
+ public int getMaxNotificationsWhileLocked(boolean recompute) {
if (recompute) {
mMaxKeyguardNotifications = Math.max(1,
mNotificationPanel.computeMaxKeyguardNotifications(
@@ -4943,8 +4013,8 @@ public class StatusBar extends SystemUI implements DemoMode,
return mMaxKeyguardNotifications;
}
- public int getMaxKeyguardNotifications() {
- return getMaxKeyguardNotifications(false /* recompute */);
+ public int getMaxNotificationsWhileLocked() {
+ return getMaxNotificationsWhileLocked(false /* recompute */);
}
// TODO: Figure out way to remove these.
@@ -5004,7 +4074,7 @@ public class StatusBar extends SystemUI implements DemoMode,
@Override
public void onTouchSlopExceeded() {
- mStackScroller.removeLongPressCallback();
+ mStackScroller.cancelLongPress();
mStackScroller.checkSnoozeLeavebehind();
}
@@ -5026,7 +4096,11 @@ public class StatusBar extends SystemUI implements DemoMode,
* @param expandView The view to expand after going to the shade.
*/
public void goToLockedShade(View expandView) {
- int userId = mCurrentUserId;
+ if ((mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
+ return;
+ }
+
+ int userId = mLockscreenUserManager.getCurrentUserId();
ExpandableNotificationRow row = null;
if (expandView instanceof ExpandableNotificationRow) {
row = (ExpandableNotificationRow) expandView;
@@ -5038,9 +4112,11 @@ public class StatusBar extends SystemUI implements DemoMode,
userId = row.getStatusBarNotification().getUserId();
}
}
- boolean fullShadeNeedsBouncer = !userAllowsPrivateNotificationsInPublic(mCurrentUserId)
- || !mShowLockscreenNotifications || mFalsingManager.shouldEnforceBouncer();
- if (isLockscreenPublicMode(userId) && fullShadeNeedsBouncer) {
+ boolean fullShadeNeedsBouncer = !mLockscreenUserManager.
+ userAllowsPrivateNotificationsInPublic(mLockscreenUserManager.getCurrentUserId())
+ || !mLockscreenUserManager.shouldShowLockscreenNotifications()
+ || mFalsingManager.shouldEnforceBouncer();
+ if (mLockscreenUserManager.isLockscreenPublicMode(userId) && fullShadeNeedsBouncer) {
mLeaveOpenOnKeyguardHide = true;
showBouncerIfKeyguard();
mDraggedDownRow = row;
@@ -5057,13 +4133,15 @@ public class StatusBar extends SystemUI implements DemoMode,
dismissKeyguardThenExecute(dismissAction, true /* afterKeyguardGone */);
}
- protected void onLockedRemoteInput(ExpandableNotificationRow row, View clicked) {
+ @Override
+ public void onLockedRemoteInput(ExpandableNotificationRow row, View clicked) {
mLeaveOpenOnKeyguardHide = true;
showBouncer();
mPendingRemoteInputView = clicked;
}
- protected void onMakeExpandedVisibleForRemoteInput(ExpandableNotificationRow row,
+ @Override
+ public void onMakeExpandedVisibleForRemoteInput(ExpandableNotificationRow row,
View clickedView) {
if (isKeyguardShowing()) {
onLockedRemoteInput(row, clickedView);
@@ -5073,6 +4151,47 @@ public class StatusBar extends SystemUI implements DemoMode,
}
}
+ @Override
+ public boolean shouldHandleRemoteInput(View view, PendingIntent pendingIntent) {
+ // Skip remote input as doing so will expand the notification shade.
+ return (mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0;
+ }
+
+ @Override
+ public boolean handleRemoteViewClick(View view, PendingIntent pendingIntent,
+ Intent fillInIntent, NotificationRemoteInputManager.ClickHandler defaultHandler) {
+ final boolean isActivity = pendingIntent.isActivity();
+ if (isActivity) {
+ final boolean afterKeyguardGone = PreviewInflater.wouldLaunchResolverActivity(
+ mContext, pendingIntent.getIntent(), mLockscreenUserManager.getCurrentUserId());
+ dismissKeyguardThenExecute(() -> {
+ try {
+ ActivityManager.getService().resumeAppSwitches();
+ } catch (RemoteException e) {
+ }
+
+ boolean handled = defaultHandler.handleClick();
+
+ // close the shade if it was open
+ if (handled && !mNotificationPanel.isFullyCollapsed()) {
+ animateCollapsePanels(
+ CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
+ visibilityChanged(false);
+ mAssistManager.hideAssist();
+
+ // Wait for activity start.
+ return true;
+ } else {
+ return false;
+ }
+
+ }, afterKeyguardGone);
+ return true;
+ } else {
+ return defaultHandler.handleClick();
+ }
+ }
+
protected boolean startWorkChallengeIfNecessary(int userId, IntentSender intendSender,
String notificationKey) {
// Clear pending remote view, as we do not want to trigger pending remote input view when
@@ -5109,7 +4228,8 @@ public class StatusBar extends SystemUI implements DemoMode,
// End old BaseStatusBar.startWorkChallengeIfNecessary.
}
- protected void onLockedWorkRemoteInput(int userId, ExpandableNotificationRow row,
+ @Override
+ public void onLockedWorkRemoteInput(int userId, ExpandableNotificationRow row,
View clicked) {
// Collapse notification and show work challenge
animateCollapsePanels();
@@ -5119,65 +4239,48 @@ public class StatusBar extends SystemUI implements DemoMode,
mPendingWorkRemoteInputView = clicked;
}
- private boolean isAnyProfilePublicMode() {
- for (int i = mCurrentProfiles.size() - 1; i >= 0; i--) {
- if (isLockscreenPublicMode(mCurrentProfiles.valueAt(i).id)) {
- return true;
- }
- }
- return false;
- }
-
- protected void onWorkChallengeChanged() {
+ @Override
+ public void onWorkChallengeChanged() {
updatePublicMode();
- updateNotifications();
- if (mPendingWorkRemoteInputView != null && !isAnyProfilePublicMode()) {
+ mEntryManager.updateNotifications();
+ if (mPendingWorkRemoteInputView != null
+ && !mLockscreenUserManager.isAnyProfilePublicMode()) {
// Expand notification panel and the notification row, then click on remote input view
- final Runnable clickPendingViewRunnable = new Runnable() {
- @Override
- public void run() {
- final View pendingWorkRemoteInputView = mPendingWorkRemoteInputView;
- if (pendingWorkRemoteInputView == null) {
+ final Runnable clickPendingViewRunnable = () -> {
+ final View pendingWorkRemoteInputView = mPendingWorkRemoteInputView;
+ if (pendingWorkRemoteInputView == null) {
+ return;
+ }
+
+ // Climb up the hierarchy until we get to the container for this row.
+ ViewParent p = pendingWorkRemoteInputView.getParent();
+ while (!(p instanceof ExpandableNotificationRow)) {
+ if (p == null) {
return;
}
+ p = p.getParent();
+ }
- // Climb up the hierarchy until we get to the container for this row.
- ViewParent p = pendingWorkRemoteInputView.getParent();
- while (!(p instanceof ExpandableNotificationRow)) {
- if (p == null) {
- return;
+ final ExpandableNotificationRow row = (ExpandableNotificationRow) p;
+ ViewParent viewParent = row.getParent();
+ if (viewParent instanceof NotificationStackScrollLayout) {
+ final NotificationStackScrollLayout scrollLayout =
+ (NotificationStackScrollLayout) viewParent;
+ row.makeActionsVisibile();
+ row.post(() -> {
+ final Runnable finishScrollingCallback = () -> {
+ mPendingWorkRemoteInputView.callOnClick();
+ mPendingWorkRemoteInputView = null;
+ scrollLayout.setFinishScrollingCallback(null);
+ };
+ if (scrollLayout.scrollTo(row)) {
+ // It scrolls! So call it when it's finished.
+ scrollLayout.setFinishScrollingCallback(finishScrollingCallback);
+ } else {
+ // It does not scroll, so call it now!
+ finishScrollingCallback.run();
}
- p = p.getParent();
- }
-
- final ExpandableNotificationRow row = (ExpandableNotificationRow) p;
- ViewParent viewParent = row.getParent();
- if (viewParent instanceof NotificationStackScrollLayout) {
- final NotificationStackScrollLayout scrollLayout =
- (NotificationStackScrollLayout) viewParent;
- row.makeActionsVisibile();
- row.post(new Runnable() {
- @Override
- public void run() {
- final Runnable finishScrollingCallback = new Runnable() {
- @Override
- public void run() {
- mPendingWorkRemoteInputView.callOnClick();
- mPendingWorkRemoteInputView = null;
- scrollLayout.setFinishScrollingCallback(null);
- }
- };
- if (scrollLayout.scrollTo(row)) {
- // It scrolls! So call it when it's finished.
- scrollLayout.setFinishScrollingCallback(
- finishScrollingCallback);
- } else {
- // It does not scroll, so call it now!
- finishScrollingCallback.run();
- }
- }
- });
- }
+ });
}
};
mNotificationPanel.getViewTreeObserver().addOnGlobalLayoutListener(
@@ -5229,6 +4332,7 @@ public class StatusBar extends SystemUI implements DemoMode,
if (mStatusBarView != null) mStatusBarView.setBouncerShowing(bouncerShowing);
updateHideIconsForBouncer(true /* animate */);
recomputeDisableFlags(true /* animate */);
+ updateScrimController();
}
public void cancelCurrentTouch() {
@@ -5240,7 +4344,7 @@ public class StatusBar extends SystemUI implements DemoMode,
}
}
- WakefulnessLifecycle.Observer mWakefulnessObserver = new WakefulnessLifecycle.Observer() {
+ final WakefulnessLifecycle.Observer mWakefulnessObserver = new WakefulnessLifecycle.Observer() {
@Override
public void onFinishedGoingToSleep() {
mNotificationPanel.onAffordanceLaunchEnded();
@@ -5263,12 +4367,7 @@ public class StatusBar extends SystemUI implements DemoMode,
// This gets executed before we will show Keyguard, so post it in order that the state
// is correct.
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- onCameraLaunchGestureDetected(mLastCameraLaunchSource);
- }
- });
+ mHandler.post(() -> onCameraLaunchGestureDetected(mLastCameraLaunchSource));
}
updateIsKeyguard();
}
@@ -5285,38 +4384,36 @@ public class StatusBar extends SystemUI implements DemoMode,
mStackScroller.setAnimationsEnabled(true);
mVisualStabilityManager.setScreenOn(true);
mNotificationPanel.setTouchDisabled(false);
-
- maybePrepareWakeUpFromAod();
-
mDozeServiceHost.stopDozing();
updateVisibleToUser();
updateIsKeyguard();
+ updateScrimController();
}
};
- ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
+ final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
@Override
public void onScreenTurningOn() {
mFalsingManager.onScreenTurningOn();
mNotificationPanel.onScreenTurningOn();
- maybePrepareWakeUpFromAod();
-
if (mLaunchCameraOnScreenTurningOn) {
mNotificationPanel.launchCamera(false, mLastCameraLaunchSource);
mLaunchCameraOnScreenTurningOn = false;
}
+
+ updateScrimController();
}
@Override
public void onScreenTurnedOn() {
- mScrimController.wakeUpFromAod();
- mDozeScrimController.onScreenTurnedOn();
+ mScrimController.onScreenTurnedOn();
}
@Override
public void onScreenTurnedOff() {
mFalsingManager.onScreenOff();
+ mScrimController.onScreenTurnedOff();
// If we pulse in from AOD, we turn the screen off first. However, updatingIsKeyguard
// in that case destroys the HeadsUpManager state, so don't do it in that case.
if (!isPulsing()) {
@@ -5329,13 +4426,6 @@ public class StatusBar extends SystemUI implements DemoMode,
return mWakefulnessLifecycle.getWakefulness();
}
- private void maybePrepareWakeUpFromAod() {
- int wakefulness = mWakefulnessLifecycle.getWakefulness();
- if (mDozing && wakefulness == WAKEFULNESS_WAKING && !isPulsing()) {
- mScrimController.prepareWakeUpFromAod();
- }
- }
-
private void vibrateForCameraGesture() {
// Make sure to pass -1 for repeat so VibratorService doesn't stop us when going to sleep.
mVibrator.vibrate(mCameraLaunchGestureVibePattern, -1 /* repeat */);
@@ -5364,9 +4454,10 @@ public class StatusBar extends SystemUI implements DemoMode,
}
public boolean hasActiveNotifications() {
- return !mNotificationData.getActiveNotifications().isEmpty();
+ return !mEntryManager.getNotificationData().getActiveNotifications().isEmpty();
}
+ @Override
public void wakeUpIfDozing(long time, View where) {
if (mDozing) {
PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
@@ -5381,6 +4472,11 @@ public class StatusBar extends SystemUI implements DemoMode,
}
@Override
+ public boolean isDeviceLocked(int userId) {
+ return mKeyguardManager.isDeviceLocked(userId);
+ }
+
+ @Override
public void appTransitionCancelled() {
EventBus.getDefault().send(new AppTransitionFinishedEvent());
}
@@ -5418,12 +4514,12 @@ public class StatusBar extends SystemUI implements DemoMode,
if (!mDeviceInteractive) {
// Avoid flickering of the scrim when we instant launch the camera and the bouncer
// comes on.
- mScrimController.dontAnimateBouncerChangesUntilNextFrame();
mGestureWakeLock.acquire(LAUNCH_TRANSITION_TIMEOUT_MS + 1000L);
}
if (isScreenTurningOnOrOn()) {
if (DEBUG_CAMERA_LIFT) Slog.d(TAG, "Launching camera");
mNotificationPanel.launchCamera(mDeviceInteractive /* animate */, source);
+ updateScrimController();
} else {
// We need to defer the camera launch until the screen comes on, since otherwise
// we will dismiss us too early since we are waiting on an activity to be drawn and
@@ -5436,11 +4532,14 @@ public class StatusBar extends SystemUI implements DemoMode,
}
boolean isCameraAllowedByAdmin() {
- if (mDevicePolicyManager.getCameraDisabled(null, mCurrentUserId)) {
+ if (mDevicePolicyManager.getCameraDisabled(null,
+ mLockscreenUserManager.getCurrentUserId())) {
return false;
- } else if (isKeyguardShowing() && isKeyguardSecure()) {
+ } else if (mStatusBarKeyguardViewManager == null ||
+ (isKeyguardShowing() && isKeyguardSecure())) {
// Check if the admin has disabled the camera specifically for the keyguard
- return (mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUserId)
+ return (mDevicePolicyManager.
+ getKeyguardDisabledFeatures(null, mLockscreenUserManager.getCurrentUserId())
& DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA) == 0;
}
@@ -5459,26 +4558,56 @@ public class StatusBar extends SystemUI implements DemoMode,
public void notifyFpAuthModeChanged() {
updateDozing();
+ updateScrimController();
}
private void updateDozing() {
Trace.beginSection("StatusBar#updateDozing");
// When in wake-and-unlock while pulsing, keep dozing state until fully unlocked.
- mDozing = mDozingRequested && mState == StatusBarState.KEYGUARD
+ boolean dozing = mDozingRequested && mState == StatusBarState.KEYGUARD
|| mFingerprintUnlockController.getMode()
== FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
// When in wake-and-unlock we may not have received a change to mState
// but we still should not be dozing, manually set to false.
if (mFingerprintUnlockController.getMode() ==
FingerprintUnlockController.MODE_WAKE_AND_UNLOCK) {
- mDozing = false;
+ dozing = false;
}
+ mDozing = dozing;
mStatusBarWindowManager.setDozing(mDozing);
mStatusBarKeyguardViewManager.setDozing(mDozing);
if (mAmbientIndicationContainer instanceof DozeReceiver) {
((DozeReceiver) mAmbientIndicationContainer).setDozing(mDozing);
}
updateDozingState();
+ updateReportRejectedTouchVisibility();
+ Trace.endSection();
+ }
+
+ @VisibleForTesting
+ void updateScrimController() {
+ Trace.beginSection("StatusBar#updateScrimController");
+
+ // We don't want to end up in KEYGUARD state when we're unlocking with
+ // fingerprint from doze. We should cross fade directly from black.
+ final boolean wakeAndUnlocking = mFingerprintUnlockController.getMode()
+ == FingerprintUnlockController.MODE_WAKE_AND_UNLOCK;
+
+ if (mBouncerShowing) {
+ mScrimController.transitionTo(ScrimState.BOUNCER);
+ } else if (mLaunchCameraOnScreenTurningOn || isInLaunchTransition()) {
+ mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback);
+ } else if (mBrightnessMirrorVisible) {
+ mScrimController.transitionTo(ScrimState.BRIGHTNESS_MIRROR);
+ } else if (isPulsing()) {
+ // Handled in DozeScrimController#setPulsing
+ } else if (mDozing) {
+ mScrimController.transitionTo(ScrimState.AOD);
+ } else if (mIsKeyguard && !wakeAndUnlocking) {
+ mScrimController.transitionTo(ScrimState.KEYGUARD);
+ } else {
+ mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback);
+ }
Trace.endSection();
}
@@ -5491,8 +4620,9 @@ public class StatusBar extends SystemUI implements DemoMode,
}
private final class DozeServiceHost implements DozeHost {
- private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
+ private final ArrayList<Callback> mCallbacks = new ArrayList<>();
private boolean mAnimateWakeup;
+ private boolean mAnimateScreenOff;
private boolean mIgnoreTouchWhilePulsing;
@Override
@@ -5541,29 +4671,25 @@ public class StatusBar extends SystemUI implements DemoMode,
}
mDozeScrimController.pulse(new PulseCallback() {
-
@Override
public void onPulseStarted() {
callback.onPulseStarted();
- Collection<HeadsUpManager.HeadsUpEntry> pulsingEntries =
- mHeadsUpManager.getAllEntries();
- if (!pulsingEntries.isEmpty()) {
+ if (mHeadsUpManager.hasHeadsUpNotifications()) {
// Only pulse the stack scroller if there's actually something to show.
// Otherwise just show the always-on screen.
- setPulsing(pulsingEntries);
+ setPulsing(true);
}
}
@Override
public void onPulseFinished() {
callback.onPulseFinished();
- setPulsing(null);
+ setPulsing(false);
}
- private void setPulsing(Collection<HeadsUpManager.HeadsUpEntry> pulsing) {
- mStackScroller.setPulsing(pulsing);
- mNotificationPanel.setPulsing(pulsing != null);
- mVisualStabilityManager.setPulsing(pulsing != null);
+ private void setPulsing(boolean pulsing) {
+ mNotificationPanel.setPulsing(pulsing);
+ mVisualStabilityManager.setPulsing(pulsing);
mIgnoreTouchWhilePulsing = false;
}
}, reason);
@@ -5591,7 +4717,7 @@ public class StatusBar extends SystemUI implements DemoMode,
@Override
public void dozeTimeTick() {
- mNotificationPanel.refreshTime();
+ mNotificationPanel.dozeTimeTick();
}
@Override
@@ -5626,11 +4752,6 @@ public class StatusBar extends SystemUI implements DemoMode,
}
@Override
- public void abortPulsing() {
- mDozeScrimController.abortPulsing();
- }
-
- @Override
public void extendPulse() {
mDozeScrimController.extendPulse();
}
@@ -5646,8 +4767,13 @@ public class StatusBar extends SystemUI implements DemoMode,
}
@Override
+ public void setAnimateScreenOff(boolean animateScreenOff) {
+ mAnimateScreenOff = animateScreenOff;
+ }
+
+ @Override
public void onDoubleTap(float screenX, float screenY) {
- if (screenX > 0 && screenY > 0 && mAmbientIndicationContainer != null
+ if (screenX > 0 && screenY > 0 && mAmbientIndicationContainer != null
&& mAmbientIndicationContainer.getVisibility() == View.VISIBLE) {
mAmbientIndicationContainer.getLocationOnScreen(mTmpInt2);
float viewX = screenX - mTmpInt2[0];
@@ -5666,7 +4792,7 @@ public class StatusBar extends SystemUI implements DemoMode,
@Override
public void setAodDimmingScrim(float scrimOpacity) {
- mDozeScrimController.setAodDimmingScrim(scrimOpacity);
+ ScrimState.AOD.setAodFrontScrimAlpha(scrimOpacity);
}
public void dispatchDoubleTap(float viewX, float viewY) {
@@ -5689,6 +4815,10 @@ public class StatusBar extends SystemUI implements DemoMode,
private boolean shouldAnimateWakeup() {
return mAnimateWakeup;
}
+
+ public boolean shouldAnimateScreenOff() {
+ return mAnimateScreenOff;
+ }
}
public boolean shouldIgnoreTouch() {
@@ -5701,65 +4831,36 @@ public class StatusBar extends SystemUI implements DemoMode,
protected IStatusBarService mBarService;
// all notifications
- protected NotificationData mNotificationData;
protected NotificationStackScrollLayout mStackScroller;
- protected NotificationGroupManager mGroupManager = new NotificationGroupManager();
+ protected NotificationGroupManager mGroupManager;
- protected RemoteInputController mRemoteInputController;
// for heads up notifications
- protected HeadsUpManager mHeadsUpManager;
+ protected HeadsUpManagerPhone mHeadsUpManager;
private AboveShelfObserver mAboveShelfObserver;
// handling reordering
- protected VisualStabilityManager mVisualStabilityManager = new VisualStabilityManager();
-
- protected int mCurrentUserId = 0;
- final protected SparseArray<UserInfo> mCurrentProfiles = new SparseArray<UserInfo>();
+ protected VisualStabilityManager mVisualStabilityManager;
- protected int mLayoutDirection = -1; // invalid
protected AccessibilityManager mAccessibilityManager;
protected boolean mDeviceInteractive;
protected boolean mVisible;
- protected ArraySet<Entry> mHeadsUpEntriesToRemoveOnSwitch = new ArraySet<>();
- protected ArraySet<Entry> mRemoteInputEntriesToRemoveOnCollapse = new ArraySet<>();
-
- /**
- * Notifications with keys in this set are not actually around anymore. We kept them around
- * when they were canceled in response to a remote input interaction. This allows us to show
- * what you replied and allows you to continue typing into it.
- */
- protected ArraySet<String> mKeysKeptForRemoteInput = new ArraySet<>();
// mScreenOnFromKeyguard && mVisible.
private boolean mVisibleToUser;
- private Locale mLocale;
-
- protected boolean mUseHeadsUp = false;
- protected boolean mHeadsUpTicker = false;
- protected boolean mDisableNotificationAlerts = false;
-
protected DevicePolicyManager mDevicePolicyManager;
protected PowerManager mPowerManager;
protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
- // public mode, private notifications, etc
- private final SparseBooleanArray mLockscreenPublicMode = new SparseBooleanArray();
- private final SparseBooleanArray mUsersAllowingPrivateNotifications = new SparseBooleanArray();
- private final SparseBooleanArray mUsersAllowingNotifications = new SparseBooleanArray();
-
- private UserManager mUserManager;
-
protected KeyguardManager mKeyguardManager;
private LockPatternUtils mLockPatternUtils;
private DeviceProvisionedController mDeviceProvisionedController
= Dependency.get(DeviceProvisionedController.class);
- protected SystemServicesProxy mSystemServicesProxy;
// UI-specific methods
@@ -5770,26 +4871,14 @@ public class StatusBar extends SystemUI implements DemoMode,
protected RecentsComponent mRecents;
- protected int mZenMode;
-
- // which notification is currently being longpress-examined by the user
- private NotificationGuts mNotificationGutsExposed;
- private MenuItem mGutsMenuItem;
-
- private KeyboardShortcuts mKeyboardShortcuts;
-
protected NotificationShelf mNotificationShelf;
protected DismissView mDismissView;
protected EmptyShadeView mEmptyShadeView;
- private NotificationClicker mNotificationClicker = new NotificationClicker();
-
protected AssistManager mAssistManager;
protected boolean mVrMode;
- private Set<String> mNonBlockablePkgs;
-
public boolean isDeviceInteractive() {
return mDeviceInteractive;
}
@@ -5810,287 +4899,15 @@ public class StatusBar extends SystemUI implements DemoMode,
return mVrMode;
}
- private final DeviceProvisionedListener mDeviceProvisionedListener =
- new DeviceProvisionedListener() {
- @Override
- public void onDeviceProvisionedChanged() {
- updateNotifications();
- }
- };
-
- protected final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- final int mode = Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF);
- setZenMode(mode);
-
- updateLockscreenNotificationSetting();
- }
- };
-
- private final ContentObserver mLockscreenSettingsObserver = new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- // We don't know which user changed LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS or
- // LOCK_SCREEN_SHOW_NOTIFICATIONS, so we just dump our cache ...
- mUsersAllowingPrivateNotifications.clear();
- mUsersAllowingNotifications.clear();
- // ... and refresh all the notifications
- updateLockscreenNotificationSetting();
- updateNotifications();
- }
- };
-
- private RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() {
-
- @Override
- public boolean onClickHandler(
- final View view, final PendingIntent pendingIntent, final Intent fillInIntent) {
- wakeUpIfDozing(SystemClock.uptimeMillis(), view);
-
-
- if (handleRemoteInput(view, pendingIntent, fillInIntent)) {
- return true;
- }
-
- if (DEBUG) {
- Log.v(TAG, "Notification click handler invoked for intent: " + pendingIntent);
- }
- logActionClick(view);
- // The intent we are sending is for the application, which
- // won't have permission to immediately start an activity after
- // the user switches to home. We know it is safe to do at this
- // point, so make sure new activity switches are now allowed.
- try {
- ActivityManager.getService().resumeAppSwitches();
- } catch (RemoteException e) {
- }
- final boolean isActivity = pendingIntent.isActivity();
- if (isActivity) {
- final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing();
- final boolean afterKeyguardGone = PreviewInflater.wouldLaunchResolverActivity(
- mContext, pendingIntent.getIntent(), mCurrentUserId);
- dismissKeyguardThenExecute(new OnDismissAction() {
- @Override
- public boolean onDismiss() {
- try {
- ActivityManager.getService().resumeAppSwitches();
- } catch (RemoteException e) {
- }
-
- boolean handled = superOnClickHandler(view, pendingIntent, fillInIntent);
-
- // close the shade if it was open
- if (handled && !mNotificationPanel.isFullyCollapsed()) {
- animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
- true /* force */);
- visibilityChanged(false);
- mAssistManager.hideAssist();
-
- // Wait for activity start.
- return true;
- } else {
- return false;
- }
-
- }
- }, afterKeyguardGone);
- return true;
- } else {
- return superOnClickHandler(view, pendingIntent, fillInIntent);
- }
- }
-
- private void logActionClick(View view) {
- ViewParent parent = view.getParent();
- String key = getNotificationKeyForParent(parent);
- if (key == null) {
- Log.w(TAG, "Couldn't determine notification for click.");
- return;
- }
- int index = -1;
- // If this is a default template, determine the index of the button.
- if (view.getId() == com.android.internal.R.id.action0 &&
- parent != null && parent instanceof ViewGroup) {
- ViewGroup actionGroup = (ViewGroup) parent;
- index = actionGroup.indexOfChild(view);
- }
- try {
- mBarService.onNotificationActionClick(key, index);
- } catch (RemoteException e) {
- // Ignore
- }
- }
-
- private String getNotificationKeyForParent(ViewParent parent) {
- while (parent != null) {
- if (parent instanceof ExpandableNotificationRow) {
- return ((ExpandableNotificationRow) parent).getStatusBarNotification().getKey();
- }
- parent = parent.getParent();
- }
- return null;
- }
-
- private boolean superOnClickHandler(View view, PendingIntent pendingIntent,
- Intent fillInIntent) {
- return super.onClickHandler(view, pendingIntent, fillInIntent,
- StackId.FULLSCREEN_WORKSPACE_STACK_ID);
- }
-
- private boolean handleRemoteInput(View view, PendingIntent pendingIntent, Intent fillInIntent) {
- Object tag = view.getTag(com.android.internal.R.id.remote_input_tag);
- RemoteInput[] inputs = null;
- if (tag instanceof RemoteInput[]) {
- inputs = (RemoteInput[]) tag;
- }
-
- if (inputs == null) {
- return false;
- }
-
- RemoteInput input = null;
-
- for (RemoteInput i : inputs) {
- if (i.getAllowFreeFormInput()) {
- input = i;
- }
- }
-
- if (input == null) {
- return false;
- }
-
- ViewParent p = view.getParent();
- RemoteInputView riv = null;
- while (p != null) {
- if (p instanceof View) {
- View pv = (View) p;
- if (pv.isRootNamespace()) {
- riv = findRemoteInputView(pv);
- break;
- }
- }
- p = p.getParent();
- }
- ExpandableNotificationRow row = null;
- while (p != null) {
- if (p instanceof ExpandableNotificationRow) {
- row = (ExpandableNotificationRow) p;
- break;
- }
- p = p.getParent();
- }
-
- if (row == null) {
- return false;
- }
-
- row.setUserExpanded(true);
-
- if (!mAllowLockscreenRemoteInput) {
- final int userId = pendingIntent.getCreatorUserHandle().getIdentifier();
- if (isLockscreenPublicMode(userId)) {
- onLockedRemoteInput(row, view);
- return true;
- }
- if (mUserManager.getUserInfo(userId).isManagedProfile()
- && mKeyguardManager.isDeviceLocked(userId)) {
- onLockedWorkRemoteInput(userId, row, view);
- return true;
- }
- }
-
- if (riv == null) {
- riv = findRemoteInputView(row.getPrivateLayout().getExpandedChild());
- if (riv == null) {
- return false;
- }
- if (!row.getPrivateLayout().getExpandedChild().isShown()) {
- onMakeExpandedVisibleForRemoteInput(row, view);
- return true;
- }
- }
-
- int width = view.getWidth();
- if (view instanceof TextView) {
- // Center the reveal on the text which might be off-center from the TextView
- TextView tv = (TextView) view;
- if (tv.getLayout() != null) {
- int innerWidth = (int) tv.getLayout().getLineWidth(0);
- innerWidth += tv.getCompoundPaddingLeft() + tv.getCompoundPaddingRight();
- width = Math.min(width, innerWidth);
- }
- }
- int cx = view.getLeft() + width / 2;
- int cy = view.getTop() + view.getHeight() / 2;
- int w = riv.getWidth();
- int h = riv.getHeight();
- int r = Math.max(
- Math.max(cx + cy, cx + (h - cy)),
- Math.max((w - cx) + cy, (w - cx) + (h - cy)));
-
- riv.setRevealParameters(cx, cy, r);
- riv.setPendingIntent(pendingIntent);
- riv.setRemoteInput(inputs, input);
- riv.focusAnimated();
-
- return true;
- }
-
- private RemoteInputView findRemoteInputView(View v) {
- if (v == null) {
- return null;
- }
- return (RemoteInputView) v.findViewWithTag(RemoteInputView.VIEW_TAG);
- }
- };
-
- private final BroadcastReceiver mBaseBroadcastReceiver = new BroadcastReceiver() {
+ private final BroadcastReceiver mBannerActionBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
- if (Intent.ACTION_USER_SWITCHED.equals(action)) {
- mCurrentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
- updateCurrentProfilesCache();
- if (true) Log.v(TAG, "userId " + mCurrentUserId + " is in the house");
-
- updateLockscreenNotificationSetting();
-
- userSwitched(mCurrentUserId);
- } else if (Intent.ACTION_USER_ADDED.equals(action)) {
- updateCurrentProfilesCache();
- } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
- List<ActivityManager.RecentTaskInfo> recentTask = null;
- try {
- recentTask = ActivityManager.getService().getRecentTasks(1,
- ActivityManager.RECENT_WITH_EXCLUDED
- | ActivityManager.RECENT_INCLUDE_PROFILES,
- mCurrentUserId).getList();
- } catch (RemoteException e) {
- // Abandon hope activity manager not running.
- }
- if (recentTask != null && recentTask.size() > 0) {
- UserInfo user = mUserManager.getUserInfo(recentTask.get(0).userId);
- if (user != null && user.isManagedProfile()) {
- Toast toast = Toast.makeText(mContext,
- R.string.managed_profile_foreground_toast,
- Toast.LENGTH_SHORT);
- TextView text = (TextView) toast.getView().findViewById(
- android.R.id.message);
- text.setCompoundDrawablesRelativeWithIntrinsicBounds(
- R.drawable.stat_sys_managed_profile_status, 0, 0, 0);
- int paddingPx = mContext.getResources().getDimensionPixelSize(
- R.dimen.managed_profile_toast_padding);
- text.setCompoundDrawablePadding(paddingPx);
- toast.show();
- }
- }
- } else if (BANNER_ACTION_CANCEL.equals(action) || BANNER_ACTION_SETUP.equals(action)) {
+ if (BANNER_ACTION_CANCEL.equals(action) || BANNER_ACTION_SETUP.equals(action)) {
NotificationManager noMan = (NotificationManager)
mContext.getSystemService(Context.NOTIFICATION_SERVICE);
- noMan.cancel(SystemMessage.NOTE_HIDDEN_NOTIFICATIONS);
+ noMan.cancel(com.android.internal.messages.nano.SystemMessageProto.SystemMessage.
+ NOTE_HIDDEN_NOTIFICATIONS);
Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0);
@@ -6102,148 +4919,181 @@ public class StatusBar extends SystemUI implements DemoMode,
);
}
- } else if (NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION.equals(action)) {
- final IntentSender intentSender = intent.getParcelableExtra(Intent.EXTRA_INTENT);
- final String notificationKey = intent.getStringExtra(Intent.EXTRA_INDEX);
- if (intentSender != null) {
- try {
- mContext.startIntentSender(intentSender, null, 0, 0, 0);
- } catch (IntentSender.SendIntentException e) {
- /* ignore */
- }
- }
- if (notificationKey != null) {
- try {
- mBarService.onNotificationClick(notificationKey);
- } catch (RemoteException e) {
- /* ignore */
- }
- }
}
}
};
- private final BroadcastReceiver mAllUsersReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
-
- if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(action) &&
- isCurrentProfile(getSendingUserId())) {
- mUsersAllowingPrivateNotifications.clear();
- updateLockscreenNotificationSetting();
- updateNotifications();
- } else if (Intent.ACTION_DEVICE_LOCKED_CHANGED.equals(action)) {
- if (userId != mCurrentUserId && isCurrentProfile(userId)) {
- onWorkChallengeChanged();
- }
- }
- }
- };
+ @Override
+ public void onNotificationClicked(StatusBarNotification sbn, ExpandableNotificationRow row) {
+ Notification notification = sbn.getNotification();
+ final PendingIntent intent = notification.contentIntent != null
+ ? notification.contentIntent
+ : notification.fullScreenIntent;
+ final String notificationKey = sbn.getKey();
- private final NotificationListenerWithPlugins mNotificationListener =
- new NotificationListenerWithPlugins() {
- @Override
- public void onListenerConnected() {
- if (DEBUG) Log.d(TAG, "onListenerConnected");
- onPluginConnected();
- final StatusBarNotification[] notifications = getActiveNotifications();
- if (notifications == null) {
- Log.w(TAG, "onListenerConnected unable to get active notifications.");
- return;
+ final boolean afterKeyguardGone = intent.isActivity()
+ && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
+ mLockscreenUserManager.getCurrentUserId());
+ dismissKeyguardThenExecute(() -> {
+ // TODO: Some of this code may be able to move to NotificationEntryManager.
+ if (mHeadsUpManager != null && mHeadsUpManager.isHeadsUp(notificationKey)) {
+ // Release the HUN notification to the shade.
+
+ if (isPresenterFullyCollapsed()) {
+ HeadsUpUtil.setIsClickedHeadsUpNotification(row, true);
+ }
+ //
+ // In most cases, when FLAG_AUTO_CANCEL is set, the notification will
+ // become canceled shortly by NoMan, but we can't assume that.
+ mHeadsUpManager.releaseImmediately(notificationKey);
+ }
+ StatusBarNotification parentToCancel = null;
+ if (shouldAutoCancel(sbn) && mGroupManager.isOnlyChildInGroup(sbn)) {
+ StatusBarNotification summarySbn =
+ mGroupManager.getLogicalGroupSummary(sbn).getStatusBarNotification();
+ if (shouldAutoCancel(summarySbn)) {
+ parentToCancel = summarySbn;
+ }
}
- final RankingMap currentRanking = getCurrentRanking();
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- for (StatusBarNotification sbn : notifications) {
- try {
- addNotification(sbn, currentRanking);
- } catch (InflationException e) {
- handleInflationException(sbn, e);
+ final StatusBarNotification parentToCancelFinal = parentToCancel;
+ final Runnable runnable = () -> {
+ try {
+ // The intent we are sending is for the application, which
+ // won't have permission to immediately start an activity after
+ // the user switches to home. We know it is safe to do at this
+ // point, so make sure new activity switches are now allowed.
+ ActivityManager.getService().resumeAppSwitches();
+ } catch (RemoteException e) {
+ }
+ int launchResult = ActivityManager.START_CANCELED;
+ if (intent != null) {
+ // If we are launching a work activity and require to launch
+ // separate work challenge, we defer the activity action and cancel
+ // notification until work challenge is unlocked.
+ if (intent.isActivity()) {
+ final int userId = intent.getCreatorUserHandle().getIdentifier();
+ if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)
+ && mKeyguardManager.isDeviceLocked(userId)) {
+ // TODO(b/28935539): should allow certain activities to
+ // bypass work challenge
+ if (startWorkChallengeIfNecessary(userId, intent.getIntentSender(),
+ notificationKey)) {
+ // Show work challenge, do not run PendingIntent and
+ // remove notification
+ collapsePanel();
+ return;
+ }
}
}
+ Intent fillInIntent = null;
+ Entry entry = row.getEntry();
+ CharSequence remoteInputText = null;
+ RemoteInputController controller = mRemoteInputManager.getController();
+ if (controller.isRemoteInputActive(entry)) {
+ remoteInputText = row.getActiveRemoteInputText();
+ }
+ if (TextUtils.isEmpty(remoteInputText)
+ && !TextUtils.isEmpty(entry.remoteInputText)) {
+ remoteInputText = entry.remoteInputText;
+ }
+ if (!TextUtils.isEmpty(remoteInputText)
+ && !controller.isSpinning(entry.key)) {
+ fillInIntent = new Intent().putExtra(Notification.EXTRA_REMOTE_INPUT_DRAFT,
+ remoteInputText.toString());
+ }
+ try {
+ launchResult = intent.sendAndReturnResult(mContext, 0, fillInIntent, null,
+ null, null, getActivityOptions(row));
+ mActivityLaunchAnimator.setLaunchResult(launchResult);
+ } catch (PendingIntent.CanceledException e) {
+ // the stack trace isn't very helpful here.
+ // Just log the exception message.
+ Log.w(TAG, "Sending contentIntent failed: " + e);
+
+ // TODO: Dismiss Keyguard.
+ }
+ if (intent.isActivity()) {
+ mAssistManager.hideAssist();
+ }
+ }
+ if (shouldCollapse()) {
+ if (Looper.getMainLooper().isCurrentThread()) {
+ collapsePanel();
+ } else {
+ mStackScroller.post(this::collapsePanel);
+ }
}
- });
- }
- @Override
- public void onNotificationPosted(final StatusBarNotification sbn,
- final RankingMap rankingMap) {
- if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn);
- if (sbn != null && !onPluginNotificationPosted(sbn, rankingMap)) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- processForRemoteInput(sbn.getNotification());
- String key = sbn.getKey();
- mKeysKeptForRemoteInput.remove(key);
- boolean isUpdate = mNotificationData.get(key) != null;
- // In case we don't allow child notifications, we ignore children of
- // notifications that have a summary, since we're not going to show them
- // anyway. This is true also when the summary is canceled,
- // because children are automatically canceled by NoMan in that case.
- if (!ENABLE_CHILD_NOTIFICATIONS
- && mGroupManager.isChildInGroupWithSummary(sbn)) {
- if (DEBUG) {
- Log.d(TAG, "Ignoring group child due to existing summary: " + sbn);
- }
+ try {
+ mBarService.onNotificationClick(notificationKey);
+ } catch (RemoteException ex) {
+ // system process is dead if we're here.
+ }
+ if (parentToCancelFinal != null) {
+ removeNotification(parentToCancelFinal);
+ }
+ if (shouldAutoCancel(sbn)) {
+ // Automatically remove all notifications that we may have kept around longer
+ removeNotification(sbn);
+ }
+ };
- // Remove existing notification to avoid stale data.
- if (isUpdate) {
- removeNotification(key, rankingMap);
- } else {
- mNotificationData.updateRanking(rankingMap);
- }
- return;
- }
- try {
- if (isUpdate) {
- updateNotification(sbn, rankingMap);
- } else {
- addNotification(sbn, rankingMap);
- }
- } catch (InflationException e) {
- handleInflationException(sbn, e);
- }
- }
- });
+ if (mStatusBarKeyguardViewManager.isShowing()
+ && mStatusBarKeyguardViewManager.isOccluded()) {
+ mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable);
+ } else {
+ new Thread(runnable).start();
}
- }
- @Override
- public void onNotificationRemoved(StatusBarNotification sbn,
- final RankingMap rankingMap) {
- if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn);
- if (sbn != null && !onPluginNotificationRemoved(sbn, rankingMap)) {
- final String key = sbn.getKey();
- mHandler.post(() -> removeNotification(key, rankingMap));
- }
- }
+ return !mNotificationPanel.isFullyCollapsed();
+ }, afterKeyguardGone);
+ }
- @Override
- public void onNotificationRankingUpdate(final RankingMap rankingMap) {
- if (DEBUG) Log.d(TAG, "onRankingUpdate");
- if (rankingMap != null) {
- RankingMap r = onPluginRankingUpdate(rankingMap);
- mHandler.post(() -> updateNotificationRanking(r));
- }
+ private boolean shouldCollapse() {
+ return mState != StatusBarState.SHADE || !mActivityLaunchAnimator.isAnimationPending();
+ }
+
+ public void collapsePanel(boolean animate) {
+ if (animate) {
+ collapsePanel();
+ } else if (!isPresenterFullyCollapsed()) {
+ instantCollapseNotificationPanel();
+ visibilityChanged(false);
+ } else {
+ runPostCollapseRunnables();
}
+ }
- };
+ private boolean collapsePanel() {
+ if (!mNotificationPanel.isFullyCollapsed()) {
+ // close the shade if it was open
+ animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */,
+ true /* delayed */);
+ visibilityChanged(false);
- private void updateCurrentProfilesCache() {
- synchronized (mCurrentProfiles) {
- mCurrentProfiles.clear();
- if (mUserManager != null) {
- for (UserInfo user : mUserManager.getProfiles(mCurrentUserId)) {
- mCurrentProfiles.put(user.id, user);
- }
- }
+ return true;
+ } else {
+ return false;
}
}
+ private void removeNotification(StatusBarNotification notification) {
+ // We have to post it to the UI thread for synchronization
+ mHandler.post(() -> {
+ Runnable removeRunnable =
+ () -> mEntryManager.performRemoveNotification(notification);
+ if (isCollapsing()) {
+ // To avoid lags we're only performing the remove
+ // after the shade was collapsed
+ addPostCollapseAction(removeRunnable);
+ } else {
+ removeRunnable.run();
+ }
+ });
+ }
+
+ protected NotificationListener mNotificationListener;
+
protected void notifyUserAboutHiddenNotifications() {
if (0 != Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 1)) {
@@ -6254,7 +5104,7 @@ public class StatusBar extends SystemUI implements DemoMode,
Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0);
return;
}
- Log.d(TAG, "disabling lockecreen notifications and alerting the user");
+ Log.d(TAG, "disabling lockscreen notifications and alerting the user");
// disable lockscreen notifications until user acts on the banner.
Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0);
@@ -6295,31 +5145,12 @@ public class StatusBar extends SystemUI implements DemoMode,
@Override // NotificationData.Environment
public boolean isNotificationForCurrentProfiles(StatusBarNotification n) {
- final int thisUserId = mCurrentUserId;
final int notificationUserId = n.getUserId();
if (DEBUG && MULTIUSER_DEBUG) {
- Log.v(TAG, String.format("%s: current userid: %d, notification userid: %d",
- n, thisUserId, notificationUserId));
- }
- return isCurrentProfile(notificationUserId);
- }
-
- protected void setNotificationShown(StatusBarNotification n) {
- setNotificationsShown(new String[]{n.getKey()});
- }
-
- protected void setNotificationsShown(String[] keys) {
- try {
- mNotificationListener.setNotificationsShown(keys);
- } catch (RuntimeException e) {
- Log.d(TAG, "failed setNotificationsShown: ", e);
- }
- }
-
- protected boolean isCurrentProfile(int userId) {
- synchronized (mCurrentProfiles) {
- return userId == UserHandle.USER_ALL || mCurrentProfiles.get(userId) != null;
+ Log.v(TAG, String.format("%s: current userid: %d, notification userid: %d", n,
+ mLockscreenUserManager.getCurrentUserId(), notificationUserId));
}
+ return mLockscreenUserManager.isCurrentProfile(notificationUserId);
}
@Override
@@ -6327,41 +5158,23 @@ public class StatusBar extends SystemUI implements DemoMode,
return mGroupManager;
}
- public boolean isMediaNotification(NotificationData.Entry entry) {
- // TODO: confirm that there's a valid media key
- return entry.getExpandedContentView() != null &&
- entry.getExpandedContentView()
- .findViewById(com.android.internal.R.id.media_actions) != null;
- }
-
- // The button in the guts that links to the system notification settings for that app
- private void startAppNotificationSettingsActivity(String packageName, final int appUid,
- final NotificationChannel channel) {
- final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
- intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
- intent.putExtra(Settings.EXTRA_APP_UID, appUid);
- if (channel != null) {
- intent.putExtra(EXTRA_FRAGMENT_ARG_KEY, channel.getId());
- }
- startNotificationGutsIntent(intent, appUid);
- }
-
- private void startNotificationGutsIntent(final Intent intent, final int appUid) {
- dismissKeyguardThenExecute(new OnDismissAction() {
- @Override
- public boolean onDismiss() {
- AsyncTask.execute(new Runnable() {
- @Override
- public void run() {
- TaskStackBuilder.create(mContext)
- .addNextIntentWithParentStack(intent)
- .startActivities(getActivityOptions(),
- new UserHandle(UserHandle.getUserId(appUid)));
- }
- });
- animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
- return true;
- }
+ @Override
+ public void startNotificationGutsIntent(final Intent intent, final int appUid,
+ ExpandableNotificationRow row) {
+ dismissKeyguardThenExecute(() -> {
+ AsyncTask.execute(() -> {
+ int launchResult = TaskStackBuilder.create(mContext)
+ .addNextIntentWithParentStack(intent)
+ .startActivities(getActivityOptions(row),
+ new UserHandle(UserHandle.getUserId(appUid)));
+ mActivityLaunchAnimator.setLaunchResult(launchResult);
+ if (shouldCollapse()) {
+ // Putting it back on the main thread, since we're touching views
+ mStatusBarWindow.post(() -> animateCollapsePanels(
+ CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */));
+ }
+ });
+ return true;
}, false /* afterKeyguardGone */);
}
@@ -6375,237 +5188,15 @@ public class StatusBar extends SystemUI implements DemoMode,
}
}
- private void bindGuts(final ExpandableNotificationRow row, MenuItem item) {
- row.inflateGuts();
- row.setGutsView(item);
- final StatusBarNotification sbn = row.getStatusBarNotification();
- row.setTag(sbn.getPackageName());
- final NotificationGuts guts = row.getGuts();
- guts.setClosedListener((NotificationGuts g) -> {
- if (!g.willBeRemoved() && !row.isRemoved()) {
- mStackScroller.onHeightChanged(row, !isPanelFullyCollapsed() /* needsAnimation */);
- }
- if (mNotificationGutsExposed == g) {
- mNotificationGutsExposed = null;
- mGutsMenuItem = null;
- }
- String key = sbn.getKey();
- if (key.equals(mKeyToRemoveOnGutsClosed)) {
- mKeyToRemoveOnGutsClosed = null;
- removeNotification(key, mLatestRankingMap);
- }
- });
-
- View gutsView = item.getGutsView();
- if (gutsView instanceof NotificationSnooze) {
- NotificationSnooze snoozeGuts = (NotificationSnooze) gutsView;
- snoozeGuts.setSnoozeListener(mStackScroller.getSwipeActionHelper());
- snoozeGuts.setStatusBarNotification(sbn);
- snoozeGuts.setSnoozeOptions(row.getEntry().snoozeCriteria);
- guts.setHeightChangedListener((NotificationGuts g) -> {
- mStackScroller.onHeightChanged(row, row.isShown() /* needsAnimation */);
- });
- }
-
- if (gutsView instanceof NotificationInfo) {
- final UserHandle userHandle = sbn.getUser();
- PackageManager pmUser = getPackageManagerForUser(mContext,
- userHandle.getIdentifier());
- final INotificationManager iNotificationManager = INotificationManager.Stub.asInterface(
- ServiceManager.getService(Context.NOTIFICATION_SERVICE));
- final String pkg = sbn.getPackageName();
- NotificationInfo info = (NotificationInfo) gutsView;
- // Settings link is only valid for notifications that specify a user, unless this is the
- // system user.
- NotificationInfo.OnSettingsClickListener onSettingsClick = null;
- if (!userHandle.equals(UserHandle.ALL) || mCurrentUserId == UserHandle.USER_SYSTEM) {
- onSettingsClick = (View v, NotificationChannel channel, int appUid) -> {
- mMetricsLogger.action(MetricsEvent.ACTION_NOTE_INFO);
- guts.resetFalsingCheck();
- startAppNotificationSettingsActivity(pkg, appUid, channel);
- };
- }
- final NotificationInfo.OnAppSettingsClickListener onAppSettingsClick = (View v,
- Intent intent) -> {
- mMetricsLogger.action(MetricsEvent.ACTION_APP_NOTE_SETTINGS);
- guts.resetFalsingCheck();
- startNotificationGutsIntent(intent, sbn.getUid());
- };
- final View.OnClickListener onDoneClick = (View v) -> {
- saveAndCloseNotificationMenu(info, row, guts, v);
- };
- final NotificationInfo.CheckSaveListener checkSaveListener =
- (Runnable saveImportance) -> {
- // If the user has security enabled, show challenge if the setting is changed.
- if (isLockscreenPublicMode(userHandle.getIdentifier())
- && (mState == StatusBarState.KEYGUARD
- || mState == StatusBarState.SHADE_LOCKED)) {
- onLockedNotificationImportanceChange(() -> {
- saveImportance.run();
- return true;
- });
- } else {
- saveImportance.run();
- }
- };
-
- ArraySet<NotificationChannel> channels = new ArraySet<NotificationChannel>();
- channels.add(row.getEntry().channel);
- if (row.isSummaryWithChildren()) {
- // If this is a summary, then add in the children notification channels for the
- // same user and pkg.
- final List<ExpandableNotificationRow> childrenRows = row.getNotificationChildren();
- final int numChildren = childrenRows.size();
- for (int i = 0; i < numChildren; i++) {
- final ExpandableNotificationRow childRow = childrenRows.get(i);
- final NotificationChannel childChannel = childRow.getEntry().channel;
- final StatusBarNotification childSbn = childRow.getStatusBarNotification();
- if (childSbn.getUser().equals(userHandle) &&
- childSbn.getPackageName().equals(pkg)) {
- channels.add(childChannel);
- }
- }
- }
- try {
- info.bindNotification(pmUser, iNotificationManager, pkg, new ArrayList(channels),
- row.getEntry().channel.getImportance(), sbn, onSettingsClick,
- onAppSettingsClick, onDoneClick, checkSaveListener,
- mNonBlockablePkgs);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
- }
- }
- }
-
- private void saveAndCloseNotificationMenu(NotificationInfo info,
- ExpandableNotificationRow row, NotificationGuts guts, View done) {
- guts.resetFalsingCheck();
- int[] rowLocation = new int[2];
- int[] doneLocation = new int[2];
- row.getLocationOnScreen(rowLocation);
- done.getLocationOnScreen(doneLocation);
-
- final int centerX = done.getWidth() / 2;
- final int centerY = done.getHeight() / 2;
- final int x = doneLocation[0] - rowLocation[0] + centerX;
- final int y = doneLocation[1] - rowLocation[1] + centerY;
- closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
- true /* removeControls */, x, y, true /* resetMenu */);
- }
-
- protected SwipeHelper.LongPressListener getNotificationLongClicker() {
- return new SwipeHelper.LongPressListener() {
- @Override
- public boolean onLongPress(View v, final int x, final int y,
- MenuItem item) {
- if (!(v instanceof ExpandableNotificationRow)) {
- return false;
- }
- if (v.getWindowToken() == null) {
- Log.e(TAG, "Trying to show notification guts, but not attached to window");
- return false;
- }
-
- final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
- if (row.isDark()) {
- return false;
- }
- v.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
- if (row.areGutsExposed()) {
- closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
- true /* removeControls */, -1 /* x */, -1 /* y */,
- true /* resetMenu */);
- return false;
- }
- bindGuts(row, item);
- NotificationGuts guts = row.getGuts();
-
- // Assume we are a status_bar_notification_row
- if (guts == null) {
- // This view has no guts. Examples are the more card or the dismiss all view
- return false;
- }
-
- mMetricsLogger.action(MetricsEvent.ACTION_NOTE_CONTROLS);
-
- // ensure that it's laid but not visible until actually laid out
- guts.setVisibility(View.INVISIBLE);
- // Post to ensure the the guts are properly laid out.
- guts.post(new Runnable() {
- @Override
- public void run() {
- if (row.getWindowToken() == null) {
- Log.e(TAG, "Trying to show notification guts, but not attached to "
- + "window");
- return;
- }
- closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
- true /* removeControls */, -1 /* x */, -1 /* y */,
- false /* resetMenu */);
- guts.setVisibility(View.VISIBLE);
- final double horz = Math.max(guts.getWidth() - x, x);
- final double vert = Math.max(guts.getHeight() - y, y);
- final float r = (float) Math.hypot(horz, vert);
- final Animator a
- = ViewAnimationUtils.createCircularReveal(guts, x, y, 0, r);
- a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
- a.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
- a.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- // Move the notification view back over the menu
- row.resetTranslation();
- }
- });
- a.start();
- final boolean needsFalsingProtection =
- (mState == StatusBarState.KEYGUARD &&
- !mAccessibilityManager.isTouchExplorationEnabled());
- guts.setExposed(true /* exposed */, needsFalsingProtection);
- row.closeRemoteInput();
- mStackScroller.onHeightChanged(row, true /* needsAnimation */);
- mNotificationGutsExposed = guts;
- mGutsMenuItem = item;
- }
- });
- return true;
- }
- };
- }
-
- /**
- * Returns the exposed NotificationGuts or null if none are exposed.
- */
- public NotificationGuts getExposedGuts() {
- return mNotificationGutsExposed;
- }
-
- /**
- * Closes guts or notification menus that might be visible and saves any changes.
- *
- * @param removeLeavebehinds true if leavebehinds (e.g. snooze) should be closed.
- * @param force true if guts should be closed regardless of state (used for snooze only).
- * @param removeControls true if controls (e.g. info) should be closed.
- * @param x if closed based on touch location, this is the x touch location.
- * @param y if closed based on touch location, this is the y touch location.
- * @param resetMenu if any notification menus that might be revealed should be closed.
- */
- public void closeAndSaveGuts(boolean removeLeavebehinds, boolean force, boolean removeControls,
- int x, int y, boolean resetMenu) {
- if (mNotificationGutsExposed != null) {
- mNotificationGutsExposed.closeControls(removeLeavebehinds, removeControls, x, y, force);
- }
- if (resetMenu) {
- mStackScroller.resetExposedMenuView(false /* animate */, true /* force */);
- }
- }
-
@Override
public void toggleSplitScreen() {
toggleSplitScreenMode(-1 /* metricsDockAction */, -1 /* metricsUndockAction */);
}
+ void awakenDreams() {
+ SystemServicesProxy.getInstance(mContext).awakenDreamsAsync();
+ }
+
@Override
public void preloadRecentApps() {
int msg = MSG_PRELOAD_RECENT_APPS;
@@ -6646,13 +5237,6 @@ public class StatusBar extends SystemUI implements DemoMode,
updateHideIconsForBouncer(true /* animate */);
}
- protected void sendCloseSystemWindows(String reason) {
- try {
- ActivityManager.getService().closeSystemDialogs(reason);
- } catch (RemoteException e) {
- }
- }
-
protected void toggleKeyboardShortcuts(int deviceId) {
KeyboardShortcuts.toggle(mContext, deviceId);
}
@@ -6661,89 +5245,14 @@ public class StatusBar extends SystemUI implements DemoMode,
KeyboardShortcuts.dismiss();
}
- /**
- * Save the current "public" (locked and secure) state of the lockscreen.
- */
- public void setLockscreenPublicMode(boolean publicMode, int userId) {
- mLockscreenPublicMode.put(userId, publicMode);
- }
-
- public boolean isLockscreenPublicMode(int userId) {
- if (userId == UserHandle.USER_ALL) {
- return mLockscreenPublicMode.get(mCurrentUserId, false);
- }
- return mLockscreenPublicMode.get(userId, false);
- }
-
- /**
- * Has the given user chosen to allow notifications to be shown even when the lockscreen is in
- * "public" (secure & locked) mode?
- */
- public boolean userAllowsNotificationsInPublic(int userHandle) {
- if (userHandle == UserHandle.USER_ALL) {
- return true;
- }
-
- if (mUsersAllowingNotifications.indexOfKey(userHandle) < 0) {
- final boolean allowed = 0 != Settings.Secure.getIntForUser(
- mContext.getContentResolver(),
- Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, userHandle);
- mUsersAllowingNotifications.append(userHandle, allowed);
- return allowed;
- }
-
- return mUsersAllowingNotifications.get(userHandle);
- }
-
- /**
- * Has the given user chosen to allow their private (full) notifications to be shown even
- * when the lockscreen is in "public" (secure & locked) mode?
- */
- public boolean userAllowsPrivateNotificationsInPublic(int userHandle) {
- if (userHandle == UserHandle.USER_ALL) {
- return true;
- }
-
- if (mUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
- final boolean allowedByUser = 0 != Settings.Secure.getIntForUser(
- mContext.getContentResolver(),
- Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, userHandle);
- final boolean allowedByDpm = adminAllowsUnredactedNotifications(userHandle);
- final boolean allowed = allowedByUser && allowedByDpm;
- mUsersAllowingPrivateNotifications.append(userHandle, allowed);
- return allowed;
- }
-
- return mUsersAllowingPrivateNotifications.get(userHandle);
- }
-
- private boolean adminAllowsUnredactedNotifications(int userHandle) {
- if (userHandle == UserHandle.USER_ALL) {
- return true;
- }
- final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures(null /* admin */,
- userHandle);
- return (dpmFlags & DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS) == 0;
- }
-
- /**
- * Returns true if we're on a secure lockscreen and the user wants to hide notification data.
- * If so, notifications should be hidden.
- */
@Override // NotificationData.Environment
public boolean shouldHideNotifications(int userId) {
- return isLockscreenPublicMode(userId) && !userAllowsNotificationsInPublic(userId)
- || (userId != mCurrentUserId && shouldHideNotifications(mCurrentUserId));
+ return mLockscreenUserManager.shouldHideNotifications(userId);
}
- /**
- * Returns true if we're on a secure lockscreen and the user wants to hide notifications via
- * package-specific override.
- */
@Override // NotificationDate.Environment
public boolean shouldHideNotifications(String key) {
- return isLockscreenPublicMode(mCurrentUserId)
- && mNotificationData.getVisibilityOverride(key) == Notification.VISIBILITY_SECRET;
+ return mLockscreenUserManager.shouldHideNotifications(key);
}
/**
@@ -6751,19 +5260,7 @@ public class StatusBar extends SystemUI implements DemoMode,
*/
@Override // NotificationData.Environment
public boolean isSecurelyLocked(int userId) {
- return isLockscreenPublicMode(userId);
- }
-
- public void onNotificationClear(StatusBarNotification notification) {
- try {
- mBarService.onNotificationClear(
- notification.getPackageName(),
- notification.getTag(),
- notification.getId(),
- notification.getUserId());
- } catch (android.os.RemoteException ex) {
- // oh well
- }
+ return mLockscreenUserManager.isLockscreenPublicMode(userId);
}
/**
@@ -6777,145 +5274,10 @@ public class StatusBar extends SystemUI implements DemoMode,
if (mState == StatusBarState.KEYGUARD) {
// Since the number of notifications is determined based on the height of the view, we
// need to update them.
- int maxBefore = getMaxKeyguardNotifications(false /* recompute */);
- int maxNotifications = getMaxKeyguardNotifications(true /* recompute */);
+ int maxBefore = getMaxNotificationsWhileLocked(false /* recompute */);
+ int maxNotifications = getMaxNotificationsWhileLocked(true /* recompute */);
if (maxBefore != maxNotifications) {
- updateRowStates();
- }
- }
- }
-
- protected void inflateViews(Entry entry, ViewGroup parent) {
- PackageManager pmUser = getPackageManagerForUser(mContext,
- entry.notification.getUser().getIdentifier());
-
- final StatusBarNotification sbn = entry.notification;
- if (entry.row != null) {
- entry.reset();
- updateNotification(entry, pmUser, sbn, entry.row);
- } else {
- new RowInflaterTask().inflate(mContext, parent, entry,
- row -> {
- bindRow(entry, pmUser, sbn, row);
- updateNotification(entry, pmUser, sbn, row);
- });
- }
-
- }
-
- private void bindRow(Entry entry, PackageManager pmUser,
- StatusBarNotification sbn, ExpandableNotificationRow row) {
- row.setExpansionLogger(this, entry.notification.getKey());
- row.setGroupManager(mGroupManager);
- row.setHeadsUpManager(mHeadsUpManager);
- row.setAboveShelfChangedListener(mAboveShelfObserver);
- row.setRemoteInputController(mRemoteInputController);
- row.setOnExpandClickListener(this);
- row.setRemoteViewClickHandler(mOnClickHandler);
- row.setInflationCallback(this);
- row.setSecureStateProvider(this::isKeyguardCurrentlySecure);
-
- // Get the app name.
- // Note that Notification.Builder#bindHeaderAppName has similar logic
- // but since this field is used in the guts, it must be accurate.
- // Therefore we will only show the application label, or, failing that, the
- // package name. No substitutions.
- final String pkg = sbn.getPackageName();
- String appname = pkg;
- try {
- final ApplicationInfo info = pmUser.getApplicationInfo(pkg,
- PackageManager.MATCH_UNINSTALLED_PACKAGES
- | PackageManager.MATCH_DISABLED_COMPONENTS);
- if (info != null) {
- appname = String.valueOf(pmUser.getApplicationLabel(info));
- }
- } catch (NameNotFoundException e) {
- // Do nothing
- }
- row.setAppName(appname);
- row.setOnDismissRunnable(() ->
- performRemoveNotification(row.getStatusBarNotification()));
- row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
- if (ENABLE_REMOTE_INPUT) {
- row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
- }
- }
-
- private void updateNotification(Entry entry, PackageManager pmUser,
- StatusBarNotification sbn, ExpandableNotificationRow row) {
- row.setNeedsRedaction(needsRedaction(entry));
- boolean isLowPriority = mNotificationData.isAmbient(sbn.getKey());
- boolean isUpdate = mNotificationData.get(entry.key) != null;
- boolean wasLowPriority = row.isLowPriority();
- row.setIsLowPriority(isLowPriority);
- row.setLowPriorityStateUpdated(isUpdate && (wasLowPriority != isLowPriority));
- // bind the click event to the content area
- mNotificationClicker.register(row, sbn);
-
- // Extract target SDK version.
- try {
- ApplicationInfo info = pmUser.getApplicationInfo(sbn.getPackageName(), 0);
- entry.targetSdk = info.targetSdkVersion;
- } catch (NameNotFoundException ex) {
- Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex);
- }
- row.setLegacy(entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD
- && entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
- entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
- entry.autoRedacted = entry.notification.getNotification().publicVersion == null;
-
- entry.row = row;
- entry.row.setOnActivatedListener(this);
-
- boolean useIncreasedCollapsedHeight = mMessagingUtil.isImportantMessaging(sbn,
- mNotificationData.getImportance(sbn.getKey()));
- boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight && mPanelExpanded;
- row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
- row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp);
- row.updateNotification(entry);
- }
-
- /**
- * Adds RemoteInput actions from the WearableExtender; to be removed once more apps support this
- * via first-class API.
- *
- * TODO: Remove once enough apps specify remote inputs on their own.
- */
- private void processForRemoteInput(Notification n) {
- if (!ENABLE_REMOTE_INPUT) return;
-
- if (n.extras != null && n.extras.containsKey("android.wearable.EXTENSIONS") &&
- (n.actions == null || n.actions.length == 0)) {
- Notification.Action viableAction = null;
- Notification.WearableExtender we = new Notification.WearableExtender(n);
-
- List<Notification.Action> actions = we.getActions();
- final int numActions = actions.size();
-
- for (int i = 0; i < numActions; i++) {
- Notification.Action action = actions.get(i);
- if (action == null) {
- continue;
- }
- RemoteInput[] remoteInputs = action.getRemoteInputs();
- if (remoteInputs == null) {
- continue;
- }
- for (RemoteInput ri : remoteInputs) {
- if (ri.getAllowFreeFormInput()) {
- viableAction = action;
- break;
- }
- }
- if (viableAction != null) {
- break;
- }
- }
-
- if (viableAction != null) {
- Notification.Builder rebuilder = Notification.Builder.recoverBuilder(mContext, n);
- rebuilder.setActions(viableAction);
- rebuilder.build(); // will rewrite n
+ mViewHierarchyManager.updateRowStates();
}
}
}
@@ -6923,240 +5285,59 @@ public class StatusBar extends SystemUI implements DemoMode,
public void startPendingIntentDismissingKeyguard(final PendingIntent intent) {
if (!isDeviceProvisioned()) return;
- final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing();
final boolean afterKeyguardGone = intent.isActivity()
&& PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
- mCurrentUserId);
- dismissKeyguardThenExecute(new OnDismissAction() {
- @Override
- public boolean onDismiss() {
- new Thread() {
- @Override
- public void run() {
- try {
- // The intent we are sending is for the application, which
- // won't have permission to immediately start an activity after
- // the user switches to home. We know it is safe to do at this
- // point, so make sure new activity switches are now allowed.
- ActivityManager.getService().resumeAppSwitches();
- } catch (RemoteException e) {
- }
- try {
- intent.send(null, 0, null, null, null, null, getActivityOptions());
- } catch (PendingIntent.CanceledException e) {
- // the stack trace isn't very helpful here.
- // Just log the exception message.
- Log.w(TAG, "Sending intent failed: " + e);
-
- // TODO: Dismiss Keyguard.
- }
- if (intent.isActivity()) {
- mAssistManager.hideAssist();
- }
- }
- }.start();
-
- if (!mNotificationPanel.isFullyCollapsed()) {
- // close the shade if it was open
- animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
- true /* force */, true /* delayed */);
- visibilityChanged(false);
-
- return true;
- } else {
- return false;
+ mLockscreenUserManager.getCurrentUserId());
+ dismissKeyguardThenExecute(() -> {
+ new Thread(() -> {
+ try {
+ // The intent we are sending is for the application, which
+ // won't have permission to immediately start an activity after
+ // the user switches to home. We know it is safe to do at this
+ // point, so make sure new activity switches are now allowed.
+ ActivityManager.getService().resumeAppSwitches();
+ } catch (RemoteException e) {
}
- }
- }, afterKeyguardGone);
- }
-
-
- private final class NotificationClicker implements View.OnClickListener {
-
- @Override
- public void onClick(final View v) {
- if (!(v instanceof ExpandableNotificationRow)) {
- Log.e(TAG, "NotificationClicker called on a view that is not a notification row.");
- return;
- }
-
- wakeUpIfDozing(SystemClock.uptimeMillis(), v);
-
- final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
- final StatusBarNotification sbn = row.getStatusBarNotification();
- if (sbn == null) {
- Log.e(TAG, "NotificationClicker called on an unclickable notification,");
- return;
- }
-
- // Check if the notification is displaying the menu, if so slide notification back
- if (row.getProvider() != null && row.getProvider().isMenuVisible()) {
- row.animateTranslateNotification(0);
- return;
- }
-
- Notification notification = sbn.getNotification();
- final PendingIntent intent = notification.contentIntent != null
- ? notification.contentIntent
- : notification.fullScreenIntent;
- final String notificationKey = sbn.getKey();
+ try {
+ intent.send(null, 0, null, null, null, null, getActivityOptions(
+ null /* sourceNotification */));
+ } catch (PendingIntent.CanceledException e) {
+ // the stack trace isn't very helpful here.
+ // Just log the exception message.
+ Log.w(TAG, "Sending intent failed: " + e);
- // Mark notification for one frame.
- row.setJustClicked(true);
- DejankUtils.postAfterTraversal(new Runnable() {
- @Override
- public void run() {
- row.setJustClicked(false);
+ // TODO: Dismiss Keyguard.
}
- });
-
- final boolean afterKeyguardGone = intent.isActivity()
- && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
- mCurrentUserId);
- dismissKeyguardThenExecute(new OnDismissAction() {
- @Override
- public boolean onDismiss() {
- if (mHeadsUpManager != null && mHeadsUpManager.isHeadsUp(notificationKey)) {
- // Release the HUN notification to the shade.
-
- if (isPanelFullyCollapsed()) {
- HeadsUpManager.setIsClickedNotification(row, true);
- }
- //
- // In most cases, when FLAG_AUTO_CANCEL is set, the notification will
- // become canceled shortly by NoMan, but we can't assume that.
- mHeadsUpManager.releaseImmediately(notificationKey);
- }
- StatusBarNotification parentToCancel = null;
- if (shouldAutoCancel(sbn) && mGroupManager.isOnlyChildInGroup(sbn)) {
- StatusBarNotification summarySbn = mGroupManager.getLogicalGroupSummary(sbn)
- .getStatusBarNotification();
- if (shouldAutoCancel(summarySbn)) {
- parentToCancel = summarySbn;
- }
- }
- final StatusBarNotification parentToCancelFinal = parentToCancel;
- final Runnable runnable = new Runnable() {
- @Override
- public void run() {
- try {
- // The intent we are sending is for the application, which
- // won't have permission to immediately start an activity after
- // the user switches to home. We know it is safe to do at this
- // point, so make sure new activity switches are now allowed.
- ActivityManager.getService().resumeAppSwitches();
- } catch (RemoteException e) {
- }
- if (intent != null) {
- // If we are launching a work activity and require to launch
- // separate work challenge, we defer the activity action and cancel
- // notification until work challenge is unlocked.
- if (intent.isActivity()) {
- final int userId = intent.getCreatorUserHandle()
- .getIdentifier();
- if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)
- && mKeyguardManager.isDeviceLocked(userId)) {
- // TODO(b/28935539): should allow certain activities to
- // bypass work challenge
- if (startWorkChallengeIfNecessary(userId,
- intent.getIntentSender(), notificationKey)) {
- // Show work challenge, do not run PendingIntent and
- // remove notification
- return;
- }
- }
- }
- try {
- intent.send(null, 0, null, null, null, null,
- getActivityOptions());
- } catch (PendingIntent.CanceledException e) {
- // the stack trace isn't very helpful here.
- // Just log the exception message.
- Log.w(TAG, "Sending contentIntent failed: " + e);
-
- // TODO: Dismiss Keyguard.
- }
- if (intent.isActivity()) {
- mAssistManager.hideAssist();
- }
- }
-
- try {
- mBarService.onNotificationClick(notificationKey);
- } catch (RemoteException ex) {
- // system process is dead if we're here.
- }
- if (parentToCancelFinal != null) {
- // We have to post it to the UI thread for synchronization
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- Runnable removeRunnable = new Runnable() {
- @Override
- public void run() {
- performRemoveNotification(parentToCancelFinal);
- }
- };
- if (isCollapsing()) {
- // To avoid lags we're only performing the remove
- // after the shade was collapsed
- addPostCollapseAction(removeRunnable);
- } else {
- removeRunnable.run();
- }
- }
- });
- }
- }
- };
-
- if (mStatusBarKeyguardViewManager.isShowing()
- && mStatusBarKeyguardViewManager.isOccluded()) {
- mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable);
- } else {
- new Thread(runnable).start();
- }
-
- if (!mNotificationPanel.isFullyCollapsed()) {
- // close the shade if it was open
- animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
- true /* force */, true /* delayed */);
- visibilityChanged(false);
-
- return true;
- } else {
- return false;
- }
+ if (intent.isActivity()) {
+ mAssistManager.hideAssist();
}
- }, afterKeyguardGone);
- }
+ }).start();
- private boolean shouldAutoCancel(StatusBarNotification sbn) {
- int flags = sbn.getNotification().flags;
- if ((flags & Notification.FLAG_AUTO_CANCEL) != Notification.FLAG_AUTO_CANCEL) {
- return false;
- }
- if ((flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
- return false;
- }
- return true;
- }
+ return collapsePanel();
+ }, afterKeyguardGone);
+ }
- public void register(ExpandableNotificationRow row, StatusBarNotification sbn) {
- Notification notification = sbn.getNotification();
- if (notification.contentIntent != null || notification.fullScreenIntent != null) {
- row.setOnClickListener(this);
- } else {
- row.setOnClickListener(null);
- }
+ private boolean shouldAutoCancel(StatusBarNotification sbn) {
+ int flags = sbn.getNotification().flags;
+ if ((flags & Notification.FLAG_AUTO_CANCEL) != Notification.FLAG_AUTO_CANCEL) {
+ return false;
}
+ if ((flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
+ return false;
+ }
+ return true;
}
- protected Bundle getActivityOptions() {
- // Anything launched from the notification shade should always go into the
- // fullscreen stack.
- ActivityOptions options = ActivityOptions.makeBasic();
- options.setLaunchStackId(StackId.FULLSCREEN_WORKSPACE_STACK_ID);
+ protected Bundle getActivityOptions(ExpandableNotificationRow sourceNotification) {
+ ActivityOptions options;
+ if (sourceNotification != null) {
+ options = mActivityLaunchAnimator.getLaunchAnimation(sourceNotification);
+ } else {
+ options = ActivityOptions.makeBasic();
+ }
+ // 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();
}
@@ -7164,7 +5345,7 @@ public class StatusBar extends SystemUI implements DemoMode,
if (mVisible != visible) {
mVisible = visible;
if (!visible) {
- closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
+ mGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
true /* removeControls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
}
}
@@ -7192,125 +5373,10 @@ public class StatusBar extends SystemUI implements DemoMode,
}
/**
- * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService
- * about the failure.
- *
- * WARNING: this will call back into us. Don't hold any locks.
- */
- void handleNotificationError(StatusBarNotification n, String message) {
- removeNotification(n.getKey(), null);
- try {
- mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(),
- n.getInitialPid(), message, n.getUserId());
- } catch (RemoteException ex) {
- // The end is nigh.
- }
- }
-
- protected StatusBarNotification removeNotificationViews(String key, RankingMap ranking) {
- NotificationData.Entry entry = mNotificationData.remove(key, ranking);
- if (entry == null) {
- Log.w(TAG, "removeNotification for unknown key: " + key);
- return null;
- }
- updateNotifications();
- Dependency.get(LeakDetector.class).trackGarbage(entry);
- return entry.notification;
- }
-
- protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn)
- throws InflationException {
- if (DEBUG) {
- Log.d(TAG, "createNotificationViews(notification=" + sbn);
- }
- NotificationData.Entry entry = new NotificationData.Entry(sbn);
- Dependency.get(LeakDetector.class).trackInstance(entry);
- entry.createIcons(mContext, sbn);
- // Construct the expanded view.
- inflateViews(entry, mStackScroller);
- return entry;
- }
-
- protected void addNotificationViews(Entry entry) {
- if (entry == null) {
- return;
- }
- // Add the expanded view and icon.
- mNotificationData.add(entry);
- updateNotifications();
- }
-
- /**
* Updates expanded, dimmed and locked states of notification rows.
*/
- protected void updateRowStates() {
- final int N = mStackScroller.getChildCount();
-
- int visibleNotifications = 0;
- boolean onKeyguard = mState == StatusBarState.KEYGUARD;
- int maxNotifications = -1;
- if (onKeyguard) {
- maxNotifications = getMaxKeyguardNotifications(true /* recompute */);
- }
- mStackScroller.setMaxDisplayedNotifications(maxNotifications);
- Stack<ExpandableNotificationRow> stack = new Stack<>();
- for (int i = N - 1; i >= 0; i--) {
- View child = mStackScroller.getChildAt(i);
- if (!(child instanceof ExpandableNotificationRow)) {
- continue;
- }
- stack.push((ExpandableNotificationRow) child);
- }
- while(!stack.isEmpty()) {
- ExpandableNotificationRow row = stack.pop();
- NotificationData.Entry entry = row.getEntry();
- boolean isChildNotification =
- mGroupManager.isChildInGroupWithSummary(entry.notification);
-
- row.setOnKeyguard(onKeyguard);
-
- if (!onKeyguard) {
- // If mAlwaysExpandNonGroupedNotification is false, then only expand the
- // very first notification and if it's not a child of grouped notifications.
- row.setSystemExpanded(mAlwaysExpandNonGroupedNotification
- || (visibleNotifications == 0 && !isChildNotification
- && !row.isLowPriority()));
- }
-
- entry.row.setShowAmbient(isDozing());
- int userId = entry.notification.getUserId();
- boolean suppressedSummary = mGroupManager.isSummaryOfSuppressedGroup(
- entry.notification) && !entry.row.isRemoved();
- boolean showOnKeyguard = shouldShowOnKeyguard(entry.notification);
- if (suppressedSummary
- || (isLockscreenPublicMode(userId) && !mShowLockscreenNotifications)
- || (onKeyguard && !showOnKeyguard)) {
- entry.row.setVisibility(View.GONE);
- } else {
- boolean wasGone = entry.row.getVisibility() == View.GONE;
- if (wasGone) {
- entry.row.setVisibility(View.VISIBLE);
- }
- if (!isChildNotification && !entry.row.isRemoved()) {
- if (wasGone) {
- // notify the scroller of a child addition
- mStackScroller.generateAddAnimation(entry.row,
- !showOnKeyguard /* fromMoreCard */);
- }
- visibleNotifications++;
- }
- }
- if (row.isSummaryWithChildren()) {
- List<ExpandableNotificationRow> notificationChildren =
- row.getNotificationChildren();
- int size = notificationChildren.size();
- for (int i = size - 1; i >= 0; i--) {
- stack.push(notificationChildren.get(i));
- }
- }
- }
- mNotificationPanel.setNoVisibleNotifications(visibleNotifications == 0);
-
+ @Override
+ public void onUpdateRowStates() {
// The following views will be moved to the end of mStackScroller. This counter represents
// the offset from the last child. Initialized to 1 for the very last position. It is post-
// incremented in the following "changeViewPosition" calls so that its value is correct for
@@ -7333,180 +5399,10 @@ public class StatusBar extends SystemUI implements DemoMode,
mScrimController.setNotificationCount(mStackScroller.getNotGoneChildCount());
}
- public boolean shouldShowOnKeyguard(StatusBarNotification sbn) {
- return mShowLockscreenNotifications && !mNotificationData.isAmbient(sbn.getKey());
- }
-
- // extended in StatusBar
- protected void setShowLockscreenNotifications(boolean show) {
- mShowLockscreenNotifications = show;
- }
-
- protected void setLockScreenAllowRemoteInput(boolean allowLockscreenRemoteInput) {
- mAllowLockscreenRemoteInput = allowLockscreenRemoteInput;
- }
-
- private void updateLockscreenNotificationSetting() {
- final boolean show = Settings.Secure.getIntForUser(mContext.getContentResolver(),
- Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
- 1,
- mCurrentUserId) != 0;
- final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures(
- null /* admin */, mCurrentUserId);
- final boolean allowedByDpm = (dpmFlags
- & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0;
-
- setShowLockscreenNotifications(show && allowedByDpm);
-
- if (ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT) {
- final boolean remoteInput = Settings.Secure.getIntForUser(mContext.getContentResolver(),
- Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT,
- 0,
- mCurrentUserId) != 0;
- final boolean remoteInputDpm =
- (dpmFlags & DevicePolicyManager.KEYGUARD_DISABLE_REMOTE_INPUT) == 0;
-
- setLockScreenAllowRemoteInput(remoteInput && remoteInputDpm);
- } else {
- setLockScreenAllowRemoteInput(false);
- }
- }
-
- public void updateNotification(StatusBarNotification notification, RankingMap ranking)
- throws InflationException {
- if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")");
-
- final String key = notification.getKey();
- abortExistingInflation(key);
- Entry entry = mNotificationData.get(key);
- if (entry == null) {
- return;
- }
- mHeadsUpEntriesToRemoveOnSwitch.remove(entry);
- mRemoteInputEntriesToRemoveOnCollapse.remove(entry);
- if (key.equals(mKeyToRemoveOnGutsClosed)) {
- mKeyToRemoveOnGutsClosed = null;
- Log.w(TAG, "Notification that was kept for guts was updated. " + key);
- }
-
- Notification n = notification.getNotification();
- mNotificationData.updateRanking(ranking);
-
- final StatusBarNotification oldNotification = entry.notification;
- entry.notification = notification;
- mGroupManager.onEntryUpdated(entry, oldNotification);
-
- entry.updateIcons(mContext, notification);
- inflateViews(entry, mStackScroller);
-
- mForegroundServiceController.updateNotification(notification,
- mNotificationData.getImportance(key));
-
- boolean shouldPeek = shouldPeek(entry, notification);
- boolean alertAgain = alertAgain(entry, n);
-
- updateHeadsUp(key, entry, shouldPeek, alertAgain);
- updateNotifications();
-
- if (!notification.isClearable()) {
- // The user may have performed a dismiss action on the notification, since it's
- // not clearable we should snap it back.
- mStackScroller.snapViewIfNeeded(entry.row);
- }
-
- if (DEBUG) {
- // Is this for you?
- boolean isForCurrentUser = isNotificationForCurrentProfiles(notification);
- Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
- }
-
- setAreThereNotifications();
- }
-
protected void notifyHeadsUpGoingToSleep() {
maybeEscalateHeadsUp();
}
- private boolean alertAgain(Entry oldEntry, Notification newNotification) {
- return oldEntry == null || !oldEntry.hasInterrupted()
- || (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0;
- }
-
- protected boolean shouldPeek(Entry entry) {
- return shouldPeek(entry, entry.notification);
- }
-
- protected boolean shouldPeek(Entry entry, StatusBarNotification sbn) {
- if (!mUseHeadsUp || isDeviceInVrMode()) {
- if (DEBUG) Log.d(TAG, "No peeking: no huns or vr mode");
- return false;
- }
-
- if (mNotificationData.shouldFilterOut(sbn)) {
- if (DEBUG) Log.d(TAG, "No peeking: filtered notification: " + sbn.getKey());
- return false;
- }
-
- boolean inUse = mPowerManager.isScreenOn() && !mSystemServicesProxy.isDreaming();
-
- if (!inUse && !isDozing()) {
- if (DEBUG) {
- Log.d(TAG, "No peeking: not in use: " + sbn.getKey());
- }
- return false;
- }
-
- if (!isDozing() && mNotificationData.shouldSuppressScreenOn(sbn.getKey())) {
- if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey());
- return false;
- }
-
- if (isDozing() && mNotificationData.shouldSuppressScreenOff(sbn.getKey())) {
- if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey());
- return false;
- }
-
- if (entry.hasJustLaunchedFullScreenIntent()) {
- if (DEBUG) Log.d(TAG, "No peeking: recent fullscreen: " + sbn.getKey());
- return false;
- }
-
- if (isSnoozedPackage(sbn)) {
- if (DEBUG) Log.d(TAG, "No peeking: snoozed package: " + sbn.getKey());
- return false;
- }
-
- // Allow peeking for DEFAULT notifications only if we're on Ambient Display.
- int importanceLevel = isDozing() ? NotificationManager.IMPORTANCE_DEFAULT
- : NotificationManager.IMPORTANCE_HIGH;
- if (mNotificationData.getImportance(sbn.getKey()) < importanceLevel) {
- if (DEBUG) Log.d(TAG, "No peeking: unimportant notification: " + sbn.getKey());
- return false;
- }
-
- if (sbn.getNotification().fullScreenIntent != null) {
- if (mAccessibilityManager.isTouchExplorationEnabled()) {
- if (DEBUG) Log.d(TAG, "No peeking: accessible fullscreen: " + sbn.getKey());
- return false;
- } else if (mDozing) {
- // We never want heads up when we are dozing.
- return false;
- } else {
- // we only allow head-up on the lockscreen if it doesn't have a fullscreen intent
- return !mStatusBarKeyguardViewManager.isShowing()
- || mStatusBarKeyguardViewManager.isOccluded();
- }
- }
-
- // Don't peek notifications that are suppressed due to group alert behavior
- if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) {
- if (DEBUG) Log.d(TAG, "No peeking: suppressed due to group alert behavior");
- return false;
- }
-
- return true;
- }
-
/**
* @return Whether the security bouncer from Keyguard is showing.
*/
@@ -7536,17 +5432,6 @@ public class StatusBar extends SystemUI implements DemoMode,
return contextForUser.getPackageManager();
}
- @Override
- public void logNotificationExpansion(String key, boolean userAction, boolean expanded) {
- mUiOffloadThread.submit(() -> {
- try {
- mBarService.onNotificationExpansionChanged(key, userAction, expanded);
- } catch (RemoteException e) {
- // Ignore.
- }
- });
- }
-
public boolean isKeyguardSecure() {
if (mStatusBarKeyguardViewManager == null) {
// startKeyguard() hasn't been called yet, so we don't know.
@@ -7583,4 +5468,33 @@ public class StatusBar extends SystemUI implements DemoMode,
mNavigationBar.getBarTransitions().setAutoDim(true);
}
};
+
+ public NotificationGutsManager getGutsManager() {
+ return mGutsManager;
+ }
+
+ @Override
+ public boolean isPresenterLocked() {
+ return mState == StatusBarState.KEYGUARD;
+ }
+
+ @Override
+ public Handler getHandler() {
+ return mHandler;
+ }
+
+ private final NotificationInfo.CheckSaveListener mCheckSaveListener =
+ (Runnable saveImportance, StatusBarNotification sbn) -> {
+ // If the user has security enabled, show challenge if the setting is changed.
+ if (mLockscreenUserManager.isLockscreenPublicMode(sbn.getUser().getIdentifier())
+ && (mState == StatusBarState.KEYGUARD ||
+ mState == StatusBarState.SHADE_LOCKED)) {
+ onLockedNotificationImportanceChange(() -> {
+ saveImportance.run();
+ return true;
+ });
+ } else {
+ saveImportance.run();
+ }
+ };
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index c2407652b2df..07610ceff7b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -14,10 +14,10 @@
package com.android.systemui.statusbar.phone;
-import android.annotation.ColorInt;
+import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS;
+import static android.app.StatusBarManager.DISABLE_NONE;
+
import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Color;
import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.util.ArraySet;
@@ -29,11 +29,11 @@ import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
import com.android.internal.statusbar.StatusBarIcon;
-import com.android.settingslib.Utils;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.policy.DarkIconDispatcher;
+import com.android.systemui.util.Utils.DisableStateTracker;
public interface StatusBarIconController {
@@ -62,7 +62,7 @@ public interface StatusBarIconController {
}
/**
- * Version of ViewGroup that observers state from the DarkIconDispatcher.
+ * Version of ViewGroup that observes state from the DarkIconDispatcher.
*/
public static class DarkIconManager extends IconManager {
private final DarkIconDispatcher mDarkIconDispatcher;
@@ -149,6 +149,14 @@ public interface StatusBarIconController {
mContext = group.getContext();
mIconSize = mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.status_bar_icon_size);
+
+ DisableStateTracker tracker =
+ new DisableStateTracker(DISABLE_NONE, DISABLE2_SYSTEM_ICONS);
+ mGroup.addOnAttachStateChangeListener(tracker);
+ if (mGroup.isAttachedToWindow()) {
+ // In case we miss the first onAttachedToWindow event
+ tracker.onViewAttachedToWindow(mGroup);
+ }
}
protected void onIconAdded(int index, String slot, boolean blocked,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 68f8e065a429..1c3ee758a3ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -33,7 +33,6 @@ import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
-import com.android.systemui.statusbar.policy.DarkIconDispatcher;
import com.android.systemui.statusbar.policy.IconLogger;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
@@ -50,21 +49,17 @@ import java.util.ArrayList;
public class StatusBarIconControllerImpl extends StatusBarIconList implements Tunable,
ConfigurationListener, Dumpable, CommandQueue.Callbacks, StatusBarIconController {
- private final DarkIconDispatcher mDarkIconDispatcher;
-
- private Context mContext;
- private DemoStatusIcons mDemoStatusIcons;
-
private final ArrayList<IconManager> mIconGroups = new ArrayList<>();
-
private final ArraySet<String> mIconBlacklist = new ArraySet<>();
private final IconLogger mIconLogger = Dependency.get(IconLogger.class);
+ private Context mContext;
+ private DemoStatusIcons mDemoStatusIcons;
+
public StatusBarIconControllerImpl(Context context) {
super(context.getResources().getStringArray(
com.android.internal.R.array.config_statusBarIcons));
Dependency.get(ConfigurationController.class).addCallback(this);
- mDarkIconDispatcher = Dependency.get(DarkIconDispatcher.class);
mContext = context;
loadDimens();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index bbce751dcdfd..c6673095d932 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -24,7 +24,6 @@ import android.content.ComponentCallbacks2;
import android.content.Context;
import android.os.Bundle;
import android.os.SystemClock;
-import android.os.Trace;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
@@ -34,7 +33,7 @@ import android.view.WindowManagerGlobal;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.keyguard.LatencyTracker;
+import com.android.internal.util.LatencyTracker;
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.DejankUtils;
import com.android.systemui.Dependency;
@@ -71,17 +70,14 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
protected final Context mContext;
private final StatusBarWindowManager mStatusBarWindowManager;
- private final boolean mDisplayBlanksAfterDoze;
protected LockPatternUtils mLockPatternUtils;
protected ViewMediatorCallback mViewMediatorCallback;
protected StatusBar mStatusBar;
- private ScrimController mScrimController;
private FingerprintUnlockController mFingerprintUnlockController;
private ViewGroup mContainer;
- private boolean mScreenTurnedOn;
protected KeyguardBouncer mBouncer;
protected boolean mShowing;
protected boolean mOccluded;
@@ -95,12 +91,10 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
private boolean mLastBouncerDismissible;
protected boolean mLastRemoteInputActive;
private boolean mLastDozing;
- private boolean mLastDeferScrimFadeOut;
private int mLastFpMode;
private OnDismissAction mAfterKeyguardGoneAction;
private final ArrayList<Runnable> mAfterKeyguardGoneRunnables = new ArrayList<>();
- private boolean mDeferScrimFadeOut;
// Dismiss action to be launched when we stop dozing or the keyguard is gone.
private DismissWithActionRequest mPendingWakeupAction;
@@ -125,18 +119,14 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mLockPatternUtils = lockPatternUtils;
mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class);
KeyguardUpdateMonitor.getInstance(context).registerCallback(mUpdateMonitorCallback);
- mDisplayBlanksAfterDoze = context.getResources().getBoolean(
- com.android.internal.R.bool.config_displayBlanksAfterDoze);
}
public void registerStatusBar(StatusBar statusBar,
ViewGroup container,
- ScrimController scrimController,
FingerprintUnlockController fingerprintUnlockController,
DismissCallbackRegistry dismissCallbackRegistry) {
mStatusBar = statusBar;
mContainer = container;
- mScrimController = scrimController;
mFingerprintUnlockController = fingerprintUnlockController;
mBouncer = SystemUIFactory.getInstance().createKeyguardBouncer(mContext,
mViewMediatorCallback, mLockPatternUtils, container, dismissCallbackRegistry);
@@ -149,7 +139,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
public void show(Bundle options) {
mShowing = true;
mStatusBarWindowManager.setKeyguardShowing(true);
- mScrimController.abortKeyguardFadingOut();
reset(true /* hideBouncerWhenShowing */);
}
@@ -186,13 +175,18 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
public void dismissWithAction(OnDismissAction r, Runnable cancelAction,
boolean afterKeyguardGone) {
+ dismissWithAction(r, cancelAction, afterKeyguardGone, null /* message */);
+ }
+
+ public void dismissWithAction(OnDismissAction r, Runnable cancelAction,
+ boolean afterKeyguardGone, String message) {
if (mShowing) {
cancelPendingWakeupAction();
// If we're dozing, this needs to be delayed until after we wake up - unless we're
// wake-and-unlocking, because there dozing will last until the end of the transition.
if (mDozing && !isWakeAndUnlocking()) {
mPendingWakeupAction = new DismissWithActionRequest(
- r, cancelAction, afterKeyguardGone);
+ r, cancelAction, afterKeyguardGone, message);
return;
}
@@ -225,7 +219,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
if (mShowing) {
if (mOccluded && !mDozing) {
mStatusBar.hideKeyguard();
- mStatusBar.stopWaitingForKeyguardExit();
if (hideBouncerWhenShowing || mBouncer.needsFullscreenBouncer()) {
hideBouncer(false /* destroyView */);
}
@@ -254,15 +247,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
}
public void onScreenTurnedOn() {
- Trace.beginSection("StatusBarKeyguardViewManager#onScreenTurnedOn");
- mScreenTurnedOn = true;
- if (mDeferScrimFadeOut) {
- mDeferScrimFadeOut = false;
- animateScrimControllerKeyguardFadingOut(0, WAKE_AND_UNLOCK_SCRIM_FADEOUT_DURATION_MS,
- true /* skipFirstFrame */);
- updateStates();
- }
- Trace.endSection();
+ // TODO: remove
}
@Override
@@ -286,7 +271,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
}
public void onScreenTurnedOff() {
- mScreenTurnedOn = false;
+ // TODO: remove
}
public void notifyDeviceWakeUpRequested() {
@@ -375,10 +360,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mStatusBarWindowManager.setKeyguardFadingAway(true);
hideBouncer(true /* destroyView */);
updateStates();
- mScrimController.animateKeyguardFadingOut(
- StatusBar.FADE_KEYGUARD_START_DELAY,
- StatusBar.FADE_KEYGUARD_DURATION, null,
- false /* skipFirstFrame */);
}
}, new Runnable() {
@Override
@@ -401,36 +382,14 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mFingerprintUnlockController.startKeyguardFadingAway();
hideBouncer(true /* destroyView */);
if (wakeUnlockPulsing) {
- mStatusBarWindowManager.setKeyguardFadingAway(true);
mStatusBar.fadeKeyguardWhilePulsing();
- animateScrimControllerKeyguardFadingOut(delay, fadeoutDuration,
- mStatusBar::hideKeyguard, false /* skipFirstFrame */);
+ wakeAndUnlockDejank();
} else {
- mFingerprintUnlockController.startKeyguardFadingAway();
- mStatusBar.setKeyguardFadingAway(startTime, delay, fadeoutDuration);
boolean staying = mStatusBar.hideKeyguard();
if (!staying) {
mStatusBarWindowManager.setKeyguardFadingAway(true);
- if (mFingerprintUnlockController.getMode() == MODE_WAKE_AND_UNLOCK) {
- boolean turnedOnSinceAuth =
- mFingerprintUnlockController.hasScreenTurnedOnSinceAuthenticating();
- if (!mScreenTurnedOn || mDisplayBlanksAfterDoze && !turnedOnSinceAuth) {
- // Not ready to animate yet; either because the screen is not on yet,
- // or it is on but will turn off before waking out of doze.
- mDeferScrimFadeOut = true;
- } else {
-
- // Screen is already on, don't defer with fading out.
- animateScrimControllerKeyguardFadingOut(0,
- WAKE_AND_UNLOCK_SCRIM_FADEOUT_DURATION_MS,
- true /* skipFirstFrame */);
- }
- } else {
- animateScrimControllerKeyguardFadingOut(delay, fadeoutDuration,
- false /* skipFirstFrame */);
- }
+ wakeAndUnlockDejank();
} else {
- mScrimController.animateGoingToFullShade(delay, fadeoutDuration);
mStatusBar.finishKeyguardFadingAway();
mFingerprintUnlockController.finishKeyguardFadingAway();
}
@@ -445,35 +404,22 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
hideBouncer(true /* destroyView */);
}
- public void onOverlayChanged() {
+ public void onThemeChanged() {
hideBouncer(true /* destroyView */);
mBouncer.prepare();
}
- private void animateScrimControllerKeyguardFadingOut(long delay, long duration,
- boolean skipFirstFrame) {
- animateScrimControllerKeyguardFadingOut(delay, duration, null /* endRunnable */,
- skipFirstFrame);
+ public void onKeyguardFadedAway() {
+ mContainer.postDelayed(() -> mStatusBarWindowManager.setKeyguardFadingAway(false),
+ 100);
+ mStatusBar.finishKeyguardFadingAway();
+ mFingerprintUnlockController.finishKeyguardFadingAway();
+ WindowManagerGlobal.getInstance().trimMemory(
+ ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
+
}
- private void animateScrimControllerKeyguardFadingOut(long delay, long duration,
- final Runnable endRunnable, boolean skipFirstFrame) {
- Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "Fading out", 0);
- mScrimController.animateKeyguardFadingOut(delay, duration, new Runnable() {
- @Override
- public void run() {
- if (endRunnable != null) {
- endRunnable.run();
- }
- mContainer.postDelayed(() -> mStatusBarWindowManager.setKeyguardFadingAway(false),
- 100);
- mStatusBar.finishKeyguardFadingAway();
- mFingerprintUnlockController.finishKeyguardFadingAway();
- WindowManagerGlobal.getInstance().trimMemory(
- ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
- Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, "Fading out", 0);
- }
- }, skipFirstFrame);
+ private void wakeAndUnlockDejank() {
if (mFingerprintUnlockController.getMode() == MODE_WAKE_AND_UNLOCK
&& LatencyTracker.isEnabled(mContext)) {
DejankUtils.postAfterTraversal(() ->
@@ -594,7 +540,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
if (bouncerShowing != mLastBouncerShowing || mFirstUpdate) {
mStatusBarWindowManager.setBouncerShowing(bouncerShowing);
mStatusBar.setBouncerShowing(bouncerShowing);
- mScrimController.setBouncerShowing(bouncerShowing);
}
KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
@@ -612,7 +557,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mLastBouncerDismissible = bouncerDismissible;
mLastRemoteInputActive = remoteInputActive;
mLastDozing = mDozing;
- mLastDeferScrimFadeOut = mDeferScrimFadeOut;
mLastFpMode = mFingerprintUnlockController.getMode();
mStatusBar.onKeyguardViewManagerStatesUpdated();
}
@@ -625,7 +569,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
boolean keyguardShowing = mShowing && !mOccluded;
boolean hideWhileDozing = mDozing && fpMode != MODE_WAKE_AND_UNLOCK_PULSING;
return (!keyguardShowing && !hideWhileDozing || mBouncer.isShowing()
- || mRemoteInputActive) && !mDeferScrimFadeOut;
+ || mRemoteInputActive);
}
/**
@@ -635,7 +579,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
boolean keyguardShowing = mLastShowing && !mLastOccluded;
boolean hideWhileDozing = mLastDozing && mLastFpMode != MODE_WAKE_AND_UNLOCK_PULSING;
return (!keyguardShowing && !hideWhileDozing || mLastBouncerShowing
- || mLastRemoteInputActive) && !mLastDeferScrimFadeOut;
+ || mLastRemoteInputActive);
}
public boolean shouldDismissOnMenuPressed() {
@@ -693,7 +637,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
if (request != null) {
if (mShowing) {
dismissWithAction(request.dismissAction, request.cancelAction,
- request.afterKeyguardGone);
+ request.afterKeyguardGone, request.message);
} else if (request.dismissAction != null) {
request.dismissAction.onDismiss();
}
@@ -712,12 +656,14 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
final OnDismissAction dismissAction;
final Runnable cancelAction;
final boolean afterKeyguardGone;
+ final String message;
DismissWithActionRequest(OnDismissAction dismissAction, Runnable cancelAction,
- boolean afterKeyguardGone) {
+ boolean afterKeyguardGone, String message) {
this.dismissAction = dismissAction;
this.cancelAction = cancelAction;
this.afterKeyguardGone = afterKeyguardGone;
+ this.message = message;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
index ed96b4115888..948f524bb188 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.phone;
+import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
+
import android.app.ActivityManager;
import android.app.IActivityManager;
import android.content.Context;
@@ -52,6 +54,7 @@ public class StatusBarWindowManager implements RemoteInputController.Callback, D
private final Context mContext;
private final WindowManager mWindowManager;
private final IActivityManager mActivityManager;
+ private final DozeParameters mDozeParameters;
private View mStatusBarView;
private WindowManager.LayoutParams mLp;
private WindowManager.LayoutParams mLpChanged;
@@ -68,8 +71,8 @@ public class StatusBarWindowManager implements RemoteInputController.Callback, D
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
mActivityManager = ActivityManager.getService();
mKeyguardScreenRotation = shouldEnableKeyguardScreenRotation();
- mScreenBrightnessDoze = mContext.getResources().getInteger(
- com.android.internal.R.integer.config_screenBrightnessDoze) / 255f;
+ mDozeParameters = new DozeParameters(mContext);
+ mScreenBrightnessDoze = mDozeParameters.getScreenBrightnessDoze();
}
private boolean shouldEnableKeyguardScreenRotation() {
@@ -103,6 +106,7 @@ public class StatusBarWindowManager implements RemoteInputController.Callback, D
mLp.gravity = Gravity.TOP;
mLp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
mLp.setTitle("StatusBar");
+ mLp.accessibilityTitle = mContext.getString(R.string.status_bar);
mLp.packageName = mContext.getPackageName();
mStatusBarView = statusBarView;
mBarHeight = barHeight;
@@ -134,7 +138,11 @@ public class StatusBarWindowManager implements RemoteInputController.Callback, D
mLpChanged.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
}
- if (state.keyguardShowing && !state.backdropShowing && !state.dozing) {
+ final boolean showWallpaperOnAod = mDozeParameters.getAlwaysOn() &&
+ state.wallpaperSupportsAmbientMode &&
+ state.scrimsVisibility != ScrimController.VISIBILITY_FULLY_OPAQUE;
+ if (state.keyguardShowing && !state.backdropShowing &&
+ (!state.dozing || showWallpaperOnAod)) {
mLpChanged.flags |= WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
} else {
mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
@@ -156,7 +164,7 @@ public class StatusBarWindowManager implements RemoteInputController.Callback, D
private void applyFocusableFlag(State state) {
boolean panelFocusable = state.statusBarFocusable && state.panelExpanded;
if (state.bouncerShowing && (state.keyguardOccluded || state.keyguardNeedsInput)
- || StatusBar.ENABLE_REMOTE_INPUT && state.remoteInputActive) {
+ || ENABLE_REMOTE_INPUT && state.remoteInputActive) {
mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
} else if (state.isKeyguardShowingAndNotOccluded() || panelFocusable) {
@@ -186,7 +194,8 @@ public class StatusBarWindowManager implements RemoteInputController.Callback, D
private boolean isExpanded(State state) {
return !state.forceCollapsed && (state.isKeyguardShowingAndNotOccluded()
|| state.panelVisible || state.keyguardFadingAway || state.bouncerShowing
- || state.headsUpShowing || state.scrimsVisible);
+ || state.headsUpShowing
+ || state.scrimsVisibility != ScrimController.VISIBILITY_FULLY_TRANSPARENT);
}
private void applyFitsSystemWindows(State state) {
@@ -334,8 +343,8 @@ public class StatusBarWindowManager implements RemoteInputController.Callback, D
apply(mCurrentState);
}
- public void setScrimsVisible(boolean scrimsVisible) {
- mCurrentState.scrimsVisible = scrimsVisible;
+ public void setScrimsVisibility(int scrimsVisibility) {
+ mCurrentState.scrimsVisibility = scrimsVisibility;
apply(mCurrentState);
}
@@ -344,6 +353,11 @@ public class StatusBarWindowManager implements RemoteInputController.Callback, D
apply(mCurrentState);
}
+ public void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) {
+ mCurrentState.wallpaperSupportsAmbientMode = supportsAmbientMode;
+ apply(mCurrentState);
+ }
+
/**
* @param state The {@link StatusBarState} of the status bar.
*/
@@ -431,6 +445,7 @@ public class StatusBarWindowManager implements RemoteInputController.Callback, D
boolean forceDozeBrightness;
boolean forceUserActivity;
boolean backdropShowing;
+ boolean wallpaperSupportsAmbientMode;
/**
* The {@link StatusBar} state from the status bar.
@@ -440,7 +455,7 @@ public class StatusBarWindowManager implements RemoteInputController.Callback, D
boolean remoteInputActive;
boolean forcePluginOpen;
boolean dozing;
- boolean scrimsVisible;
+ int scrimsVisibility;
private boolean isKeyguardShowingAndNotOccluded() {
return keyguardShowing && !keyguardOccluded;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index d7f11f710501..a79a41b07797 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -62,6 +62,9 @@ import com.android.systemui.statusbar.DragDownHelper;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
public class StatusBarWindowView extends FrameLayout {
public static final String TAG = "StatusBarWindowView";
@@ -88,6 +91,8 @@ public class StatusBarWindowView extends FrameLayout {
private ViewTreeObserver.OnPreDrawListener mFloatingToolbarPreDrawListener;
private boolean mTouchCancelled;
private boolean mTouchActive;
+ private boolean mExpandAnimationRunning;
+ private boolean mExpandAnimationPending;
public StatusBarWindowView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -248,7 +253,6 @@ public class StatusBarWindowView extends FrameLayout {
public void setTouchActive(boolean touchActive) {
mTouchActive = touchActive;
- mStackScrollLayout.setTouchActive(touchActive);
}
@Override
@@ -268,7 +272,7 @@ public class StatusBarWindowView extends FrameLayout {
|| ev.getActionMasked() == MotionEvent.ACTION_CANCEL) {
setTouchActive(false);
}
- if (mTouchCancelled) {
+ if (mTouchCancelled || mExpandAnimationRunning || mExpandAnimationPending) {
return false;
}
mFalsingManager.onTouchEvent(ev, getWidth(), getHeight());
@@ -389,6 +393,21 @@ public class StatusBarWindowView extends FrameLayout {
}
}
+ public void setExpandAnimationRunning(boolean expandAnimationRunning) {
+ mExpandAnimationRunning = expandAnimationRunning;
+ }
+
+ public void setExpandAnimationPending(boolean pending) {
+ mExpandAnimationPending = pending;
+ }
+
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.print(" mExpandAnimationPending="); pw.println(mExpandAnimationPending);
+ pw.print(" mExpandAnimationRunning="); pw.println(mExpandAnimationRunning);
+ pw.print(" mTouchCancelled="); pw.println(mTouchCancelled);
+ pw.print(" mTouchActive="); pw.println(mTouchActive);
+ }
+
public class LayoutParams extends FrameLayout.LayoutParams {
public boolean ignoreRightInset;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
new file mode 100644
index 000000000000..503a1b40bb0c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
@@ -0,0 +1,180 @@
+/*
+ * 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.
+ */
+
+/**
+ * A container for Status bar system icons. Limits the number of system icons and handles overflow
+ * similar to NotificationIconController. Can be used to layout nested StatusIconContainers
+ *
+ * Children are expected to be of type StatusBarIconView.
+ */
+package com.android.systemui.statusbar.phone;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.util.ArrayMap;
+import android.util.AttributeSet;
+
+import android.view.View;
+import com.android.keyguard.AlphaOptimizedLinearLayout;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.StatusBarIconView;
+import com.android.systemui.statusbar.stack.ViewState;
+
+public class StatusIconContainer extends AlphaOptimizedLinearLayout {
+
+ private static final String TAG = "StatusIconContainer";
+ private static final boolean DEBUG = false;
+ private static final int MAX_ICONS = 5;
+ private static final int MAX_DOTS = 3;
+
+ public StatusIconContainer(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ float midY = getHeight() / 2.0f;
+
+ // Layout all child views so that we can move them around later
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ int width = child.getMeasuredWidth();
+ int height = child.getMeasuredHeight();
+ int top = (int) (midY - height / 2.0f);
+ child.layout(0, top, width, top + height);
+ }
+
+ resetViewStates();
+ calculateIconTranslations();
+ applyIconStates();
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ final int count = getChildCount();
+ // Measure all children so that they report the correct width
+ for (int i = 0; i < count; i++) {
+ measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec);
+ }
+ }
+
+ @Override
+ public void onViewAdded(View child) {
+ super.onViewAdded(child);
+ ViewState vs = new ViewState();
+ child.setTag(R.id.status_bar_view_state_tag, vs);
+ }
+
+ @Override
+ public void onViewRemoved(View child) {
+ super.onViewRemoved(child);
+ child.setTag(R.id.status_bar_view_state_tag, null);
+ }
+
+ /**
+ * Layout is happening from end -> start
+ */
+ private void calculateIconTranslations() {
+ float width = getWidth();
+ float translationX = width;
+ float contentStart = getPaddingStart();
+ int childCount = getChildCount();
+ // Underflow === don't show content until that index
+ int firstUnderflowIndex = -1;
+ if (DEBUG) android.util.Log.d(TAG, "calculateIconTransitions: start=" + translationX);
+
+ //TODO: Dots
+ for (int i = childCount - 1; i >= 0; i--) {
+ View child = getChildAt(i);
+ if (!(child instanceof StatusBarIconView)) {
+ continue;
+ }
+
+ ViewState childState = getViewStateFromChild(child);
+ if (childState == null ) {
+ continue;
+ }
+
+ // Rely on StatusBarIcon for truth about visibility
+ if (!((StatusBarIconView) child).getStatusBarIcon().visible) {
+ childState.hidden = true;
+ continue;
+ }
+
+ childState.xTranslation = translationX - child.getWidth();
+
+ if (childState.xTranslation < contentStart) {
+ if (firstUnderflowIndex == -1) {
+ firstUnderflowIndex = i;
+ }
+ }
+
+ translationX -= child.getWidth();
+ }
+
+ if (firstUnderflowIndex != -1) {
+ for (int i = 0; i <= firstUnderflowIndex; i++) {
+ View child = getChildAt(i);
+ ViewState vs = getViewStateFromChild(child);
+ if (vs != null) {
+ vs.hidden = true;
+ }
+ }
+ }
+
+ // Stole this from NotificationIconContainer. Not optimal but keeps the layout logic clean
+ if (isLayoutRtl()) {
+ for (int i = 0; i < childCount; i++) {
+ View child = getChildAt(i);
+ ViewState state = getViewStateFromChild(child);
+ state.xTranslation = width - state.xTranslation - child.getWidth();
+ }
+ }
+ }
+
+ private void applyIconStates() {
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ ViewState vs = getViewStateFromChild(child);
+ if (vs != null) {
+ vs.applyToView(child);
+ }
+ }
+ }
+
+ private void resetViewStates() {
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ ViewState vs = getViewStateFromChild(child);
+ if (vs == null) {
+ continue;
+ }
+
+ vs.initFrom(child);
+ vs.alpha = 1.0f;
+ if (child instanceof StatusBarIconView) {
+ vs.hidden = !((StatusBarIconView)child).getStatusBarIcon().visible;
+ } else {
+ vs.hidden = false;
+ }
+ }
+ }
+
+ private static @Nullable ViewState getViewStateFromChild(View child) {
+ return (ViewState) child.getTag(R.id.status_bar_view_state_tag);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
index c0a683734b8b..0d21c4efe22e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
@@ -20,7 +20,6 @@ import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.net.wifi.WifiManager.ActionListener;
-import android.os.Looper;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -59,13 +58,19 @@ public class AccessPointControllerImpl
private int mCurrentUser;
- public AccessPointControllerImpl(Context context, Looper bgLooper) {
+ public AccessPointControllerImpl(Context context) {
mContext = context;
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- mWifiTracker = new WifiTracker(context, this, bgLooper, false, true);
+ mWifiTracker = new WifiTracker(context, this, false, true);
mCurrentUser = ActivityManager.getCurrentUser();
}
+ @Override
+ protected void finalize() throws Throwable {
+ super.finalize();
+ mWifiTracker.onDestroy();
+ }
+
public boolean canConfigWifi() {
return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_WIFI,
new UserHandle(mCurrentUser));
@@ -81,7 +86,7 @@ public class AccessPointControllerImpl
if (DEBUG) Log.d(TAG, "addCallback " + callback);
mCallbacks.add(callback);
if (mCallbacks.size() == 1) {
- mWifiTracker.startTracking();
+ mWifiTracker.onStart();
}
}
@@ -91,7 +96,7 @@ public class AccessPointControllerImpl
if (DEBUG) Log.d(TAG, "removeCallback " + callback);
mCallbacks.remove(callback);
if (mCallbacks.isEmpty()) {
- mWifiTracker.stopTracking();
+ mWifiTracker.onStop();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java
index 6a573f593a92..d85e18c17252 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java
@@ -14,10 +14,14 @@
package com.android.systemui.statusbar.policy;
+import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.Context;
+import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener;
+import java.util.List;
+
/**
* For mocking because AccessibilityManager is final for some reason...
*/
@@ -39,4 +43,27 @@ public class AccessibilityManagerWrapper implements
public void removeCallback(AccessibilityServicesStateChangeListener listener) {
mAccessibilityManager.removeAccessibilityServicesStateChangeListener(listener);
}
+
+ public void addAccessibilityStateChangeListener(
+ AccessibilityManager.AccessibilityStateChangeListener listener) {
+ mAccessibilityManager.addAccessibilityStateChangeListener(listener);
+ }
+
+ public void removeAccessibilityStateChangeListener(
+ AccessibilityManager.AccessibilityStateChangeListener listener) {
+ mAccessibilityManager.removeAccessibilityStateChangeListener(listener);
+ }
+
+ public boolean isEnabled() {
+ return mAccessibilityManager.isEnabled();
+ }
+
+ public void sendAccessibilityEvent(AccessibilityEvent event) {
+ mAccessibilityManager.sendAccessibilityEvent(event);
+ }
+
+ public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(
+ int feedbackTypeFlags) {
+ return mAccessibilityManager.getEnabledAccessibilityServiceList(feedbackTypeFlags);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
index 42ce4c5bf2dc..a011952f1476 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
@@ -16,7 +16,9 @@
package com.android.systemui.statusbar.policy;
+import android.annotation.NonNull;
import android.util.ArraySet;
+import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewPropertyAnimator;
@@ -25,55 +27,52 @@ import android.widget.FrameLayout;
import com.android.internal.util.Preconditions;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.phone.StatusBarWindowView;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
+import java.util.function.Consumer;
+
/**
* Controls showing and hiding of the brightness mirror.
*/
public class BrightnessMirrorController
implements CallbackController<BrightnessMirrorController.BrightnessMirrorListener> {
- private final NotificationStackScrollLayout mStackScroller;
- public long TRANSITION_DURATION_OUT = 150;
- public long TRANSITION_DURATION_IN = 200;
+ private final static long TRANSITION_DURATION_OUT = 150;
+ private final static long TRANSITION_DURATION_IN = 200;
private final StatusBarWindowView mStatusBarWindow;
- private final ScrimController mScrimController;
+ private final NotificationStackScrollLayout mStackScroller;
+ private final Consumer<Boolean> mVisibilityCallback;
private final View mNotificationPanel;
private final ArraySet<BrightnessMirrorListener> mBrightnessMirrorListeners = new ArraySet<>();
private final int[] mInt2Cache = new int[2];
private View mBrightnessMirror;
public BrightnessMirrorController(StatusBarWindowView statusBarWindow,
- ScrimController scrimController) {
+ @NonNull Consumer<Boolean> visibilityCallback) {
mStatusBarWindow = statusBarWindow;
mBrightnessMirror = statusBarWindow.findViewById(R.id.brightness_mirror);
mNotificationPanel = statusBarWindow.findViewById(R.id.notification_panel);
- mStackScroller = (NotificationStackScrollLayout) statusBarWindow.findViewById(
- R.id.notification_stack_scroller);
- mScrimController = scrimController;
+ mStackScroller = statusBarWindow.findViewById(R.id.notification_stack_scroller);
+ mVisibilityCallback = visibilityCallback;
}
public void showMirror() {
mBrightnessMirror.setVisibility(View.VISIBLE);
mStackScroller.setFadingOut(true);
- mScrimController.forceHideScrims(true /* hide */, true /* animated */);
+ mVisibilityCallback.accept(true);
outAnimation(mNotificationPanel.animate())
.withLayer();
}
public void hideMirror() {
- mScrimController.forceHideScrims(false /* hide */, true /* animated */);
+ mVisibilityCallback.accept(false);
inAnimation(mNotificationPanel.animate())
.withLayer()
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- mBrightnessMirror.setVisibility(View.INVISIBLE);
- mStackScroller.setFadingOut(false);
- }
+ .withEndAction(() -> {
+ mBrightnessMirror.setVisibility(View.INVISIBLE);
+ mStackScroller.setFadingOut(false);
});
}
@@ -128,9 +127,11 @@ public class BrightnessMirrorController
}
private void reinflate() {
+ ContextThemeWrapper qsThemeContext =
+ new ContextThemeWrapper(mBrightnessMirror.getContext(), R.style.qs_theme);
int index = mStatusBarWindow.indexOfChild(mBrightnessMirror);
mStatusBarWindow.removeView(mBrightnessMirror);
- mBrightnessMirror = LayoutInflater.from(mBrightnessMirror.getContext()).inflate(
+ mBrightnessMirror = LayoutInflater.from(qsThemeContext).inflate(
R.layout.brightness_mirror, mStatusBarWindow, false);
mStatusBarWindow.addView(mBrightnessMirror, index);
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 53dfb244c776..040d7ec32ecc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -16,119 +16,69 @@
package com.android.systemui.statusbar.policy;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Resources;
import android.database.ContentObserver;
+import android.os.SystemClock;
import android.os.Handler;
import android.os.Looper;
-import android.os.SystemClock;
-import android.provider.Settings;
-import android.support.v4.util.ArraySet;
import android.util.ArrayMap;
+import android.provider.Settings;
import android.util.Log;
-import android.util.Pools;
-import android.view.View;
-import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityEvent;
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.NotificationData;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.phone.StatusBar;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Collection;
+import java.util.Iterator;
+import java.util.stream.Stream;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.Stack;
/**
* A manager which handles heads up notifications which is a special mode where
* they simply peek from the top of the screen.
*/
-public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsListener,
- VisualStabilityManager.Callback {
+public class HeadsUpManager {
private static final String TAG = "HeadsUpManager";
private static final boolean DEBUG = false;
private static final String SETTING_HEADS_UP_SNOOZE_LENGTH_MS = "heads_up_snooze_length_ms";
- private static final int TAG_CLICKED_NOTIFICATION = R.id.is_clicked_heads_up_tag;
- private final int mHeadsUpNotificationDecay;
- private final int mMinimumDisplayTime;
+ protected final Clock mClock = new Clock();
+ protected final HashSet<OnHeadsUpChangedListener> mListeners = new HashSet<>();
+ protected final Handler mHandler = new Handler(Looper.getMainLooper());
- private final int mTouchAcceptanceDelay;
- private final ArrayMap<String, Long> mSnoozedPackages;
- private final HashSet<OnHeadsUpChangedListener> mListeners = new HashSet<>();
- private final int mDefaultSnoozeLengthMs;
- private final Handler mHandler = new Handler(Looper.getMainLooper());
- private final Pools.Pool<HeadsUpEntry> mEntryPool = new Pools.Pool<HeadsUpEntry>() {
+ protected final Context mContext;
- private Stack<HeadsUpEntry> mPoolObjects = new Stack<>();
+ protected int mHeadsUpNotificationDecay;
+ protected int mMinimumDisplayTime;
+ protected int mTouchAcceptanceDelay;
+ protected int mSnoozeLengthMs;
+ protected boolean mHasPinnedNotification;
+ protected int mUser;
- @Override
- public HeadsUpEntry acquire() {
- if (!mPoolObjects.isEmpty()) {
- return mPoolObjects.pop();
- }
- return new HeadsUpEntry();
- }
+ private final HashMap<String, HeadsUpEntry> mHeadsUpEntries = new HashMap<>();
+ private final ArrayMap<String, Long> mSnoozedPackages;
- @Override
- public boolean release(HeadsUpEntry instance) {
- instance.reset();
- mPoolObjects.push(instance);
- return true;
- }
- };
-
- private final View mStatusBarWindowView;
- private final int mStatusBarHeight;
- private final Context mContext;
- private final NotificationGroupManager mGroupManager;
- private StatusBar mBar;
- private int mSnoozeLengthMs;
- private ContentObserver mSettingsObserver;
- private HashMap<String, HeadsUpEntry> mHeadsUpEntries = new HashMap<>();
- private HashSet<String> mSwipedOutKeys = new HashSet<>();
- private int mUser;
- private Clock mClock;
- private boolean mReleaseOnExpandFinish;
- private boolean mTrackingHeadsUp;
- private HashSet<NotificationData.Entry> mEntriesToRemoveAfterExpand = new HashSet<>();
- private ArraySet<NotificationData.Entry> mEntriesToRemoveWhenReorderingAllowed
- = new ArraySet<>();
- private boolean mIsExpanded;
- private boolean mHasPinnedNotification;
- private int[] mTmpTwoArray = new int[2];
- private boolean mHeadsUpGoingAway;
- private boolean mWaitingOnCollapseWhenGoingAway;
- private boolean mIsObserving;
- private boolean mRemoteInputActive;
- private float mExpandedHeight;
- private VisualStabilityManager mVisualStabilityManager;
- private int mStatusBarState;
-
- public HeadsUpManager(final Context context, View statusBarWindowView,
- NotificationGroupManager groupManager) {
+ public HeadsUpManager(@NonNull final Context context) {
mContext = context;
- Resources resources = mContext.getResources();
- mTouchAcceptanceDelay = resources.getInteger(R.integer.touch_acceptance_delay);
- mSnoozedPackages = new ArrayMap<>();
- mDefaultSnoozeLengthMs = resources.getInteger(R.integer.heads_up_default_snooze_length_ms);
- mSnoozeLengthMs = mDefaultSnoozeLengthMs;
+ Resources resources = context.getResources();
mMinimumDisplayTime = resources.getInteger(R.integer.heads_up_notification_minimum_time);
mHeadsUpNotificationDecay = resources.getInteger(R.integer.heads_up_notification_decay);
- mClock = new Clock();
+ mTouchAcceptanceDelay = resources.getInteger(R.integer.touch_acceptance_delay);
+ mSnoozedPackages = new ArrayMap<>();
+ int defaultSnoozeLengthMs =
+ resources.getInteger(R.integer.heads_up_default_snooze_length_ms);
mSnoozeLengthMs = Settings.Global.getInt(context.getContentResolver(),
- SETTING_HEADS_UP_SNOOZE_LENGTH_MS, mDefaultSnoozeLengthMs);
- mSettingsObserver = new ContentObserver(mHandler) {
+ SETTING_HEADS_UP_SNOOZE_LENGTH_MS, defaultSnoozeLengthMs);
+ ContentObserver settingsObserver = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
final int packageSnoozeLengthMs = Settings.Global.getInt(
@@ -141,48 +91,27 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL
};
context.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(SETTING_HEADS_UP_SNOOZE_LENGTH_MS), false,
- mSettingsObserver);
- mStatusBarWindowView = statusBarWindowView;
- mGroupManager = groupManager;
- mStatusBarHeight = resources.getDimensionPixelSize(
- com.android.internal.R.dimen.status_bar_height);
- }
-
- private void updateTouchableRegionListener() {
- boolean shouldObserve = mHasPinnedNotification || mHeadsUpGoingAway
- || mWaitingOnCollapseWhenGoingAway;
- if (shouldObserve == mIsObserving) {
- return;
- }
- if (shouldObserve) {
- mStatusBarWindowView.getViewTreeObserver().addOnComputeInternalInsetsListener(this);
- mStatusBarWindowView.requestLayout();
- } else {
- mStatusBarWindowView.getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
- }
- mIsObserving = shouldObserve;
+ settingsObserver);
}
- public void setBar(StatusBar bar) {
- mBar = bar;
- }
-
- public void addListener(OnHeadsUpChangedListener listener) {
+ /**
+ * Adds an OnHeadUpChangedListener to observe events.
+ */
+ public void addListener(@NonNull OnHeadsUpChangedListener listener) {
mListeners.add(listener);
}
- public void removeListener(OnHeadsUpChangedListener listener) {
+ /**
+ * Removes the OnHeadUpChangedListener from the observer list.
+ */
+ public void removeListener(@NonNull OnHeadsUpChangedListener listener) {
mListeners.remove(listener);
}
- public StatusBar getBar() {
- return mBar;
- }
-
/**
* Called when posting a new notification to the heads up.
*/
- public void showNotification(NotificationData.Entry headsUp) {
+ public void showNotification(@NonNull NotificationData.Entry headsUp) {
if (DEBUG) Log.v(TAG, "showNotification");
addHeadsUpEntry(headsUp);
updateNotification(headsUp, true);
@@ -192,7 +121,7 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL
/**
* Called when updating or posting a notification to the heads up.
*/
- public void updateNotification(NotificationData.Entry headsUp, boolean alert) {
+ public void updateNotification(@NonNull NotificationData.Entry headsUp, boolean alert) {
if (DEBUG) Log.v(TAG, "updateNotification");
headsUp.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
@@ -204,14 +133,13 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL
// with the groupmanager
return;
}
- headsUpEntry.updateEntry();
+ headsUpEntry.updateEntry(true /* updatePostTime */);
setEntryPinned(headsUpEntry, shouldHeadsUpBecomePinned(headsUp));
}
}
- private void addHeadsUpEntry(NotificationData.Entry entry) {
- HeadsUpEntry headsUpEntry = mEntryPool.acquire();
-
+ private void addHeadsUpEntry(@NonNull NotificationData.Entry entry) {
+ HeadsUpEntry headsUpEntry = createHeadsUpEntry();
// This will also add the entry to the sortedList
headsUpEntry.setEntry(entry);
mHeadsUpEntries.put(entry.key, headsUpEntry);
@@ -223,16 +151,17 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL
entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
}
- private boolean shouldHeadsUpBecomePinned(NotificationData.Entry entry) {
- return mStatusBarState != StatusBarState.KEYGUARD
- && !mIsExpanded || hasFullScreenIntent(entry);
+ protected boolean shouldHeadsUpBecomePinned(@NonNull NotificationData.Entry entry) {
+ return hasFullScreenIntent(entry);
}
- private boolean hasFullScreenIntent(NotificationData.Entry entry) {
+ protected boolean hasFullScreenIntent(@NonNull NotificationData.Entry entry) {
return entry.notification.getNotification().fullScreenIntent != null;
}
- private void setEntryPinned(HeadsUpEntry headsUpEntry, boolean isPinned) {
+ protected void setEntryPinned(
+ @NonNull HeadsUpManager.HeadsUpEntry headsUpEntry, boolean isPinned) {
+ if (DEBUG) Log.v(TAG, "setEntryPinned: " + isPinned);
ExpandableNotificationRow row = headsUpEntry.entry.row;
if (row.isPinned() != isPinned) {
row.setPinned(isPinned);
@@ -247,33 +176,35 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL
}
}
- private void removeHeadsUpEntry(NotificationData.Entry entry) {
+ protected void removeHeadsUpEntry(@NonNull NotificationData.Entry entry) {
HeadsUpEntry remove = mHeadsUpEntries.remove(entry.key);
+ onHeadsUpEntryRemoved(remove);
+ }
+
+ protected void onHeadsUpEntryRemoved(@NonNull HeadsUpEntry remove) {
+ NotificationData.Entry entry = remove.entry;
entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
entry.row.setHeadsUp(false);
setEntryPinned(remove, false /* isPinned */);
for (OnHeadsUpChangedListener listener : mListeners) {
listener.onHeadsUpStateChanged(entry, false);
}
- mEntryPool.release(remove);
+ releaseHeadsUpEntry(remove);
}
- public void removeAllHeadsUpEntries() {
- for (String key : mHeadsUpEntries.keySet()) {
- removeHeadsUpEntry(mHeadsUpEntries.get(key).entry);
- }
- }
-
- private void updatePinnedMode() {
+ protected void updatePinnedMode() {
boolean hasPinnedNotification = hasPinnedNotificationInternal();
if (hasPinnedNotification == mHasPinnedNotification) {
return;
}
+ if (DEBUG) {
+ Log.v(TAG, "Pinned mode changed: " + mHasPinnedNotification + " -> " +
+ hasPinnedNotification);
+ }
mHasPinnedNotification = hasPinnedNotification;
if (mHasPinnedNotification) {
MetricsLogger.count(mContext, "note_peek", 1);
}
- updateTouchableRegionListener();
for (OnHeadsUpChangedListener listener : mListeners) {
listener.onHeadsUpPinnedModeChanged(hasPinnedNotification);
}
@@ -285,47 +216,36 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL
* @return true if the notification was removed and false if it still needs to be kept around
* for a bit since it wasn't shown long enough
*/
- public boolean removeNotification(String key, boolean ignoreEarliestRemovalTime) {
- if (DEBUG) Log.v(TAG, "remove");
- if (wasShownLongEnough(key) || ignoreEarliestRemovalTime) {
- releaseImmediately(key);
- return true;
- } else {
- getHeadsUpEntry(key).removeAsSoonAsPossible();
- return false;
- }
+ public boolean removeNotification(@NonNull String key, boolean ignoreEarliestRemovalTime) {
+ if (DEBUG) Log.v(TAG, "removeNotification");
+ releaseImmediately(key);
+ return true;
}
- private boolean wasShownLongEnough(String key) {
- HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
- HeadsUpEntry topEntry = getTopEntry();
- if (mSwipedOutKeys.contains(key)) {
- // We always instantly dismiss views being manually swiped out.
- mSwipedOutKeys.remove(key);
- return true;
- }
- if (headsUpEntry != topEntry) {
- return true;
- }
- return headsUpEntry.wasShownLongEnough();
- }
-
- public boolean isHeadsUp(String key) {
+ /**
+ * Returns if the given notification is in the Heads Up Notification list or not.
+ */
+ public boolean isHeadsUp(@NonNull String key) {
return mHeadsUpEntries.containsKey(key);
}
/**
- * Push any current Heads Up notification down into the shade.
+ * Pushes any current Heads Up notification down into the shade.
*/
public void releaseAllImmediately() {
if (DEBUG) Log.v(TAG, "releaseAllImmediately");
- ArrayList<String> keys = new ArrayList<>(mHeadsUpEntries.keySet());
- for (String key : keys) {
- releaseImmediately(key);
+ Iterator<HeadsUpEntry> iterator = mHeadsUpEntries.values().iterator();
+ while (iterator.hasNext()) {
+ HeadsUpEntry entry = iterator.next();
+ iterator.remove();
+ onHeadsUpEntryRemoved(entry);
}
}
- public void releaseImmediately(String key) {
+ /**
+ * Pushes the given Heads Up notification down into the shade.
+ */
+ public void releaseImmediately(@NonNull String key) {
HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
if (headsUpEntry == null) {
return;
@@ -334,11 +254,14 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL
removeHeadsUpEntry(shadeEntry);
}
- public boolean isSnoozed(String packageName) {
+ /**
+ * Returns if the given notification is snoozed or not.
+ */
+ public boolean isSnoozed(@NonNull String packageName) {
final String key = snoozeKey(packageName, mUser);
Long snoozedUntil = mSnoozedPackages.get(key);
if (snoozedUntil != null) {
- if (snoozedUntil > SystemClock.elapsedRealtime()) {
+ if (snoozedUntil > mClock.currentTimeMillis()) {
if (DEBUG) Log.v(TAG, key + " snoozed");
return true;
}
@@ -347,39 +270,71 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL
return false;
}
+ /**
+ * Snoozes all current Heads Up Notifications.
+ */
public void snooze() {
for (String key : mHeadsUpEntries.keySet()) {
HeadsUpEntry entry = mHeadsUpEntries.get(key);
String packageName = entry.entry.notification.getPackageName();
mSnoozedPackages.put(snoozeKey(packageName, mUser),
- SystemClock.elapsedRealtime() + mSnoozeLengthMs);
+ mClock.currentTimeMillis() + mSnoozeLengthMs);
}
- mReleaseOnExpandFinish = true;
}
- private static String snoozeKey(String packageName, int user) {
+ @NonNull
+ private static String snoozeKey(@NonNull String packageName, int user) {
return user + "," + packageName;
}
- private HeadsUpEntry getHeadsUpEntry(String key) {
+ @Nullable
+ protected HeadsUpEntry getHeadsUpEntry(@NonNull String key) {
return mHeadsUpEntries.get(key);
}
- public NotificationData.Entry getEntry(String key) {
- return mHeadsUpEntries.get(key).entry;
+ /**
+ * Returns the entry of given Heads Up Notification.
+ *
+ * @param key Key of heads up notification
+ */
+ @Nullable
+ public NotificationData.Entry getEntry(@NonNull String key) {
+ HeadsUpEntry entry = mHeadsUpEntries.get(key);
+ return entry != null ? entry.entry : null;
+ }
+
+ /**
+ * Returns the stream of all current Heads Up Notifications.
+ */
+ @NonNull
+ public Stream<NotificationData.Entry> getAllEntries() {
+ return mHeadsUpEntries.values().stream().map(headsUpEntry -> headsUpEntry.entry);
+ }
+
+ /**
+ * Returns the top Heads Up Notification, which appeares to show at first.
+ */
+ @Nullable
+ public NotificationData.Entry getTopEntry() {
+ HeadsUpEntry topEntry = getTopHeadsUpEntry();
+ return (topEntry != null) ? topEntry.entry : null;
}
- public Collection<HeadsUpEntry> getAllEntries() {
- return mHeadsUpEntries.values();
+ /**
+ * Returns if any heads up notification is available or not.
+ */
+ public boolean hasHeadsUpNotifications() {
+ return !mHeadsUpEntries.isEmpty();
}
- public HeadsUpEntry getTopEntry() {
+ @Nullable
+ protected HeadsUpEntry getTopHeadsUpEntry() {
if (mHeadsUpEntries.isEmpty()) {
return null;
}
HeadsUpEntry topEntry = null;
for (HeadsUpEntry entry: mHeadsUpEntries.values()) {
- if (topEntry == null || entry.compareTo(topEntry) == -1) {
+ if (topEntry == null || entry.compareTo(topEntry) < 0) {
topEntry = entry;
}
}
@@ -387,56 +342,22 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL
}
/**
- * Decides whether a click is invalid for a notification, i.e it has not been shown long enough
- * that a user might have consciously clicked on it.
- *
- * @param key the key of the touched notification
- * @return whether the touch is invalid and should be discarded
+ * Sets the current user.
*/
- public boolean shouldSwallowClick(String key) {
- HeadsUpEntry entry = mHeadsUpEntries.get(key);
- if (entry != null && mClock.currentTimeMillis() < entry.postTime) {
- return true;
- }
- return false;
- }
-
- public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) {
- if (mIsExpanded || mBar.isBouncerShowing()) {
- // The touchable region is always the full area when expanded
- return;
- }
- if (mHasPinnedNotification) {
- ExpandableNotificationRow topEntry = getTopEntry().entry.row;
- if (topEntry.isChildInGroup()) {
- final ExpandableNotificationRow groupSummary
- = mGroupManager.getGroupSummary(topEntry.getStatusBarNotification());
- if (groupSummary != null) {
- topEntry = groupSummary;
- }
- }
- topEntry.getLocationOnScreen(mTmpTwoArray);
- int minX = mTmpTwoArray[0];
- int maxX = mTmpTwoArray[0] + topEntry.getWidth();
- int maxY = topEntry.getIntrinsicHeight();
-
- info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
- info.touchableRegion.set(minX, 0, maxX, maxY);
- } else if (mHeadsUpGoingAway || mWaitingOnCollapseWhenGoingAway) {
- info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
- info.touchableRegion.set(0, 0, mStatusBarWindowView.getWidth(), mStatusBarHeight);
- }
- }
-
public void setUser(int user) {
mUser = user;
}
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
pw.println("HeadsUpManager state:");
+ dumpInternal(fd, pw, args);
+ }
+
+ protected void dumpInternal(
+ @NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
pw.print(" mTouchAcceptanceDelay="); pw.println(mTouchAcceptanceDelay);
pw.print(" mSnoozeLengthMs="); pw.println(mSnoozeLengthMs);
- pw.print(" now="); pw.println(SystemClock.elapsedRealtime());
+ pw.print(" now="); pw.println(mClock.currentTimeMillis());
pw.print(" mUser="); pw.println(mUser);
for (HeadsUpEntry entry: mHeadsUpEntries.values()) {
pw.print(" HeadsUpEntry="); pw.println(entry.entry);
@@ -449,6 +370,9 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL
}
}
+ /**
+ * Returns if there are any pinned Heads Up Notifications or not.
+ */
public boolean hasPinnedHeadsUp() {
return mHasPinnedNotification;
}
@@ -464,14 +388,8 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL
}
/**
- * Notifies that a notification was swiped out and will be removed.
- *
- * @param key the notification key
+ * Unpins all pinned Heads Up Notifications.
*/
- public void addSwipedOutNotification(String key) {
- mSwipedOutKeys.add(key);
- }
-
public void unpinAll() {
for (String key : mHeadsUpEntries.keySet()) {
HeadsUpEntry entry = mHeadsUpEntries.get(key);
@@ -481,60 +399,13 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL
}
}
- public void onExpandingFinished() {
- if (mReleaseOnExpandFinish) {
- releaseAllImmediately();
- mReleaseOnExpandFinish = false;
- } else {
- for (NotificationData.Entry entry : mEntriesToRemoveAfterExpand) {
- if (isHeadsUp(entry.key)) {
- // Maybe the heads-up was removed already
- removeHeadsUpEntry(entry);
- }
- }
- }
- mEntriesToRemoveAfterExpand.clear();
- }
-
- public void setTrackingHeadsUp(boolean trackingHeadsUp) {
- mTrackingHeadsUp = trackingHeadsUp;
- }
-
- public boolean isTrackingHeadsUp() {
- return mTrackingHeadsUp;
- }
-
- public void setIsExpanded(boolean isExpanded) {
- if (isExpanded != mIsExpanded) {
- mIsExpanded = isExpanded;
- if (isExpanded) {
- // make sure our state is sane
- mWaitingOnCollapseWhenGoingAway = false;
- mHeadsUpGoingAway = false;
- updateTouchableRegionListener();
- }
- }
- }
-
/**
- * @return the height of the top heads up notification when pinned. This is different from the
- * intrinsic height, which also includes whether the notification is system expanded and
- * is mainly used when dragging down from a heads up notification.
+ * Returns the value of the tracking-heads-up flag. See the doc of {@code setTrackingHeadsUp} as
+ * well.
*/
- public int getTopHeadsUpPinnedHeight() {
- HeadsUpEntry topEntry = getTopEntry();
- if (topEntry == null || topEntry.entry == null) {
- return 0;
- }
- ExpandableNotificationRow row = topEntry.entry.row;
- if (row.isChildInGroup()) {
- final ExpandableNotificationRow groupSummary
- = mGroupManager.getGroupSummary(row.getStatusBarNotification());
- if (groupSummary != null) {
- row = groupSummary;
- }
- }
- return row.getPinnedHeadsUpHeight();
+ public boolean isTrackingHeadsUp() {
+ // Might be implemented in subclass.
+ return false;
}
/**
@@ -543,7 +414,7 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL
* @return -1 if the first argument should be ranked higher than the second, 1 if the second
* one should be ranked higher and 0 if they are equal.
*/
- public int compare(NotificationData.Entry a, NotificationData.Entry b) {
+ public int compare(@NonNull NotificationData.Entry a, @NonNull NotificationData.Entry b) {
HeadsUpEntry aEntry = getHeadsUpEntry(a.key);
HeadsUpEntry bEntry = getHeadsUpEntry(b.key);
if (aEntry == null || bEntry == null) {
@@ -553,147 +424,62 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL
}
/**
- * Set that we are exiting the headsUp pinned mode, but some notifications might still be
- * animating out. This is used to keep the touchable regions in a sane state.
- */
- public void setHeadsUpGoingAway(boolean headsUpGoingAway) {
- if (headsUpGoingAway != mHeadsUpGoingAway) {
- mHeadsUpGoingAway = headsUpGoingAway;
- if (!headsUpGoingAway) {
- waitForStatusBarLayout();
- }
- updateTouchableRegionListener();
- }
- }
-
- /**
- * We need to wait on the whole panel to collapse, before we can remove the touchable region
- * listener.
- */
- private void waitForStatusBarLayout() {
- mWaitingOnCollapseWhenGoingAway = true;
- mStatusBarWindowView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
- @Override
- public void onLayoutChange(View v, int left, int top, int right, int bottom,
- int oldLeft,
- int oldTop, int oldRight, int oldBottom) {
- if (mStatusBarWindowView.getHeight() <= mStatusBarHeight) {
- mStatusBarWindowView.removeOnLayoutChangeListener(this);
- mWaitingOnCollapseWhenGoingAway = false;
- updateTouchableRegionListener();
- }
- }
- });
- }
-
- public static void setIsClickedNotification(View child, boolean clicked) {
- child.setTag(TAG_CLICKED_NOTIFICATION, clicked ? true : null);
- }
-
- public static boolean isClickedHeadsUpNotification(View child) {
- Boolean clicked = (Boolean) child.getTag(TAG_CLICKED_NOTIFICATION);
- return clicked != null && clicked;
- }
-
- public void setRemoteInputActive(NotificationData.Entry entry, boolean remoteInputActive) {
- HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(entry.key);
- if (headsUpEntry != null && headsUpEntry.remoteInputActive != remoteInputActive) {
- headsUpEntry.remoteInputActive = remoteInputActive;
- if (remoteInputActive) {
- headsUpEntry.removeAutoRemovalCallbacks();
- } else {
- headsUpEntry.updateEntry(false /* updatePostTime */);
- }
- }
- }
-
- /**
* Set an entry to be expanded and therefore stick in the heads up area if it's pinned
* until it's collapsed again.
*/
- public void setExpanded(NotificationData.Entry entry, boolean expanded) {
- HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(entry.key);
- if (headsUpEntry != null && headsUpEntry.expanded != expanded && entry.row.isPinned()) {
- headsUpEntry.expanded = expanded;
- if (expanded) {
- headsUpEntry.removeAutoRemovalCallbacks();
- } else {
- headsUpEntry.updateEntry(false /* updatePostTime */);
- }
+ public void setExpanded(@NonNull NotificationData.Entry entry, boolean expanded) {
+ HeadsUpManager.HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(entry.key);
+ if (headsUpEntry != null && entry.row.isPinned()) {
+ headsUpEntry.expanded(expanded);
}
}
- @Override
- public void onReorderingAllowed() {
- mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(false);
- for (NotificationData.Entry entry : mEntriesToRemoveWhenReorderingAllowed) {
- if (isHeadsUp(entry.key)) {
- // Maybe the heads-up was removed already
- removeHeadsUpEntry(entry);
- }
- }
- mEntriesToRemoveWhenReorderingAllowed.clear();
- mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(true);
+ @NonNull
+ protected HeadsUpEntry createHeadsUpEntry() {
+ return new HeadsUpEntry();
}
- public void setVisualStabilityManager(VisualStabilityManager visualStabilityManager) {
- mVisualStabilityManager = visualStabilityManager;
- }
-
- public void setStatusBarState(int statusBarState) {
- mStatusBarState = statusBarState;
+ protected void releaseHeadsUpEntry(@NonNull HeadsUpEntry entry) {
+ entry.reset();
}
/**
* This represents a notification and how long it is in a heads up mode. It also manages its
* lifecycle automatically when created.
*/
- public class HeadsUpEntry implements Comparable<HeadsUpEntry> {
- public NotificationData.Entry entry;
+ protected class HeadsUpEntry implements Comparable<HeadsUpEntry> {
+ @Nullable public NotificationData.Entry entry;
public long postTime;
- public long earliestRemovaltime;
- private Runnable mRemoveHeadsUpRunnable;
public boolean remoteInputActive;
+ public long earliestRemovaltime;
public boolean expanded;
- public void setEntry(final NotificationData.Entry entry) {
+ @Nullable private Runnable mRemoveHeadsUpRunnable;
+
+ public void setEntry(@Nullable final NotificationData.Entry entry) {
+ setEntry(entry, null);
+ }
+
+ public void setEntry(@Nullable final NotificationData.Entry entry,
+ @Nullable Runnable removeHeadsUpRunnable) {
this.entry = entry;
+ this.mRemoveHeadsUpRunnable = removeHeadsUpRunnable;
// The actual post time will be just after the heads-up really slided in
postTime = mClock.currentTimeMillis() + mTouchAcceptanceDelay;
- mRemoveHeadsUpRunnable = new Runnable() {
- @Override
- public void run() {
- if (!mVisualStabilityManager.isReorderingAllowed()) {
- mEntriesToRemoveWhenReorderingAllowed.add(entry);
- mVisualStabilityManager.addReorderingAllowedCallback(HeadsUpManager.this);
- } else if (!mTrackingHeadsUp) {
- removeHeadsUpEntry(entry);
- } else {
- mEntriesToRemoveAfterExpand.add(entry);
- }
- }
- };
- updateEntry();
- }
-
- public void updateEntry() {
- updateEntry(true);
+ updateEntry(true /* updatePostTime */);
}
public void updateEntry(boolean updatePostTime) {
+ if (DEBUG) Log.v(TAG, "updateEntry");
+
long currentTime = mClock.currentTimeMillis();
earliestRemovaltime = currentTime + mMinimumDisplayTime;
if (updatePostTime) {
postTime = Math.max(postTime, currentTime);
}
removeAutoRemovalCallbacks();
- if (mEntriesToRemoveAfterExpand.contains(entry)) {
- mEntriesToRemoveAfterExpand.remove(entry);
- }
- if (mEntriesToRemoveWhenReorderingAllowed.contains(entry)) {
- mEntriesToRemoveWhenReorderingAllowed.remove(entry);
- }
+
if (!isSticky()) {
long finishTime = postTime + mHeadsUpNotificationDecay;
long removeDelay = Math.max(finishTime - currentTime, mMinimumDisplayTime);
@@ -707,7 +493,7 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL
}
@Override
- public int compareTo(HeadsUpEntry o) {
+ public int compareTo(@NonNull HeadsUpEntry o) {
boolean isPinned = entry.row.isPinned();
boolean otherPinned = o.entry.row.isPinned();
if (isPinned && !otherPinned) {
@@ -734,26 +520,29 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL
: -1;
}
- public void removeAutoRemovalCallbacks() {
- mHandler.removeCallbacks(mRemoveHeadsUpRunnable);
- }
-
- public boolean wasShownLongEnough() {
- return earliestRemovaltime < mClock.currentTimeMillis();
- }
-
- public void removeAsSoonAsPossible() {
- removeAutoRemovalCallbacks();
- mHandler.postDelayed(mRemoveHeadsUpRunnable,
- earliestRemovaltime - mClock.currentTimeMillis());
+ public void expanded(boolean expanded) {
+ this.expanded = expanded;
}
public void reset() {
- removeAutoRemovalCallbacks();
entry = null;
- mRemoveHeadsUpRunnable = null;
expanded = false;
remoteInputActive = false;
+ removeAutoRemovalCallbacks();
+ mRemoveHeadsUpRunnable = null;
+ }
+
+ public void removeAutoRemovalCallbacks() {
+ if (mRemoveHeadsUpRunnable != null)
+ mHandler.removeCallbacks(mRemoveHeadsUpRunnable);
+ }
+
+ public void removeAsSoonAsPossible() {
+ if (mRemoveHeadsUpRunnable != null) {
+ removeAutoRemovalCallbacks();
+ mHandler.postDelayed(mRemoveHeadsUpRunnable,
+ earliestRemovaltime - mClock.currentTimeMillis());
+ }
}
}
@@ -762,5 +551,4 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL
return SystemClock.elapsedRealtime();
}
}
-
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java
new file mode 100644
index 000000000000..1e3c123cfbc6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java
@@ -0,0 +1,47 @@
+/*
+ * 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.statusbar.policy;
+
+import android.view.View;
+
+import com.android.systemui.R;
+
+/**
+ * A class of utility static methods for heads up notifications.
+ */
+public final class HeadsUpUtil {
+ private static final int TAG_CLICKED_NOTIFICATION = R.id.is_clicked_heads_up_tag;
+
+ /**
+ * Set the given view as clicked or not-clicked.
+ * @param view The view to be set the flag to.
+ * @param clicked True to set as clicked. False to not-clicked.
+ */
+ public static void setIsClickedHeadsUpNotification(View view, boolean clicked) {
+ view.setTag(TAG_CLICKED_NOTIFICATION, clicked ? true : null);
+ }
+
+ /**
+ * Check if the given view has the flag of "clicked notification"
+ * @param view The view to be checked.
+ * @return True if the view has clicked. False othrewise.
+ */
+ public static boolean isClickedHeadsUpNotification(View view) {
+ Boolean clicked = (Boolean) view.getTag(TAG_CLICKED_NOTIFICATION);
+ return clicked != null && clicked;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java
index 6457209a407b..830b50e35490 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java
@@ -26,7 +26,9 @@ public interface HotspotController extends CallbackController<Callback>, Dumpabl
void setHotspotEnabled(boolean enabled);
boolean isHotspotSupported();
- public interface Callback {
- void onHotspotChanged(boolean enabled);
+ int getNumConnectedDevices();
+
+ interface Callback {
+ void onHotspotChanged(boolean enabled, int numDevices);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
index 1ebb986e4488..8792b4f3cc4c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
@@ -23,31 +23,35 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.wifi.WifiManager;
-import android.os.Handler;
import android.os.UserManager;
import android.util.Log;
+import com.android.systemui.Dependency;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
-public class HotspotControllerImpl implements HotspotController {
+public class HotspotControllerImpl implements HotspotController, WifiManager.SoftApCallback {
private static final String TAG = "HotspotController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
- private final Receiver mReceiver = new Receiver();
+ private final ArrayList<Callback> mCallbacks = new ArrayList<>();
+ private final WifiStateReceiver mWifiStateReceiver = new WifiStateReceiver();
private final ConnectivityManager mConnectivityManager;
+ private final WifiManager mWifiManager;
private final Context mContext;
private int mHotspotState;
+ private int mNumConnectedDevices;
private boolean mWaitingForCallback;
public HotspotControllerImpl(Context context) {
mContext = context;
- mConnectivityManager = (ConnectivityManager) context.getSystemService(
- Context.CONNECTIVITY_SERVICE);
+ mConnectivityManager =
+ (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
}
@Override
@@ -84,7 +88,8 @@ public class HotspotControllerImpl implements HotspotController {
if (callback == null || mCallbacks.contains(callback)) return;
if (DEBUG) Log.d(TAG, "addCallback " + callback);
mCallbacks.add(callback);
- mReceiver.setListening(!mCallbacks.isEmpty());
+
+ updateWifiStateListeners(!mCallbacks.isEmpty());
}
}
@@ -94,7 +99,26 @@ public class HotspotControllerImpl implements HotspotController {
if (DEBUG) Log.d(TAG, "removeCallback " + callback);
synchronized (mCallbacks) {
mCallbacks.remove(callback);
- mReceiver.setListening(!mCallbacks.isEmpty());
+
+ updateWifiStateListeners(!mCallbacks.isEmpty());
+ }
+ }
+
+ /**
+ * Updates the wifi state receiver to either start or stop listening to get updates to the
+ * hotspot status. Additionally starts listening to wifi manager state to track the number of
+ * connected devices.
+ *
+ * @param shouldListen whether we should start listening to various wifi statuses
+ */
+ private void updateWifiStateListeners(boolean shouldListen) {
+ mWifiStateReceiver.setListening(shouldListen);
+ if (shouldListen) {
+ mWifiManager.registerSoftApCallback(
+ this,
+ Dependency.get(Dependency.MAIN_HANDLER));
+ } else {
+ mWifiManager.unregisterSoftApCallback(this);
}
}
@@ -116,20 +140,55 @@ public class HotspotControllerImpl implements HotspotController {
if (DEBUG) Log.d(TAG, "Starting tethering");
mConnectivityManager.startTethering(
ConnectivityManager.TETHERING_WIFI, false, callback);
- fireCallback(isHotspotEnabled());
+ fireHotspotChangedCallback(isHotspotEnabled());
} else {
mConnectivityManager.stopTethering(ConnectivityManager.TETHERING_WIFI);
}
}
- private void fireCallback(boolean isEnabled) {
+ @Override
+ public int getNumConnectedDevices() {
+ return mNumConnectedDevices;
+ }
+
+ /**
+ * Sends a hotspot changed callback with the new enabled status. Wraps
+ * {@link #fireHotspotChangedCallback(boolean, int)} and assumes that the number of devices has
+ * not changed.
+ *
+ * @param enabled whether the hotspot is enabled
+ */
+ private void fireHotspotChangedCallback(boolean enabled) {
+ fireHotspotChangedCallback(enabled, mNumConnectedDevices);
+ }
+
+ /**
+ * Sends a hotspot changed callback with the new enabled status & the number of devices
+ * connected to the hotspot. Be careful when calling over multiple threads, especially if one of
+ * them is the main thread (as it can be blocked).
+ *
+ * @param enabled whether the hotspot is enabled
+ * @param numConnectedDevices number of devices connected to the hotspot
+ */
+ private void fireHotspotChangedCallback(boolean enabled, int numConnectedDevices) {
synchronized (mCallbacks) {
for (Callback callback : mCallbacks) {
- callback.onHotspotChanged(isEnabled);
+ callback.onHotspotChanged(enabled, numConnectedDevices);
}
}
}
+ @Override
+ public void onStateChanged(int state, int failureReason) {
+ // Do nothing - we don't care about changing anything here.
+ }
+
+ @Override
+ public void onNumClientsChanged(int numConnectedDevices) {
+ mNumConnectedDevices = numConnectedDevices;
+ fireHotspotChangedCallback(isHotspotEnabled(), numConnectedDevices);
+ }
+
private final class OnStartTetheringCallback extends
ConnectivityManager.OnStartTetheringCallback {
@Override
@@ -143,12 +202,15 @@ public class HotspotControllerImpl implements HotspotController {
public void onTetheringFailed() {
if (DEBUG) Log.d(TAG, "onTetheringFailed");
mWaitingForCallback = false;
- fireCallback(isHotspotEnabled());
+ fireHotspotChangedCallback(isHotspotEnabled());
// TODO: Show error.
}
}
- private final class Receiver extends BroadcastReceiver {
+ /**
+ * Class to listen in on wifi state and update the hotspot state
+ */
+ private final class WifiStateReceiver extends BroadcastReceiver {
private boolean mRegistered;
public void setListening(boolean listening) {
@@ -170,8 +232,17 @@ public class HotspotControllerImpl implements HotspotController {
int state = intent.getIntExtra(
WifiManager.EXTRA_WIFI_AP_STATE, WifiManager.WIFI_AP_STATE_FAILED);
if (DEBUG) Log.d(TAG, "onReceive " + state);
+
+ // Update internal hotspot state for tracking before using any enabled/callback methods.
mHotspotState = state;
- fireCallback(mHotspotState == WifiManager.WIFI_AP_STATE_ENABLED);
+
+ if (!isHotspotEnabled()) {
+ // Reset num devices if the hotspot is no longer enabled so we don't get ghost
+ // counters.
+ mNumConnectedDevices = 0;
+ }
+
+ fireHotspotChangedCallback(isHotspotEnabled());
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java
index 21a96e2ea06a..cce9d1cd6d95 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java
@@ -39,7 +39,7 @@ public class KeyButtonDrawable extends LayerDrawable {
}
}
- private KeyButtonDrawable(Drawable[] drawables) {
+ protected KeyButtonDrawable(Drawable[] drawables) {
super(drawables);
for (int i = 0; i < drawables.length; i++) {
setLayerGravity(i, Gravity.CENTER);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java
index cc7943b85bc1..a2bec982ed58 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java
@@ -26,9 +26,11 @@ import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
+import android.os.Handler;
import android.view.DisplayListCanvas;
import android.view.RenderNodeAnimator;
import android.view.View;
+import android.view.ViewConfiguration;
import android.view.animation.Interpolator;
import com.android.systemui.Interpolators;
@@ -56,14 +58,17 @@ public class KeyButtonRipple extends Drawable {
private float mGlowAlpha = 0f;
private float mGlowScale = 1f;
private boolean mPressed;
+ private boolean mVisible;
private boolean mDrawingHardwareGlow;
private int mMaxWidth;
private boolean mLastDark;
private boolean mDark;
+ private boolean mDelayTouchFeedback;
private final Interpolator mInterpolator = new LogInterpolator();
private boolean mSupportHardware;
private final View mTargetView;
+ private final Handler mHandler = new Handler();
private final HashSet<Animator> mRunningAnimations = new HashSet<>();
private final ArrayList<Animator> mTmpArray = new ArrayList<>();
@@ -77,6 +82,10 @@ public class KeyButtonRipple extends Drawable {
mDark = darkIntensity >= 0.5f;
}
+ public void setDelayTouchFeedback(boolean delay) {
+ mDelayTouchFeedback = delay;
+ }
+
private Paint getRipplePaint() {
if (mRipplePaint == null) {
mRipplePaint = new Paint();
@@ -211,7 +220,16 @@ public class KeyButtonRipple extends Drawable {
}
}
+ /**
+ * Abort the ripple while it is delayed and before shown used only when setShouldDelayStartTouch
+ * is enabled.
+ */
+ public void abortDelayedRipple() {
+ mHandler.removeCallbacksAndMessages(null);
+ }
+
private void cancelAnimations() {
+ mVisible = false;
mTmpArray.addAll(mRunningAnimations);
int size = mTmpArray.size();
for (int i = 0; i < size; i++) {
@@ -220,11 +238,21 @@ public class KeyButtonRipple extends Drawable {
}
mTmpArray.clear();
mRunningAnimations.clear();
+ mHandler.removeCallbacksAndMessages(null);
}
private void setPressedSoftware(boolean pressed) {
if (pressed) {
- enterSoftware();
+ if (mDelayTouchFeedback) {
+ if (mRunningAnimations.isEmpty()) {
+ mHandler.removeCallbacksAndMessages(null);
+ mHandler.postDelayed(this::enterSoftware, ViewConfiguration.getTapTimeout());
+ } else if (mVisible) {
+ enterSoftware();
+ }
+ } else {
+ enterSoftware();
+ }
} else {
exitSoftware();
}
@@ -232,6 +260,7 @@ public class KeyButtonRipple extends Drawable {
private void enterSoftware() {
cancelAnimations();
+ mVisible = true;
mGlowAlpha = getMaxGlowAlpha();
ObjectAnimator scaleAnimator = ObjectAnimator.ofFloat(this, "glowScale",
0f, GLOW_MAX_SCALE_FACTOR);
@@ -240,6 +269,12 @@ public class KeyButtonRipple extends Drawable {
scaleAnimator.addListener(mAnimatorListener);
scaleAnimator.start();
mRunningAnimations.add(scaleAnimator);
+
+ // With the delay, it could eventually animate the enter animation with no pressed state,
+ // then immediately show the exit animation. If this is skipped there will be no ripple.
+ if (mDelayTouchFeedback && !mPressed) {
+ exitSoftware();
+ }
}
private void exitSoftware() {
@@ -253,7 +288,16 @@ public class KeyButtonRipple extends Drawable {
private void setPressedHardware(boolean pressed) {
if (pressed) {
- enterHardware();
+ if (mDelayTouchFeedback) {
+ if (mRunningAnimations.isEmpty()) {
+ mHandler.removeCallbacksAndMessages(null);
+ mHandler.postDelayed(this::enterHardware, ViewConfiguration.getTapTimeout());
+ } else if (mVisible) {
+ enterHardware();
+ }
+ } else {
+ enterHardware();
+ }
} else {
exitHardware();
}
@@ -302,6 +346,7 @@ public class KeyButtonRipple extends Drawable {
private void enterHardware() {
cancelAnimations();
+ mVisible = true;
mDrawingHardwareGlow = true;
setExtendStart(CanvasProperty.createFloat(getExtendSize() / 2));
final RenderNodeAnimator startAnim = new RenderNodeAnimator(getExtendStart(),
@@ -343,6 +388,12 @@ public class KeyButtonRipple extends Drawable {
mRunningAnimations.add(endAnim);
invalidateSelf();
+
+ // With the delay, it could eventually animate the enter animation with no pressed state,
+ // then immediately show the exit animation. If this is skipped there will be no ripple.
+ if (mDelayTouchFeedback && !mPressed) {
+ exitHardware();
+ }
}
private void exitHardware() {
@@ -366,6 +417,7 @@ public class KeyButtonRipple extends Drawable {
public void onAnimationEnd(Animator animation) {
mRunningAnimations.remove(animation);
if (mRunningAnimations.isEmpty() && !mPressed) {
+ mVisible = false;
mDrawingHardwareGlow = false;
invalidateSelf();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
index 05017718d42b..98bebec8511e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
@@ -27,8 +27,12 @@ import android.media.AudioManager;
import android.metrics.LogMaker;
import android.os.AsyncTask;
import android.os.Bundle;
+import android.os.RemoteException;
import android.os.SystemClock;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
import android.util.AttributeSet;
+import android.util.Log;
import android.util.TypedValue;
import android.view.HapticFeedbackConstants;
import android.view.InputDevice;
@@ -45,25 +49,32 @@ import android.widget.ImageView;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.Dependency;
+import com.android.systemui.OverviewProxyService;
import com.android.systemui.R;
import com.android.systemui.plugins.statusbar.phone.NavBarButtonProvider.ButtonInterface;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK;
public class KeyButtonView extends ImageView implements ButtonInterface {
+ private static final String TAG = KeyButtonView.class.getSimpleName();
private final boolean mPlaySounds;
private int mContentDescriptionRes;
private long mDownTime;
private int mCode;
private int mTouchSlop;
+ private int mTouchDownX;
+ private int mTouchDownY;
private boolean mSupportsLongpress = true;
private AudioManager mAudioManager;
private boolean mGestureAborted;
private boolean mLongClicked;
private OnClickListener mOnClickListener;
private final KeyButtonRipple mRipple;
+ private final OverviewProxyService mOverviewProxyService;
+ private final Vibrator mVibrator;
private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
private final Runnable mCheckLongPress = new Runnable() {
@@ -110,6 +121,8 @@ public class KeyButtonView extends ImageView implements ButtonInterface {
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
mRipple = new KeyButtonRipple(context, this);
+ mVibrator = mContext.getSystemService(Vibrator.class);
+ mOverviewProxyService = Dependency.get(OverviewProxyService.class);
setBackground(mRipple);
}
@@ -189,6 +202,7 @@ public class KeyButtonView extends ImageView implements ButtonInterface {
}
public boolean onTouchEvent(MotionEvent ev) {
+ final boolean isProxyConnected = mOverviewProxyService.getProxy() != null;
final int action = ev.getAction();
int x, y;
if (action == MotionEvent.ACTION_DOWN) {
@@ -203,23 +217,34 @@ public class KeyButtonView extends ImageView implements ButtonInterface {
mDownTime = SystemClock.uptimeMillis();
mLongClicked = false;
setPressed(true);
+ mTouchDownX = (int) ev.getX();
+ mTouchDownY = (int) ev.getY();
if (mCode != 0) {
sendEvent(KeyEvent.ACTION_DOWN, 0, mDownTime);
} else {
// Provide the same haptic feedback that the system offers for virtual keys.
performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
}
- playSoundEffect(SoundEffectConstants.CLICK);
+ if (isProxyConnected) {
+ // Provide small vibration for quick step or immediate down feedback
+ AsyncTask.execute(() ->
+ mVibrator.vibrate(VibrationEffect
+ .get(VibrationEffect.EFFECT_TICK, false)));
+ } else {
+ playSoundEffect(SoundEffectConstants.CLICK);
+ }
removeCallbacks(mCheckLongPress);
postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout());
break;
case MotionEvent.ACTION_MOVE:
x = (int)ev.getX();
y = (int)ev.getY();
- setPressed(x >= -mTouchSlop
- && x < getWidth() + mTouchSlop
- && y >= -mTouchSlop
- && y < getHeight() + mTouchSlop);
+ boolean exceededTouchSlopX = Math.abs(x - mTouchDownX) > mTouchSlop;
+ boolean exceededTouchSlopY = Math.abs(y - mTouchDownY) > mTouchSlop;
+ if (exceededTouchSlopX || exceededTouchSlopY) {
+ setPressed(false);
+ removeCallbacks(mCheckLongPress);
+ }
break;
case MotionEvent.ACTION_CANCEL:
setPressed(false);
@@ -231,13 +256,21 @@ public class KeyButtonView extends ImageView implements ButtonInterface {
case MotionEvent.ACTION_UP:
final boolean doIt = isPressed() && !mLongClicked;
setPressed(false);
- // Always send a release ourselves because it doesn't seem to be sent elsewhere
- // and it feels weird to sometimes get a release haptic and other times not.
- if ((SystemClock.uptimeMillis() - mDownTime) > 150 && !mLongClicked) {
+ if (isProxyConnected) {
+ if (doIt) {
+ performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
+ playSoundEffect(SoundEffectConstants.CLICK);
+ }
+ } else if ((SystemClock.uptimeMillis() - mDownTime) > 150 && !mLongClicked) {
+ // Always send a release ourselves because it doesn't seem to be sent elsewhere
+ // and it feels weird to sometimes get a release haptic and other times not.
performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY_RELEASE);
}
if (mCode != 0) {
if (doIt) {
+ // If there was a pending remote recents animation, then we need to
+ // cancel the animation now before we handle the button itself
+ ActivityManagerWrapper.getInstance().cancelRecentsAnimation();
sendEvent(KeyEvent.ACTION_UP, 0);
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
} else {
@@ -284,6 +317,7 @@ public class KeyButtonView extends ImageView implements ButtonInterface {
@Override
public void abortCurrentGesture() {
setPressed(false);
+ mRipple.abortDelayedRipple();
mGestureAborted = true;
}
@@ -301,6 +335,11 @@ public class KeyButtonView extends ImageView implements ButtonInterface {
}
@Override
+ public void setDelayTouchFeedback(boolean shouldDelay) {
+ mRipple.setDelayTouchFeedback(shouldDelay);
+ }
+
+ @Override
public void setVertical(boolean vertical) {
//no op
}
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 874f0d9d5b5f..3febdfdfbe7e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
@@ -16,11 +16,12 @@
package com.android.systemui.statusbar.policy;
+import static com.android.settingslib.Utils.updateLocationEnabled;
+
import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.StatusBarManager;
import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -28,14 +29,12 @@ import android.location.LocationManager;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.support.annotation.VisibleForTesting;
-
-import com.android.systemui.R;
import com.android.systemui.util.Utils;
-
import java.util.ArrayList;
import java.util.List;
@@ -99,31 +98,29 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio
* @return true if attempt to change setting was successful.
*/
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();
if (isUserLocationRestricted(currentUserId)) {
return false;
}
- final ContentResolver cr = mContext.getContentResolver();
// When enabling location, a user consent dialog will pop up, and the
// setting won't be fully enabled until the user accepts the agreement.
- int mode = enabled
- ? Settings.Secure.LOCATION_MODE_PREVIOUS : Settings.Secure.LOCATION_MODE_OFF;
- // QuickSettings always runs as the owner, so specifically set the settings
- // for the current foreground user.
- return Settings.Secure
- .putIntForUser(cr, Settings.Secure.LOCATION_MODE, mode, currentUserId);
+ updateLocationEnabled(mContext, enabled, currentUserId,
+ Settings.Secure.LOCATION_CHANGER_QUICK_SETTINGS);
+ return true;
}
/**
- * Returns true if location isn't disabled in settings.
+ * Returns true if location is enabled in settings.
*/
public boolean isLocationEnabled() {
- ContentResolver resolver = mContext.getContentResolver();
// QuickSettings always runs as the owner, so specifically retrieve the settings
// for the current foreground user.
- int mode = Settings.Secure.getIntForUser(resolver, Settings.Secure.LOCATION_MODE,
- Settings.Secure.LOCATION_MODE_OFF, ActivityManager.getCurrentUser());
- return mode != Settings.Secure.LOCATION_MODE_OFF;
+ LocationManager locationManager =
+ (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
+ return locationManager.isLocationEnabledForUser(
+ UserHandle.of(ActivityManager.getCurrentUser()));
}
@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 8516278a8891..f0854edeaece 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -35,8 +35,8 @@ import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.telephony.cdma.EriInfo;
+import com.android.settingslib.graph.SignalDrawable;
import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.SignalDrawable;
import com.android.systemui.statusbar.policy.NetworkController.IconState;
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
import com.android.systemui.statusbar.policy.NetworkControllerImpl.Config;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index d45a5ec70782..baf0ebf5efaa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -152,7 +152,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
(WifiManager) context.getSystemService(Context.WIFI_SERVICE),
SubscriptionManager.from(context), Config.readConfig(context), bgLooper,
new CallbackHandler(),
- new AccessPointControllerImpl(context, bgLooper),
+ new AccessPointControllerImpl(context),
new DataUsageController(context),
new SubscriptionDefaults(),
deviceProvisionedController);
@@ -385,13 +385,6 @@ public class NetworkControllerImpl extends BroadcastReceiver
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... args) {
- // Disable tethering if enabling Wifi
- final int wifiApState = mWifiManager.getWifiApState();
- if (enabled && ((wifiApState == WifiManager.WIFI_AP_STATE_ENABLING) ||
- (wifiApState == WifiManager.WIFI_AP_STATE_ENABLED))) {
- mWifiManager.setWifiApEnabled(null, false);
- }
-
mWifiManager.setWifiEnabled(enabled);
return null;
}
@@ -828,6 +821,10 @@ public class NetworkControllerImpl extends BroadcastReceiver
} else {
mWifiSignalController.setActivity(WifiManager.DATA_ACTIVITY_NONE);
}
+ String ssid = args.getString("ssid");
+ if (ssid != null) {
+ mDemoWifiState.ssid = ssid;
+ }
mDemoWifiState.enabled = show;
mWifiSignalController.notifyListeners();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 37b0de400c9e..179c0d57aa50 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -27,7 +27,9 @@ import android.content.pm.ShortcutManager;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
+import android.os.SystemClock;
import android.text.Editable;
+import android.text.SpannedString;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.util.Log;
@@ -53,11 +55,9 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.statusbar.ExpandableView;
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.notification.NotificationViewWrapper;
-import com.android.systemui.statusbar.stack.ScrollContainer;
import com.android.systemui.statusbar.stack.StackStateAnimator;
/**
@@ -82,8 +82,6 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
private NotificationData.Entry mEntry;
- private ScrollContainer mScrollContainer;
- private View mScrollContainerChild;
private boolean mRemoved;
private int mRevealCx;
@@ -139,11 +137,13 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
RemoteInput.addResultsToIntent(mRemoteInputs, fillInIntent,
results);
+ RemoteInput.setResultsSource(fillInIntent, RemoteInput.SOURCE_FREE_FORM_INPUT);
mEditText.setEnabled(false);
mSendButton.setVisibility(INVISIBLE);
mProgressBar.setVisibility(VISIBLE);
mEntry.remoteInputText = mEditText.getText();
+ mEntry.lastRemoteInputSent = SystemClock.elapsedRealtime();
mController.addSpinning(mEntry.key, mToken);
mController.removeRemoteInput(mEntry, mToken);
mEditText.mShowImeOnInputConnection = false;
@@ -169,6 +169,10 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
}
}
+ public CharSequence getText() {
+ return mEditText.getText();
+ }
+
public static RemoteInputView inflate(Context context, ViewGroup root,
NotificationData.Entry entry,
RemoteInputController controller) {
@@ -302,6 +306,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
private void reset() {
mResetting = true;
+ mEntry.remoteInputTextWhenReset = SpannedString.valueOf(mEditText.getText());
mEditText.getText().clear();
mEditText.setEnabled(true);
@@ -347,41 +352,16 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- findScrollContainer();
- if (mScrollContainer != null) {
- mScrollContainer.requestDisallowLongPress();
- mScrollContainer.requestDisallowDismiss();
- }
+ mController.requestDisallowLongPressAndDismiss();
}
return super.onInterceptTouchEvent(ev);
}
public boolean requestScrollTo() {
- findScrollContainer();
- mScrollContainer.lockScrollTo(mScrollContainerChild);
+ mController.lockScrollTo(mEntry);
return true;
}
- private void findScrollContainer() {
- if (mScrollContainer == null) {
- mScrollContainerChild = null;
- ViewParent p = this;
- while (p != null) {
- if (mScrollContainerChild == null && p instanceof ExpandableView) {
- mScrollContainerChild = (View) p;
- }
- if (p.getParent() instanceof ScrollContainer) {
- mScrollContainer = (ScrollContainer) p.getParent();
- if (mScrollContainerChild == null) {
- mScrollContainerChild = (View) p;
- }
- break;
- }
- p = p.getParent();
- }
- }
- }
-
public boolean isActive() {
return mEditText.isFocused() && mEditText.isEnabled();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java
index 722874b07c97..f258fb19ff7d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java
@@ -24,6 +24,7 @@ public interface RotationLockController extends Listenable,
boolean isRotationLockAffordanceVisible();
boolean isRotationLocked();
void setRotationLocked(boolean locked);
+ void setRotationLockedAtAngle(boolean locked, int rotation);
public interface RotationLockControllerCallback {
void onRotationLockStateChanged(boolean rotationLocked, boolean affordanceVisible);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
index 4f964964cd68..5418dc14e0bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
@@ -63,6 +63,10 @@ public final class RotationLockControllerImpl implements RotationLockController
RotationPolicy.setRotationLock(mContext, locked);
}
+ public void setRotationLockedAtAngle(boolean locked, int rotation){
+ RotationPolicy.setRotationLockAtAngle(mContext, locked, rotation);
+ }
+
public boolean isRotationLockAffordanceVisible() {
return RotationPolicy.isRotationLockToggleVisible(mContext);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
index 4cfd1c7fe6fd..91c208d571f5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
@@ -297,7 +297,7 @@ public abstract class SignalController<T extends SignalController.State,
.append("activityIn=").append(activityIn).append(',')
.append("activityOut=").append(activityOut).append(',')
.append("rssi=").append(rssi).append(',')
- .append("lastModified=").append(DateFormat.format("MM-dd hh:mm:ss", time));
+ .append("lastModified=").append(DateFormat.format("MM-dd HH:mm:ss", time));
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
new file mode 100644
index 000000000000..c5067a6578eb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.provider.Settings;
+import android.util.KeyValueListParser;
+import android.util.Log;
+
+import com.android.systemui.R;
+
+public final class SmartReplyConstants extends ContentObserver {
+
+ private static final String TAG = "SmartReplyConstants";
+
+ private static final String KEY_ENABLED = "enabled";
+ private static final String KEY_MAX_SQUEEZE_REMEASURE_ATTEMPTS =
+ "max_squeeze_remeasure_attempts";
+
+ private final boolean mDefaultEnabled;
+ private final int mDefaultMaxSqueezeRemeasureAttempts;
+
+ private boolean mEnabled;
+ private int mMaxSqueezeRemeasureAttempts;
+
+ private final Context mContext;
+ private final KeyValueListParser mParser = new KeyValueListParser(',');
+
+ public SmartReplyConstants(Handler handler, Context context) {
+ super(handler);
+
+ mContext = context;
+ final Resources resources = mContext.getResources();
+ mDefaultEnabled = resources.getBoolean(
+ R.bool.config_smart_replies_in_notifications_enabled);
+ mDefaultMaxSqueezeRemeasureAttempts = resources.getInteger(
+ R.integer.config_smart_replies_in_notifications_max_squeeze_remeasure_attempts);
+
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS),
+ false, this);
+ updateConstants();
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ updateConstants();
+ }
+
+ private void updateConstants() {
+ synchronized (SmartReplyConstants.this) {
+ try {
+ mParser.setString(Settings.Global.getString(mContext.getContentResolver(),
+ Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS));
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Bad smart reply constants", e);
+ }
+ mEnabled = mParser.getBoolean(KEY_ENABLED, mDefaultEnabled);
+ mMaxSqueezeRemeasureAttempts = mParser.getInt(
+ KEY_MAX_SQUEEZE_REMEASURE_ATTEMPTS, mDefaultMaxSqueezeRemeasureAttempts);
+ }
+ }
+
+ /** Returns whether smart replies in notifications are enabled. */
+ public boolean isEnabled() {
+ return mEnabled;
+ }
+
+ /**
+ * Returns the maximum number of times {@link SmartReplyView#onMeasure(int, int)} will try to
+ * find a better (narrower) line-break for a double-line smart reply button.
+ */
+ public int getMaxSqueezeRemeasureAttempts() {
+ return mMaxSqueezeRemeasureAttempts;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
new file mode 100644
index 000000000000..57fc03cb7308
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
@@ -0,0 +1,68 @@
+package com.android.systemui.statusbar.policy;
+
+import android.app.PendingIntent;
+import android.app.RemoteInput;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.LinearLayout;
+
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+
+/** View which displays smart reply buttons in notifications. */
+public class SmartReplyView extends LinearLayout {
+
+ private static final String TAG = "SmartReplyView";
+
+ private final SmartReplyConstants mConstants;
+
+ public SmartReplyView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mConstants = Dependency.get(SmartReplyConstants.class);
+ }
+
+ public void setRepliesFromRemoteInput(RemoteInput remoteInput, PendingIntent pendingIntent) {
+ removeAllViews();
+ if (remoteInput != null && pendingIntent != null) {
+ CharSequence[] choices = remoteInput.getChoices();
+ if (choices != null) {
+ for (CharSequence choice : choices) {
+ Button replyButton = inflateReplyButton(
+ getContext(), this, choice, remoteInput, pendingIntent);
+ addView(replyButton);
+ }
+ }
+ }
+ }
+
+ public static SmartReplyView inflate(Context context, ViewGroup root) {
+ return (SmartReplyView)
+ LayoutInflater.from(context).inflate(R.layout.smart_reply_view, root, false);
+ }
+
+ private static Button inflateReplyButton(Context context, ViewGroup root, CharSequence choice,
+ RemoteInput remoteInput, PendingIntent pendingIntent) {
+ Button b = (Button) LayoutInflater.from(context).inflate(
+ R.layout.smart_reply_button, root, false);
+ b.setText(choice);
+ b.setOnClickListener(view -> {
+ Bundle results = new Bundle();
+ results.putString(remoteInput.getResultKey(), choice.toString());
+ Intent intent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ RemoteInput.addResultsToIntent(new RemoteInput[]{remoteInput}, intent, results);
+ RemoteInput.setResultsSource(intent, RemoteInput.SOURCE_CHOICE);
+ try {
+ pendingIntent.send(context, 0, intent);
+ } catch (PendingIntent.CanceledException e) {
+ Log.w(TAG, "Unable to send smart reply", e);
+ }
+ });
+ return b;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TintedKeyButtonDrawable.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TintedKeyButtonDrawable.java
new file mode 100644
index 000000000000..acf9c00a111c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TintedKeyButtonDrawable.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import android.annotation.ColorInt;
+import android.graphics.drawable.Drawable;
+
+import com.android.internal.graphics.ColorUtils;
+
+/**
+ * Drawable for {@link KeyButtonView}s which contains a single asset and colors for light and dark
+ * navigation bar mode.
+ */
+public class TintedKeyButtonDrawable extends KeyButtonDrawable {
+
+ private final int mLightColor;
+ private final int mDarkColor;
+
+ public static TintedKeyButtonDrawable create(Drawable drawable, @ColorInt int lightColor,
+ @ColorInt int darkColor) {
+ return new TintedKeyButtonDrawable(new Drawable[] { drawable }, lightColor, darkColor);
+ }
+
+ private TintedKeyButtonDrawable(Drawable[] drawables, int lightColor, int darkColor){
+ super(drawables);
+ mLightColor = lightColor;
+ mDarkColor = darkColor;
+ }
+
+ @Override
+ public void setDarkIntensity(float intensity) {
+ // Duplicate intensity scaling from KeyButtonDrawable
+ int intermediateColor = ColorUtils.compositeColors(
+ setAlphaFloat(mDarkColor, intensity),
+ setAlphaFloat(mLightColor,1f - intensity));
+ getDrawable(0).setTint(intermediateColor);
+ invalidateSelf();
+ }
+
+ private int setAlphaFloat(int color, float alpha) {
+ return ColorUtils.setAlphaComponent(color, (int) (alpha * 255f));
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
index 527addf11da5..f5ae88b1788d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
@@ -154,7 +154,9 @@ public class UserInfoControllerImpl implements UserInfoController {
avatar = new UserIconDrawable(avatarSize)
.setIcon(rawAvatar).setBadgeIfManagedUser(mContext, userId).bake();
} else {
- avatar = UserIcons.getDefaultUserIcon(isGuest? UserHandle.USER_NULL : userId,
+ avatar = UserIcons.getDefaultUserIcon(
+ context.getResources(),
+ isGuest? UserHandle.USER_NULL : userId,
lightIcon);
}
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 700c01a55ad6..7006d389cc34 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -747,7 +747,8 @@ public class UserSwitcherController {
if (item.isAddUser) {
return context.getDrawable(R.drawable.ic_add_circle_qs);
}
- Drawable icon = UserIcons.getDefaultUserIcon(item.resolveId(), /* light= */ false);
+ Drawable icon = UserIcons.getDefaultUserIcon(
+ context.getResources(), item.resolveId(), /* light= */ false);
if (item.isGuest) {
icon.setColorFilter(Utils.getColorAttr(context, android.R.attr.colorForeground),
Mode.SRC_IN);
@@ -910,6 +911,7 @@ public class UserSwitcherController {
context.getString(android.R.string.cancel), this);
setButton(DialogInterface.BUTTON_POSITIVE,
context.getString(R.string.guest_exit_guest_dialog_remove), this);
+ SystemUIDialog.setWindowOnTop(this);
setCanceledOnTouchOutside(false);
mGuestId = guestId;
mTargetId = targetId;
@@ -937,6 +939,7 @@ public class UserSwitcherController {
context.getString(android.R.string.cancel), this);
setButton(DialogInterface.BUTTON_POSITIVE,
context.getString(android.R.string.ok), this);
+ SystemUIDialog.setWindowOnTop(this);
}
@Override
@@ -957,7 +960,7 @@ public class UserSwitcherController {
}
int id = user.id;
Bitmap icon = UserIcons.convertToBitmap(UserIcons.getDefaultUserIcon(
- id, /* light= */ false));
+ mContext.getResources(), id, /* light= */ false));
mUserManager.setUserIcon(id, icon);
switchToUserId(id);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
index 4d8da441c039..d7a810eca02e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.stack;
+import android.annotation.Nullable;
import android.content.Context;
import android.view.View;
@@ -63,10 +64,12 @@ public class AmbientState {
private boolean mPanelTracking;
private boolean mExpansionChanging;
private boolean mPanelFullWidth;
- private Collection<HeadsUpManager.HeadsUpEntry> mPulsing;
+ private boolean mPulsing;
private boolean mUnlockHintRunning;
private boolean mQsCustomizerShowing;
private int mIntrinsicPadding;
+ private int mExpandAnimationTopChange;
+ private ExpandableNotificationRow mExpandingNotification;
public AmbientState(Context context) {
reload(context);
@@ -76,9 +79,25 @@ public class AmbientState {
* Reload the dimens e.g. if the density changed.
*/
public void reload(Context context) {
- mZDistanceBetweenElements = Math.max(1, context.getResources()
+ mZDistanceBetweenElements = getZDistanceBetweenElements(context);
+ mBaseZHeight = getBaseHeight(mZDistanceBetweenElements);
+ }
+
+ private static int getZDistanceBetweenElements(Context context) {
+ return Math.max(1, context.getResources()
.getDimensionPixelSize(R.dimen.z_distance_between_notifications));
- mBaseZHeight = 4 * mZDistanceBetweenElements;
+ }
+
+ private static int getBaseHeight(int zdistanceBetweenElements) {
+ return 4 * zdistanceBetweenElements;
+ }
+
+ /**
+ * @return the launch height for notifications that are launched
+ */
+ public static int getNotificationLaunchHeight(Context context) {
+ int zDistance = getZDistanceBetweenElements(context);
+ return getBaseHeight(zDistance) * 2;
}
/**
@@ -201,7 +220,8 @@ public class AmbientState {
}
public int getInnerHeight() {
- return Math.max(Math.min(mLayoutHeight, mMaxLayoutHeight) - mTopPadding, mLayoutMinHeight);
+ return Math.max(Math.min(mLayoutHeight, mMaxLayoutHeight) - mTopPadding
+ - mExpandAnimationTopChange, mLayoutMinHeight);
}
public boolean isShadeExpanded() {
@@ -236,6 +256,7 @@ public class AmbientState {
mShelf = shelf;
}
+ @Nullable
public NotificationShelf getShelf() {
return mShelf;
}
@@ -294,23 +315,18 @@ public class AmbientState {
}
public boolean hasPulsingNotifications() {
- return mPulsing != null;
+ return mPulsing;
}
- public void setPulsing(Collection<HeadsUpManager.HeadsUpEntry> hasPulsing) {
+ public void setPulsing(boolean hasPulsing) {
mPulsing = hasPulsing;
}
public boolean isPulsing(NotificationData.Entry entry) {
- if (mPulsing == null) {
+ if (!mPulsing || mHeadsUpManager == null) {
return false;
}
- for (HeadsUpManager.HeadsUpEntry e : mPulsing) {
- if (e.entry == entry) {
- return true;
- }
- }
- return false;
+ return mHeadsUpManager.getAllEntries().anyMatch(e -> (e == entry));
}
public boolean isPanelTracking() {
@@ -378,4 +394,20 @@ public class AmbientState {
public boolean isDozingAndNotPulsing(ExpandableNotificationRow row) {
return isDark() && !isPulsing(row.getEntry());
}
+
+ public void setExpandAnimationTopChange(int expandAnimationTopChange) {
+ mExpandAnimationTopChange = expandAnimationTopChange;
+ }
+
+ public void setExpandingNotification(ExpandableNotificationRow row) {
+ mExpandingNotification = row;
+ }
+
+ public ExpandableNotificationRow getExpandingNotification() {
+ return mExpandingNotification;
+ }
+
+ public int getExpandAnimationTopChange() {
+ return mExpandAnimationTopChange;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationProperties.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationProperties.java
index ebb0a6d250cc..2f6e6584cc5e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationProperties.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationProperties.java
@@ -36,7 +36,12 @@ public class AnimationProperties {
* @return an animation filter for this animation.
*/
public AnimationFilter getAnimationFilter() {
- return new AnimationFilter();
+ return new AnimationFilter() {
+ @Override
+ public boolean shouldAnimateProperty(Property property) {
+ return true;
+ }
+ };
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ExpandableViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ExpandableViewState.java
index e0fd481c4cc2..3bf7d892ea0e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ExpandableViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ExpandableViewState.java
@@ -95,6 +95,12 @@ public class ExpandableViewState extends ViewState {
public boolean inShelf;
/**
+ * A state indicating whether a headsup is currently fully visible, even when not scrolled.
+ * Only valid if the view is heads upped.
+ */
+ public boolean headsUpIsVisible;
+
+ /**
* How much the child overlaps with the previous child on top. This is used to
* show the background properly when the child on top is translating away.
*/
@@ -126,6 +132,7 @@ public class ExpandableViewState extends ViewState {
clipTopAmount = svs.clipTopAmount;
notGoneIndex = svs.notGoneIndex;
location = svs.location;
+ headsUpIsVisible = svs.headsUpIsVisible;
}
}
@@ -175,6 +182,10 @@ public class ExpandableViewState extends ViewState {
expandableView.setTransformingInShelf(false);
expandableView.setInShelf(inShelf);
+
+ if (headsUpIsVisible) {
+ expandableView.setHeadsUpIsVisible();
+ }
}
}
@@ -229,6 +240,10 @@ public class ExpandableViewState extends ViewState {
expandableView.setTransformingInShelf(true);
}
expandableView.setInShelf(this.inShelf);
+
+ if (headsUpIsVisible) {
+ expandableView.setHeadsUpIsVisible();
+ }
}
private void startHeightAnimation(final ExpandableView child, AnimationProperties properties) {
@@ -452,4 +467,21 @@ public class ExpandableViewState extends ViewState {
return getChildTag(view, TAG_END_HEIGHT);
}
}
+
+ @Override
+ public void cancelAnimations(View view) {
+ super.cancelAnimations(view);
+ Animator animator = getChildTag(view, TAG_ANIMATOR_HEIGHT);
+ if (animator != null) {
+ animator.cancel();
+ }
+ animator = getChildTag(view, TAG_ANIMATOR_SHADOW_ALPHA);
+ if (animator != null) {
+ animator.cancel();
+ }
+ animator = getChildTag(view, TAG_ANIMATOR_TOP_INSET);
+ if (animator != null) {
+ animator.cancel();
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
index fe53104ff9c2..4ca33cd3f601 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
@@ -128,12 +128,11 @@ public class NotificationChildrenContainer extends ViewGroup {
mDividerHeight = res.getDimensionPixelSize(
R.dimen.notification_children_container_divider_height);
mDividerAlpha = res.getFloat(R.dimen.notification_divider_alpha);
- mHeaderHeight = res.getDimensionPixelSize(
- R.dimen.notification_children_container_header_height);
mNotificationHeaderMargin = res.getDimensionPixelSize(
R.dimen.notification_children_container_margin_top);
mNotificatonTopPadding = res.getDimensionPixelSize(
R.dimen.notification_children_container_top_padding);
+ mHeaderHeight = mNotificationHeaderMargin + mNotificatonTopPadding;
mCollapsedBottompadding = res.getDimensionPixelSize(
com.android.internal.R.dimen.notification_content_margin_bottom);
mEnableShadowOnChildNotifications =
@@ -395,7 +394,7 @@ public class NotificationChildrenContainer extends ViewGroup {
}
} else if (mOverflowNumber != null) {
removeView(mOverflowNumber);
- if (isShown()) {
+ if (isShown() && isAttachedToWindow()) {
final View removedOverflowNumber = mOverflowNumber;
addTransientView(removedOverflowNumber, getTransientViewCount());
CrossFadeHelper.fadeOut(removedOverflowNumber, new Runnable() {
@@ -1260,4 +1259,17 @@ public class NotificationChildrenContainer extends ViewGroup {
public boolean isUserLocked() {
return mUserLocked;
}
+
+ public void setCurrentBottomRoundness(float currentBottomRoundness) {
+ boolean last = true;
+ for (int i = mChildren.size() - 1; i >= 0; i--) {
+ ExpandableNotificationRow child = mChildren.get(i);
+ if (child.getVisibility() == View.GONE) {
+ continue;
+ }
+ float bottomRoundness = last ? currentBottomRoundness : 0.0f;
+ child.setBottomRoundness(bottomRoundness, isShown() /* animate */);
+ last = false;
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 75532d9e09be..1b55a5b0325f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.stack;
+import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
@@ -29,7 +31,9 @@ import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.Paint;
+import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
@@ -42,6 +46,7 @@ import android.support.annotation.VisibleForTesting;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.util.Log;
+import android.util.MathUtils;
import android.util.Pair;
import android.util.Property;
import android.view.ContextThemeWrapper;
@@ -78,6 +83,8 @@ import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.ExpandableView;
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.NotificationGuts;
+import com.android.systemui.statusbar.NotificationListContainer;
+import com.android.systemui.statusbar.NotificationLogger;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.NotificationSnooze;
import com.android.systemui.statusbar.StackScrollerDecorView;
@@ -85,10 +92,11 @@ import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.FakeShadowView;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.ScrimController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.policy.HeadsUpUtil;
import com.android.systemui.statusbar.policy.ScrollAdapter;
import android.support.v4.graphics.ColorUtils;
@@ -108,8 +116,8 @@ import java.util.List;
public class NotificationStackScrollLayout extends ViewGroup
implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter,
ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener,
- NotificationMenuRowPlugin.OnMenuEventListener, ScrollContainer,
- VisibilityLocationProvider {
+ NotificationMenuRowPlugin.OnMenuEventListener, VisibilityLocationProvider,
+ NotificationListContainer {
public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
private static final String TAG = "StackScroller";
@@ -127,6 +135,7 @@ public class NotificationStackScrollLayout extends ViewGroup
private boolean mSwipingInProgress;
private int mCurrentStackHeight = Integer.MAX_VALUE;
private final Paint mBackgroundPaint = new Paint();
+ private final Path mBackgroundPath = new Path();
private final boolean mShouldDrawNotificationBackground;
private float mExpandedHeight;
@@ -154,7 +163,12 @@ public class NotificationStackScrollLayout extends ViewGroup
private int mCollapsedSize;
private int mPaddingBetweenElements;
private int mIncreasedPaddingBetweenElements;
+ private int mRegularTopPadding;
+ private int mDarkTopPadding;
+ // Current padding, will be either mRegularTopPadding or mDarkTopPadding
private int mTopPadding;
+ // Distance between AOD separator and shelf
+ private int mDarkSeparatorPadding;
private int mBottomMargin;
private int mBottomInset = 0;
@@ -192,7 +206,7 @@ public class NotificationStackScrollLayout extends ViewGroup
* The raw amount of the overScroll on the bottom, which is not rubber-banded.
*/
private float mOverScrolledBottomPixels;
- private OnChildLocationsChangedListener mListener;
+ private NotificationLogger.OnChildLocationsChangedListener mListener;
private OnOverscrollTopChangedListener mOverscrollTopChangedListener;
private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
private OnEmptySpaceClickListener mOnEmptySpaceClickListener;
@@ -240,7 +254,7 @@ public class NotificationStackScrollLayout extends ViewGroup
* motion.
*/
private int mMaxScrollAfterExpand;
- private SwipeHelper.LongPressListener mLongPressListener;
+ private ExpandableNotificationRow.LongPressListener mLongPressListener;
private NotificationMenuRowPlugin mCurrMenuRow;
private View mTranslatingParentView;
@@ -275,7 +289,7 @@ public class NotificationStackScrollLayout extends ViewGroup
private HashSet<View> mClearOverlayViewsWhenFinished = new HashSet<>();
private HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations
= new HashSet<>();
- private HeadsUpManager mHeadsUpManager;
+ private HeadsUpManagerPhone mHeadsUpManager;
private boolean mTrackingHeadsUp;
private ScrimController mScrimController;
private boolean mForceNoOverlappingRendering;
@@ -345,24 +359,24 @@ public class NotificationStackScrollLayout extends ViewGroup
}
};
private PorterDuffXfermode mSrcMode = new PorterDuffXfermode(PorterDuff.Mode.SRC);
- private Collection<HeadsUpManager.HeadsUpEntry> mPulsing;
+ private boolean mPulsing;
private boolean mDrawBackgroundAsSrc;
private boolean mFadingOut;
private boolean mParentNotFullyVisible;
private boolean mGroupExpandedForMeasure;
private boolean mScrollable;
private View mForcedScroll;
- private float mBackgroundFadeAmount = 1.0f;
- private static final Property<NotificationStackScrollLayout, Float> BACKGROUND_FADE =
- new FloatProperty<NotificationStackScrollLayout>("backgroundFade") {
+ private float mDarkAmount = 0f;
+ private static final Property<NotificationStackScrollLayout, Float> DARK_AMOUNT =
+ new FloatProperty<NotificationStackScrollLayout>("darkAmount") {
@Override
public void setValue(NotificationStackScrollLayout object, float value) {
- object.setBackgroundFadeAmount(value);
+ object.setDarkAmount(value);
}
@Override
public Float get(NotificationStackScrollLayout object) {
- return object.getBackgroundFadeAmount();
+ return object.getDarkAmount();
}
};
private boolean mUsingLightTheme;
@@ -383,6 +397,13 @@ public class NotificationStackScrollLayout extends ViewGroup
private int mCachedBackgroundColor;
private boolean mHeadsUpGoingAwayAnimationsAllowed = true;
private Runnable mAnimateScroll = this::animateScroll;
+ private int mCornerRadius;
+ private int mSidePaddings;
+ private final int mSeparatorWidth;
+ private final int mSeparatorThickness;
+ private final Rect mTmpRect = new Rect();
+ private int mClockBottom;
+ private int mAntiBurnInOffsetX;
public NotificationStackScrollLayout(Context context) {
this(context, null);
@@ -410,7 +431,6 @@ public class NotificationStackScrollLayout extends ViewGroup
mExpandHelper.setEventSource(this);
mExpandHelper.setScrollAdapter(this);
mSwipeHelper = new NotificationSwipeHelper(SwipeHelper.X, this, getContext());
- mSwipeHelper.setLongPressListener(mLongPressListener);
mStackScrollAlgorithm = createStackScrollAlgorithm(context);
initView(context);
mFalsingManager = FalsingManager.getInstance(context);
@@ -418,8 +438,12 @@ public class NotificationStackScrollLayout extends ViewGroup
res.getBoolean(R.bool.config_drawNotificationBackground);
mFadeNotificationsOnDismiss =
res.getBoolean(R.bool.config_fadeNotificationsOnDismiss);
+ mSeparatorWidth = res.getDimensionPixelSize(R.dimen.widget_separator_width);
+ mSeparatorThickness = res.getDimensionPixelSize(R.dimen.widget_separator_thickness);
+ mDarkSeparatorPadding = res.getDimensionPixelSize(R.dimen.widget_bottom_separator_padding);
updateWillNotDraw();
+ mBackgroundPaint.setAntiAlias(true);
if (DEBUG) {
mDebugPaint = new Paint();
mDebugPaint.setColor(0xffff0000);
@@ -428,6 +452,7 @@ public class NotificationStackScrollLayout extends ViewGroup
}
}
+ @Override
public NotificationSwipeActionHelper getSwipeActionHelper() {
return mSwipeHelper;
}
@@ -465,10 +490,9 @@ public class NotificationStackScrollLayout extends ViewGroup
}
protected void onDraw(Canvas canvas) {
- if (mShouldDrawNotificationBackground && !mAmbientState.isDark()
- && mCurrentBounds.top < mCurrentBounds.bottom) {
- canvas.drawRect(0, mCurrentBounds.top, getWidth(), mCurrentBounds.bottom,
- mBackgroundPaint);
+ if (mShouldDrawNotificationBackground
+ && (mCurrentBounds.top < mCurrentBounds.bottom || mAmbientState.isDark())) {
+ drawBackground(canvas);
}
if (DEBUG) {
@@ -481,17 +505,57 @@ public class NotificationStackScrollLayout extends ViewGroup
}
}
+ private void drawBackground(Canvas canvas) {
+ final int lockScreenLeft = mSidePaddings;
+ final int lockScreenRight = getWidth() - mSidePaddings;
+ final int lockScreenTop = mCurrentBounds.top;
+ final int lockScreenBottom = mCurrentBounds.bottom;
+ final int darkLeft = getWidth() / 2 - mSeparatorWidth / 2;
+ final int darkRight = darkLeft + mSeparatorWidth;
+ final int darkTop = (int) (mRegularTopPadding + mSeparatorThickness / 2f);
+ final int darkBottom = darkTop + mSeparatorThickness;
+
+ if (mAmbientState.hasPulsingNotifications()) {
+ // TODO draw divider between notification and shelf
+ } else if (mAmbientState.isDark()) {
+ // Only draw divider on AOD if we actually have notifications
+ if (mFirstVisibleBackgroundChild != null) {
+ canvas.drawRect(darkLeft, darkTop, darkRight, darkBottom, mBackgroundPaint);
+ }
+ setClipBounds(null);
+ } else {
+ float animProgress = Interpolators.FAST_OUT_SLOW_IN
+ .getInterpolation(1f - mDarkAmount);
+ float sidePaddingsProgress = Interpolators.FAST_OUT_SLOW_IN
+ .getInterpolation((1f - mDarkAmount) * 2);
+ mTmpRect.set((int) MathUtils.lerp(darkLeft, lockScreenLeft, sidePaddingsProgress),
+ (int) MathUtils.lerp(darkTop, lockScreenTop, animProgress),
+ (int) MathUtils.lerp(darkRight, lockScreenRight, sidePaddingsProgress),
+ (int) MathUtils.lerp(darkBottom, lockScreenBottom, animProgress));
+ canvas.drawRoundRect(mTmpRect.left, mTmpRect.top, mTmpRect.right, mTmpRect.bottom,
+ mCornerRadius, mCornerRadius, mBackgroundPaint);
+ setClipBounds(animProgress == 1 ? null : mTmpRect);
+ }
+ }
+
private void updateBackgroundDimming() {
// No need to update the background color if it's not being drawn.
if (!mShouldDrawNotificationBackground) {
return;
}
- float alpha = BACKGROUND_ALPHA_DIMMED + (1 - BACKGROUND_ALPHA_DIMMED) * (1.0f - mDimAmount);
- alpha *= mBackgroundFadeAmount;
- // We need to manually blend in the background color
- int scrimColor = mScrimController.getBackgroundColor();
- int color = ColorUtils.blendARGB(scrimColor, mBgColor, alpha);
+ final int color;
+ if (mAmbientState.isDark()) {
+ color = Color.WHITE;
+ } else {
+ float alpha =
+ BACKGROUND_ALPHA_DIMMED + (1 - BACKGROUND_ALPHA_DIMMED) * (1.0f - mDimAmount);
+ alpha *= 1f - mDarkAmount;
+ // We need to manually blend in the background color
+ int scrimColor = mScrimController.getBackgroundColor();
+ color = ColorUtils.blendARGB(scrimColor, mBgColor, alpha);
+ }
+
if (mCachedBackgroundColor != color) {
mCachedBackgroundColor = color;
mBackgroundPaint.setColor(color);
@@ -521,8 +585,11 @@ public class NotificationStackScrollLayout extends ViewGroup
R.dimen.min_top_overscroll_to_qs);
mStatusBarHeight = res.getDimensionPixelOffset(R.dimen.status_bar_height);
mBottomMargin = res.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom);
+ mSidePaddings = res.getDimensionPixelSize(R.dimen.notification_side_paddings);
mMinInteractionHeight = res.getDimensionPixelSize(
R.dimen.notification_min_interaction_height);
+ mCornerRadius = res.getDimensionPixelSize(
+ Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius));
}
public void setDrawBackgroundAsSrc(boolean asSrc) {
@@ -549,11 +616,15 @@ public class NotificationStackScrollLayout extends ViewGroup
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ int childWidthSpec = MeasureSpec.makeMeasureSpec(width - mSidePaddings * 2,
+ MeasureSpec.getMode(widthMeasureSpec));
// We need to measure all children even the GONE ones, such that the heights are calculated
// correctly as they are used to calculate how many we can fit on the screen.
final int size = getChildCount();
for (int i = 0; i < size; i++) {
- measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec);
+ measureChild(getChildAt(i), childWidthSpec, heightMeasureSpec);
}
}
@@ -592,7 +663,9 @@ public class NotificationStackScrollLayout extends ViewGroup
mNoAmbient = noAmbient;
}
- public void setChildLocationsChangedListener(OnChildLocationsChangedListener listener) {
+ @Override
+ public void setChildLocationsChangedListener(
+ NotificationLogger.OnChildLocationsChangedListener listener) {
mListener = listener;
}
@@ -602,7 +675,7 @@ public class NotificationStackScrollLayout extends ViewGroup
if (childViewState == null) {
return false;
}
- if ((childViewState.location &= ExpandableViewState.VISIBLE_LOCATIONS) == 0) {
+ if ((childViewState.location & ExpandableViewState.VISIBLE_LOCATIONS) == 0) {
return false;
}
if (row.getVisibility() != View.VISIBLE) {
@@ -618,6 +691,11 @@ public class NotificationStackScrollLayout extends ViewGroup
}
private void updateAlgorithmHeightAndPadding() {
+ if (mPulsing) {
+ mTopPadding = mClockBottom;
+ } else {
+ mTopPadding = mAmbientState.isDark() ? mDarkTopPadding : mRegularTopPadding;
+ }
mAmbientState.setLayoutHeight(getLayoutHeight());
updateAlgorithmLayoutMinHeight();
mAmbientState.setTopPadding(mTopPadding);
@@ -647,11 +725,32 @@ public class NotificationStackScrollLayout extends ViewGroup
private void onPreDrawDuringAnimation() {
mShelf.updateAppearance();
+ updateClippingToTopRoundedCorner();
if (!mNeedsAnimation && !mChildrenUpdateRequested) {
updateBackground();
}
}
+ private void updateClippingToTopRoundedCorner() {
+ Float clipStart = (float) mTopPadding + mAmbientState.getExpandAnimationTopChange();
+ Float clipEnd = clipStart + mCornerRadius;
+ boolean first = true;
+ for (int i = 0; i < getChildCount(); i++) {
+ ExpandableView child = (ExpandableView) getChildAt(i);
+ if (child.getVisibility() == GONE) {
+ continue;
+ }
+ float start = child.getTranslationY();
+ float end = start + Math.max(child.getActualHeight() - child.getClipBottomAmount(),
+ 0);
+ boolean clip = clipStart > start && clipStart < end
+ || clipEnd >= start && clipEnd <= end;
+ clip &= !(first && mOwnScrollY == 0);
+ child.setDistanceToTopRoundness(clip ? Math.max(start - clipStart, 0) : -1);
+ first = false;
+ }
+ }
+
private void updateScrollStateForAddedChildren() {
if (mChildrenToAddAnimated.isEmpty()) {
return;
@@ -719,8 +818,9 @@ public class NotificationStackScrollLayout extends ViewGroup
}
private void setTopPadding(int topPadding, boolean animate) {
- if (mTopPadding != topPadding) {
- mTopPadding = topPadding;
+ if (mRegularTopPadding != topPadding) {
+ mRegularTopPadding = topPadding;
+ mDarkTopPadding = topPadding + mDarkSeparatorPadding;
updateAlgorithmHeightAndPadding();
updateContentHeight();
if (animate && mAnimationsEnabled && mIsExpanded) {
@@ -821,6 +921,27 @@ public class NotificationStackScrollLayout extends ViewGroup
}
/**
+ * @return the height of the top heads up notification when pinned. This is different from the
+ * intrinsic height, which also includes whether the notification is system expanded and
+ * is mainly used when dragging down from a heads up notification.
+ */
+ private int getTopHeadsUpPinnedHeight() {
+ NotificationData.Entry topEntry = mHeadsUpManager.getTopEntry();
+ if (topEntry == null) {
+ return 0;
+ }
+ ExpandableNotificationRow row = topEntry.row;
+ if (row.isChildInGroup()) {
+ final ExpandableNotificationRow groupSummary
+ = mGroupManager.getGroupSummary(row.getStatusBarNotification());
+ if (groupSummary != null) {
+ row = groupSummary;
+ }
+ }
+ return row.getPinnedHeadsUpHeight();
+ }
+
+ /**
* @return the position from where the appear transition ends when expanding.
* Measured in absolute height.
*/
@@ -831,7 +952,7 @@ public class NotificationStackScrollLayout extends ViewGroup
int minNotificationsForShelf = 1;
if (mTrackingHeadsUp
|| (mHeadsUpManager.hasPinnedHeadsUp() && !mAmbientState.isDark())) {
- appearPosition = mHeadsUpManager.getTopHeadsUpPinnedHeight();
+ appearPosition = getTopHeadsUpPinnedHeight();
minNotificationsForShelf = 2;
} else {
appearPosition = 0;
@@ -884,8 +1005,7 @@ public class NotificationStackScrollLayout extends ViewGroup
return firstChild != null ? firstChild.getMinHeight() : mCollapsedSize;
}
- public void setLongPressListener(SwipeHelper.LongPressListener listener) {
- mSwipeHelper.setLongPressListener(listener);
+ public void setLongPressListener(ExpandableNotificationRow.LongPressListener listener) {
mLongPressListener = listener;
}
@@ -924,7 +1044,9 @@ public class NotificationStackScrollLayout extends ViewGroup
mHeadsUpManager.addSwipedOutNotification(row.getStatusBarNotification().getKey());
}
}
- performDismiss(v, mGroupManager, false /* fromAccessibility */);
+ if (v instanceof ExpandableNotificationRow) {
+ ((ExpandableNotificationRow) v).performDismiss(false /* fromAccessibility */);
+ }
mFalsingManager.onNotificationDismissed();
if (mFalsingManager.shouldEnforceBouncer()) {
@@ -933,26 +1055,6 @@ public class NotificationStackScrollLayout extends ViewGroup
}
}
- public static void performDismiss(View v, NotificationGroupManager groupManager,
- boolean fromAccessibility) {
- if (!(v instanceof ExpandableNotificationRow)) {
- return;
- }
- ExpandableNotificationRow row = (ExpandableNotificationRow) v;
- if (groupManager.isOnlyChildInGroup(row.getStatusBarNotification())) {
- ExpandableNotificationRow groupSummary =
- groupManager.getLogicalGroupSummary(row.getStatusBarNotification());
- if (groupSummary.isClearable()) {
- performDismiss(groupSummary, groupManager, fromAccessibility);
- }
- }
- row.setDismissed(true, fromAccessibility);
- if (row.isClearable()) {
- row.performDismiss();
- }
- if (DEBUG) Log.v(TAG, "onChildDismissed: " + v);
- }
-
@Override
public void onChildSnappedBack(View animView, float targetLeft) {
mAmbientState.onDragFinished(animView);
@@ -1118,9 +1220,9 @@ public class NotificationStackScrollLayout extends ViewGroup
if (slidingChild instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild;
if (!mIsExpanded && row.isHeadsUp() && row.isPinned()
- && mHeadsUpManager.getTopEntry().entry.row != row
+ && mHeadsUpManager.getTopEntry().row != row
&& mGroupManager.getGroupSummary(
- mHeadsUpManager.getTopEntry().entry.row.getStatusBarNotification())
+ mHeadsUpManager.getTopEntry().row.getStatusBarNotification())
!= row) {
continue;
}
@@ -1175,7 +1277,7 @@ public class NotificationStackScrollLayout extends ViewGroup
if (v instanceof ExpandableNotificationRow) {
((ExpandableNotificationRow) v).setUserLocked(userLocked);
}
- removeLongPressCallback();
+ cancelLongPress();
requestDisallowInterceptTouchEvent(true);
}
@@ -1197,7 +1299,6 @@ public class NotificationStackScrollLayout extends ViewGroup
mScrollingEnabled = enable;
}
- @Override
public void lockScrollTo(View v) {
if (mForcedScroll == v) {
return;
@@ -1206,7 +1307,6 @@ public class NotificationStackScrollLayout extends ViewGroup
scrollTo(v);
}
- @Override
public boolean scrollTo(View v) {
ExpandableView expandableView = (ExpandableView) v;
int positionInLinearLayout = getPositionInLinearLayout(v);
@@ -1306,6 +1406,7 @@ public class NotificationStackScrollLayout extends ViewGroup
true /* isDismissAll */);
}
+ @Override
public void snapViewIfNeeded(ExpandableNotificationRow child) {
boolean animate = mIsExpanded || isPinnedHeadsUp(child);
// If the child is showing the notification menu snap to that
@@ -1314,6 +1415,11 @@ public class NotificationStackScrollLayout extends ViewGroup
}
@Override
+ public ViewGroup getViewParentForNotification(NotificationData.Entry entry) {
+ return this;
+ }
+
+ @Override
public boolean onTouchEvent(MotionEvent ev) {
boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL
|| ev.getActionMasked()== MotionEvent.ACTION_UP;
@@ -1345,7 +1451,7 @@ public class NotificationStackScrollLayout extends ViewGroup
}
// Check if we need to clear any snooze leavebehinds
- NotificationGuts guts = mStatusBar.getExposedGuts();
+ NotificationGuts guts = mStatusBar.getGutsManager().getExposedGuts();
if (guts != null && !isTouchInView(ev, guts)
&& guts.getGutsContent() instanceof NotificationSnooze) {
NotificationSnooze ns = (NotificationSnooze) guts.getGutsContent();
@@ -2034,8 +2140,9 @@ public class NotificationStackScrollLayout extends ViewGroup
return mAmbientState.isPulsing(entry);
}
+ @Override
public boolean hasPulsingNotifications() {
- return mPulsing != null;
+ return mPulsing;
}
private void updateScrollability() {
@@ -2220,8 +2327,9 @@ public class NotificationStackScrollLayout extends ViewGroup
return;
}
+ final boolean awake = mDarkAmount != 0 || mAmbientState.isDark();
mScrimController.setExcludedBackgroundArea(
- mFadingOut || mParentNotFullyVisible || mAmbientState.isDark() || mIsClipped ? null
+ mFadingOut || mParentNotFullyVisible || awake || mIsClipped ? null
: mCurrentBounds);
invalidate();
}
@@ -2238,6 +2346,8 @@ public class NotificationStackScrollLayout extends ViewGroup
mBackgroundBounds.left = mTempInt2[0];
mBackgroundBounds.right = mTempInt2[0] + getWidth();
}
+ mBackgroundBounds.left += mSidePaddings;
+ mBackgroundBounds.right -= mSidePaddings;
if (!mIsExpanded) {
mBackgroundBounds.top = 0;
mBackgroundBounds.bottom = 0;
@@ -2510,12 +2620,13 @@ public class NotificationStackScrollLayout extends ViewGroup
}
// Check if we need to clear any snooze leavebehinds
boolean isUp = ev.getActionMasked() == MotionEvent.ACTION_UP;
- NotificationGuts guts = mStatusBar.getExposedGuts();
+ NotificationGuts guts = mStatusBar.getGutsManager().getExposedGuts();
if (!isTouchInView(ev, guts) && isUp && !swipeWantsIt && !expandWantsIt
&& !scrollWantsIt) {
mCheckForLeavebehind = false;
- mStatusBar.closeAndSaveGuts(true /* removeLeavebehind */, false /* force */,
- false /* removeControls */, -1 /* x */, -1 /* y */, false /* resetMenu */);
+ mStatusBar.getGutsManager().closeAndSaveGuts(true /* removeLeavebehind */,
+ false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
+ false /* resetMenu */);
}
if (ev.getActionMasked() == MotionEvent.ACTION_UP) {
mCheckForLeavebehind = true;
@@ -2566,10 +2677,7 @@ public class NotificationStackScrollLayout extends ViewGroup
}
}
- /**
- * Called when a notification is removed from the shade. This cleans up the state for a given
- * view.
- */
+ @Override
public void cleanUpViewState(View child) {
if (child == mTranslatingParentView) {
mTranslatingParentView = null;
@@ -2581,7 +2689,7 @@ public class NotificationStackScrollLayout extends ViewGroup
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
super.requestDisallowInterceptTouchEvent(disallowIntercept);
if (disallowIntercept) {
- mSwipeHelper.removeLongPressCallback();
+ cancelLongPress();
}
}
@@ -2667,7 +2775,7 @@ public class NotificationStackScrollLayout extends ViewGroup
}
private boolean isClickedHeadsUp(View child) {
- return HeadsUpManager.isClickedHeadsUpNotification(child);
+ return HeadsUpUtil.isClickedHeadsUpNotification(child);
}
/**
@@ -2821,16 +2929,43 @@ public class NotificationStackScrollLayout extends ViewGroup
private void updateFirstAndLastBackgroundViews() {
ActivatableNotificationView firstChild = getFirstChildWithBackground();
ActivatableNotificationView lastChild = getLastChildWithBackground();
+ boolean firstChanged = firstChild != mFirstVisibleBackgroundChild;
+ boolean lastChanged = lastChild != mLastVisibleBackgroundChild;
if (mAnimationsEnabled && mIsExpanded) {
- mAnimateNextBackgroundTop = firstChild != mFirstVisibleBackgroundChild;
- mAnimateNextBackgroundBottom = lastChild != mLastVisibleBackgroundChild;
+ mAnimateNextBackgroundTop = firstChanged;
+ mAnimateNextBackgroundBottom = lastChanged;
} else {
mAnimateNextBackgroundTop = false;
mAnimateNextBackgroundBottom = false;
}
+ if (firstChanged && mFirstVisibleBackgroundChild != null
+ && !mFirstVisibleBackgroundChild.isRemoved()) {
+ mFirstVisibleBackgroundChild.setTopRoundness(0.0f,
+ mFirstVisibleBackgroundChild.isShown());
+ }
+ if (lastChanged && mLastVisibleBackgroundChild != null
+ && !mLastVisibleBackgroundChild.isRemoved()) {
+ mLastVisibleBackgroundChild.setBottomRoundness(0.0f,
+ mLastVisibleBackgroundChild.isShown());
+ }
mFirstVisibleBackgroundChild = firstChild;
mLastVisibleBackgroundChild = lastChild;
mAmbientState.setLastVisibleBackgroundChild(lastChild);
+ applyRoundedNess();
+ }
+
+ private void applyRoundedNess() {
+ if (mFirstVisibleBackgroundChild != null) {
+ mFirstVisibleBackgroundChild.setTopRoundness(1.0f,
+ mFirstVisibleBackgroundChild.isShown()
+ && !mChildrenToAddAnimated.contains(mFirstVisibleBackgroundChild));
+ }
+ if (mLastVisibleBackgroundChild != null) {
+ mLastVisibleBackgroundChild.setBottomRoundness(1.0f,
+ mLastVisibleBackgroundChild.isShown()
+ && !mChildrenToAddAnimated.contains(mLastVisibleBackgroundChild));
+ }
+ invalidate();
}
private void onViewAddedInternal(View child) {
@@ -2848,10 +2983,12 @@ public class NotificationStackScrollLayout extends ViewGroup
}
}
+ @Override
public void notifyGroupChildRemoved(View row, ViewGroup childrenContainer) {
onViewRemovedInternal(row, childrenContainer);
}
+ @Override
public void notifyGroupChildAdded(View row) {
onViewAddedInternal(row);
}
@@ -2877,6 +3014,17 @@ public class NotificationStackScrollLayout extends ViewGroup
&& (mIsExpanded || isPinnedHeadsUp(child)), child);
}
+ @Override
+ public void setExpandingNotification(ExpandableNotificationRow row) {
+ mAmbientState.setExpandingNotification(row);
+ requestChildrenUpdate();
+ }
+
+ @Override
+ public void applyExpandAnimationParams(ExpandAnimationParameters params) {
+ mAmbientState.setExpandAnimationTopChange(params == null ? 0 : params.getTopChange());
+ requestChildrenUpdate();
+ }
private void updateAnimationState(boolean running, View child) {
if (child instanceof ExpandableNotificationRow) {
@@ -2889,12 +3037,8 @@ public class NotificationStackScrollLayout extends ViewGroup
return mNeedsAnimation
&& (!mChildrenToAddAnimated.isEmpty() || !mChildrenToRemoveAnimated.isEmpty());
}
- /**
- * Generate an animation for an added child view.
- *
- * @param child The view to be added.
- * @param fromMoreCard Whether this add is coming from the "more" card on lockscreen.
- */
+
+ @Override
public void generateAddAnimation(View child, boolean fromMoreCard) {
if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress) {
// Generate Animations
@@ -2910,12 +3054,7 @@ public class NotificationStackScrollLayout extends ViewGroup
}
}
- /**
- * Change the position of child to a new location
- *
- * @param child the view to change the position for
- * @param newIndex the new index
- */
+ @Override
public void changeViewPosition(View child, int newIndex) {
int currentIndex = indexOfChild(child);
if (child != null && child.getParent() == this && currentIndex != newIndex) {
@@ -2944,6 +3083,7 @@ public class NotificationStackScrollLayout extends ViewGroup
mAnimationEvents.clear();
updateBackground();
updateViewShadows();
+ updateClippingToTopRoundedCorner();
} else {
applyCurrentState();
}
@@ -3067,7 +3207,7 @@ public class NotificationStackScrollLayout extends ViewGroup
}
if (!childWasSwipedOut) {
Rect clipBounds = child.getClipBounds();
- childWasSwipedOut = clipBounds.height() == 0;
+ childWasSwipedOut = clipBounds != null && clipBounds.height() == 0;
}
int animationType = childWasSwipedOut
? AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT
@@ -3302,7 +3442,7 @@ public class NotificationStackScrollLayout extends ViewGroup
mIsBeingDragged = isDragged;
if (isDragged) {
requestDisallowInterceptTouchEvent(true);
- removeLongPressCallback();
+ cancelLongPress();
}
}
@@ -3310,7 +3450,7 @@ public class NotificationStackScrollLayout extends ViewGroup
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
if (!hasWindowFocus) {
- removeLongPressCallback();
+ cancelLongPress();
}
}
@@ -3322,18 +3462,16 @@ public class NotificationStackScrollLayout extends ViewGroup
}
}
- @Override
public void requestDisallowLongPress() {
- removeLongPressCallback();
+ cancelLongPress();
}
- @Override
public void requestDisallowDismiss() {
mDisallowDismissInThisMotion = true;
}
- public void removeLongPressCallback() {
- mSwipeHelper.removeLongPressCallback();
+ public void cancelLongPress() {
+ mSwipeHelper.cancelLongPress();
}
@Override
@@ -3357,8 +3495,9 @@ public class NotificationStackScrollLayout extends ViewGroup
public void checkSnoozeLeavebehind() {
if (mCheckForLeavebehind) {
- mStatusBar.closeAndSaveGuts(true /* removeLeavebehind */, false /* force */,
- false /* removeControls */, -1 /* x */, -1 /* y */, false /* resetMenu */);
+ mStatusBar.getGutsManager().closeAndSaveGuts(true /* removeLeavebehind */,
+ false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
+ false /* resetMenu */);
mCheckForLeavebehind = false;
}
}
@@ -3608,6 +3747,7 @@ public class NotificationStackScrollLayout extends ViewGroup
mHideSensitiveNeedsAnimation = true;
mNeedsAnimation = true;
}
+ updateContentHeight();
requestChildrenUpdate();
}
}
@@ -3631,12 +3771,13 @@ public class NotificationStackScrollLayout extends ViewGroup
private void applyCurrentState() {
mCurrentStackScrollState.apply();
if (mListener != null) {
- mListener.onChildLocationsChanged(this);
+ mListener.onChildLocationsChanged();
}
runAnimationFinishedRunnables();
setAnimationRunning(false);
updateBackground();
updateViewShadows();
+ updateClippingToTopRoundedCorner();
}
private void updateViewShadows() {
@@ -3741,43 +3882,43 @@ public class NotificationStackScrollLayout extends ViewGroup
mDarkNeedsAnimation = true;
mDarkAnimationOriginIndex = findDarkAnimationOriginIndex(touchWakeUpScreenLocation);
mNeedsAnimation = true;
- setBackgroundFadeAmount(0.0f);
- } else if (!dark) {
- setBackgroundFadeAmount(1.0f);
- }
- requestChildrenUpdate();
- if (dark) {
- mScrimController.setExcludedBackgroundArea(null);
} else {
+ setDarkAmount(dark ? 1f : 0f);
updateBackground();
}
-
+ requestChildrenUpdate();
+ applyCurrentBackgroundBounds();
updateWillNotDraw();
updateContentHeight();
+ updateAntiBurnInTranslation();
notifyHeightChangeListener(mShelf);
}
+ private void updateAntiBurnInTranslation() {
+ setTranslationX(mAmbientState.isDark() ? mAntiBurnInOffsetX : 0);
+ }
+
/**
* Updates whether or not this Layout will perform its own custom drawing (i.e. whether or
* not {@link #onDraw(Canvas)} is called). This method should be called whenever the
* {@link #mAmbientState}'s dark mode is toggled.
*/
private void updateWillNotDraw() {
- boolean willDraw = !mAmbientState.isDark() && mShouldDrawNotificationBackground || DEBUG;
+ boolean willDraw = mShouldDrawNotificationBackground || DEBUG;
setWillNotDraw(!willDraw);
}
- private void setBackgroundFadeAmount(float fadeAmount) {
- mBackgroundFadeAmount = fadeAmount;
+ private void setDarkAmount(float darkAmount) {
+ mDarkAmount = darkAmount;
updateBackgroundDimming();
}
- public float getBackgroundFadeAmount() {
- return mBackgroundFadeAmount;
+ public float getDarkAmount() {
+ return mDarkAmount;
}
private void startBackgroundFadeIn() {
- ObjectAnimator fadeAnimator = ObjectAnimator.ofFloat(this, BACKGROUND_FADE, 0f, 1f);
+ ObjectAnimator fadeAnimator = ObjectAnimator.ofFloat(this, DARK_AMOUNT, mDarkAmount, 0f);
fadeAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_WAKEUP);
fadeAnimator.setInterpolator(Interpolators.ALPHA_IN);
fadeAnimator.start();
@@ -4115,11 +4256,31 @@ public class NotificationStackScrollLayout extends ViewGroup
}
}
+ @Override
+ public int getContainerChildCount() {
+ return getChildCount();
+ }
+
+ @Override
+ public View getContainerChildAt(int i) {
+ return getChildAt(i);
+ }
+
+ @Override
+ public void removeContainerView(View v) {
+ removeView(v);
+ }
+
+ @Override
+ public void addContainerView(View v) {
+ addView(v);
+ }
+
public void runAfterAnimationFinished(Runnable runnable) {
mAnimationFinishedRunnables.add(runnable);
}
- public void setHeadsUpManager(HeadsUpManager headsUpManager) {
+ public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
mHeadsUpManager = headsUpManager;
mAmbientState.setHeadsUpManager(headsUpManager);
}
@@ -4187,13 +4348,15 @@ public class NotificationStackScrollLayout extends ViewGroup
return mIsExpanded;
}
- public void setPulsing(Collection<HeadsUpManager.HeadsUpEntry> pulsing) {
- if (mPulsing == null && pulsing == null) {
+ public void setPulsing(boolean pulsing, int clockBottom) {
+ if (!mPulsing && !pulsing) {
return;
}
mPulsing = pulsing;
+ mClockBottom = clockBottom;
mAmbientState.setPulsing(pulsing);
updateNotificationAnimationStates();
+ updateAlgorithmHeightAndPadding();
updateContentHeight();
notifyHeightChangeListener(mShelf);
requestChildrenUpdate();
@@ -4316,15 +4479,16 @@ public class NotificationStackScrollLayout extends ViewGroup
mHeadsUpGoingAwayAnimationsAllowed = headsUpGoingAwayAnimationsAllowed;
}
- public void setDarkShelfOffsetX(int shelfOffsetX) {
- mShelf.setDarkOffsetX(shelfOffsetX);
+ public void setAntiBurnInOffsetX(int antiBurnInOffsetX) {
+ mAntiBurnInOffsetX = antiBurnInOffsetX;
+ updateAntiBurnInTranslation();
}
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println(String.format("[%s: pulsing=%s qsCustomizerShowing=%s visibility=%s"
+ " alpha:%f scrollY:%d]",
this.getClass().getSimpleName(),
- mPulsing != null ?"T":"f",
+ mPulsing ? "T":"f",
mAmbientState.isQsCustomizerShowing() ? "T":"f",
getVisibility() == View.VISIBLE ? "visible"
: getVisibility() == View.GONE ? "gone"
@@ -4333,17 +4497,6 @@ public class NotificationStackScrollLayout extends ViewGroup
mAmbientState.getScrollY()));
}
- public void setTouchActive(boolean touchActive) {
- mShelf.setTouchActive(touchActive);
- }
-
- /**
- * A listener that is notified when some child locations might have changed.
- */
- public interface OnChildLocationsChangedListener {
- void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout);
- }
-
/**
* A listener that is notified when the empty space below the notifications is clicked on
*/
@@ -4440,8 +4593,9 @@ public class NotificationStackScrollLayout extends ViewGroup
// of the panel early.
handleChildDismissed(view);
}
- mStatusBar.closeAndSaveGuts(true /* removeLeavebehind */, false /* force */,
- false /* removeControls */, -1 /* x */, -1 /* y */, false /* resetMenu */);
+ mStatusBar.getGutsManager().closeAndSaveGuts(true /* removeLeavebehind */,
+ false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
+ false /* resetMenu */);
handleMenuCoveredOrDismissed();
}
@@ -4526,7 +4680,7 @@ public class NotificationStackScrollLayout extends ViewGroup
}
public void closeControlsIfOutsideTouch(MotionEvent ev) {
- NotificationGuts guts = mStatusBar.getExposedGuts();
+ NotificationGuts guts = mStatusBar.getGutsManager().getExposedGuts();
View view = null;
if (guts != null && !guts.getGutsContent().isLeavebehind()) {
// Only close visible guts if they're not a leavebehind.
@@ -4538,8 +4692,9 @@ public class NotificationStackScrollLayout extends ViewGroup
}
if (view != null && !isTouchInView(ev, view)) {
// Touch was outside visible guts / menu notification, close what's visible
- mStatusBar.closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
- true /* removeControls */, -1 /* x */, -1 /* y */, false /* resetMenu */);
+ mStatusBar.getGutsManager().closeAndSaveGuts(false /* removeLeavebehind */,
+ false /* force */, true /* removeControls */, -1 /* x */, -1 /* y */,
+ false /* resetMenu */);
resetExposedMenuView(true /* animate */, true /* force */);
}
}
@@ -4597,6 +4752,7 @@ public class NotificationStackScrollLayout extends ViewGroup
}
}
+ @Override
public void resetExposedMenuView(boolean animate, boolean force) {
mSwipeHelper.resetExposedMenuView(animate, force);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ScrollContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ScrollContainer.java
deleted file mode 100644
index b9d12ce8f151..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ScrollContainer.java
+++ /dev/null
@@ -1,48 +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.statusbar.stack;
-
-import android.view.View;
-
-/**
- * Interface for container layouts that scroll and listen for long presses. A child that
- * wants to handle long press can use this to cancel the parents long press logic or request
- * to be made visible by scrolling to it.
- */
-public interface ScrollContainer {
- /**
- * Request that the view does not perform long press for the current touch.
- */
- void requestDisallowLongPress();
-
- /**
- * Request that the view is made visible by scrolling to it.
- * Return true if it scrolls.
- */
- boolean scrollTo(View v);
-
- /**
- * Like {@link #scrollTo(View)}, but keeps the scroll locked until the user
- * scrolls, or {@param v} loses focus or is detached.
- */
- void lockScrollTo(View v);
-
- /**
- * Request that the view does not dismiss for the current touch.
- */
- void requestDisallowDismiss();
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index c060b0882060..d68a7b1dc205 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -122,20 +122,23 @@ public class StackScrollAlgorithm {
}
private void updateShelfState(StackScrollState resultState, AmbientState ambientState) {
NotificationShelf shelf = ambientState.getShelf();
- shelf.updateState(resultState, ambientState);
+ if (shelf != null) {
+ shelf.updateState(resultState, ambientState);
+ }
}
private void updateClipping(StackScrollState resultState,
StackScrollAlgorithmState algorithmState, AmbientState ambientState) {
float drawStart = !ambientState.isOnKeyguard() ? ambientState.getTopPadding()
- + ambientState.getStackTranslation() : 0;
+ + ambientState.getStackTranslation() + ambientState.getExpandAnimationTopChange()
+ : 0;
float previousNotificationEnd = 0;
float previousNotificationStart = 0;
int childCount = algorithmState.visibleChildren.size();
for (int i = 0; i < childCount; i++) {
ExpandableView child = algorithmState.visibleChildren.get(i);
ExpandableViewState state = resultState.getViewStateForView(child);
- if (!child.mustStayOnScreen()) {
+ if (!child.mustStayOnScreen() || state.headsUpIsVisible) {
previousNotificationEnd = Math.max(drawStart, previousNotificationEnd);
previousNotificationStart = Math.max(drawStart, previousNotificationStart);
}
@@ -318,6 +321,10 @@ public class StackScrollAlgorithm {
lastView = v;
}
}
+ ExpandableNotificationRow expandingNotification = ambientState.getExpandingNotification();
+ state.indexOfExpandingNotification = expandingNotification != null
+ ? state.visibleChildren.indexOf(expandingNotification)
+ : -1;
}
private float getPaddingForValue(Float increasedPadding) {
@@ -378,6 +385,16 @@ public class StackScrollAlgorithm {
boolean isEmptyShadeView = child instanceof EmptyShadeView;
childViewState.location = ExpandableViewState.LOCATION_MAIN_AREA;
+ float inset = ambientState.getTopPadding() + ambientState.getStackTranslation();
+ if (i < algorithmState.getIndexOfExpandingNotification()) {
+ inset += ambientState.getExpandAnimationTopChange();
+ }
+ if (child.mustStayOnScreen() && childViewState.yTranslation >= 0) {
+ // Even if we're not scrolled away we're in view and we're also not in the
+ // shelf. We can relax the constraints and let us scroll off the top!
+ float end = childViewState.yTranslation + childViewState.height + inset;
+ childViewState.headsUpIsVisible = end < ambientState.getMaxHeadsUpTranslation();
+ }
if (isDismissView) {
childViewState.yTranslation = Math.min(childViewState.yTranslation,
ambientState.getInnerHeight() - childHeight);
@@ -385,7 +402,7 @@ public class StackScrollAlgorithm {
childViewState.yTranslation = ambientState.getInnerHeight() - childHeight
+ ambientState.getStackTranslation() * 0.25f;
} else {
- clampPositionToShelf(childViewState, ambientState);
+ clampPositionToShelf(child, childViewState, ambientState);
}
currentYPosition = childViewState.yTranslation + childHeight + paddingAfterChild;
@@ -396,8 +413,7 @@ public class StackScrollAlgorithm {
Log.wtf(LOG_TAG, "Failed to assign location for child " + i);
}
- childViewState.yTranslation += ambientState.getTopPadding()
- + ambientState.getStackTranslation();
+ childViewState.yTranslation += inset;
return currentYPosition;
}
@@ -420,19 +436,21 @@ public class StackScrollAlgorithm {
break;
}
ExpandableViewState childState = resultState.getViewStateForView(row);
- if (topHeadsUpEntry == null) {
+ if (topHeadsUpEntry == null && row.mustStayOnScreen() && !childState.headsUpIsVisible) {
topHeadsUpEntry = row;
childState.location = ExpandableViewState.LOCATION_FIRST_HUN;
}
boolean isTopEntry = topHeadsUpEntry == row;
float unmodifiedEndLocation = childState.yTranslation + childState.height;
if (mIsExpanded) {
- // Ensure that the heads up is always visible even when scrolled off
- clampHunToTop(ambientState, row, childState);
- if (i == 0 && ambientState.isAboveShelf(row)) {
- // the first hun can't get off screen.
- clampHunToMaxTranslation(ambientState, row, childState);
- childState.hidden = false;
+ if (row.mustStayOnScreen() && !childState.headsUpIsVisible) {
+ // Ensure that the heads up is always visible even when scrolled off
+ clampHunToTop(ambientState, row, childState);
+ if (i == 0 && ambientState.isAboveShelf(row)) {
+ // the first hun can't get off screen.
+ clampHunToMaxTranslation(ambientState, row, childState);
+ childState.hidden = false;
+ }
}
}
if (row.isPinned()) {
@@ -440,7 +458,7 @@ public class StackScrollAlgorithm {
childState.height = Math.max(row.getIntrinsicHeight(), childState.height);
childState.hidden = false;
ExpandableViewState topState = resultState.getViewStateForView(topHeadsUpEntry);
- if (!isTopEntry && (!mIsExpanded
+ if (topState != null && !isTopEntry && (!mIsExpanded
|| unmodifiedEndLocation < topState.yTranslation + topState.height)) {
// Ensure that a headsUp doesn't vertically extend further than the heads-up at
// the top most z-position
@@ -482,17 +500,24 @@ public class StackScrollAlgorithm {
* Clamp the height of the child down such that its end is at most on the beginning of
* the shelf.
*
+ * @param child
* @param childViewState the view state of the child
* @param ambientState the ambient state
*/
- private void clampPositionToShelf(ExpandableViewState childViewState,
+ private void clampPositionToShelf(ExpandableView child,
+ ExpandableViewState childViewState,
AmbientState ambientState) {
+ if (ambientState.getShelf() == null) {
+ return;
+ }
+
int shelfStart = ambientState.getInnerHeight()
- ambientState.getShelf().getIntrinsicHeight();
childViewState.yTranslation = Math.min(childViewState.yTranslation, shelfStart);
if (childViewState.yTranslation >= shelfStart) {
- childViewState.hidden = true;
+ childViewState.hidden = !child.isExpandAnimationRunning();
childViewState.inShelf = true;
+ childViewState.headsUpIsVisible = false;
}
if (!ambientState.isShadeExpanded()) {
childViewState.height = (int) (mStatusBarHeight - childViewState.yTranslation);
@@ -531,7 +556,8 @@ public class StackScrollAlgorithm {
ExpandableViewState childViewState = resultState.getViewStateForView(child);
int zDistanceBetweenElements = ambientState.getZDistanceBetweenElements();
float baseZ = ambientState.getBaseZHeight();
- if (child.mustStayOnScreen() && !ambientState.isDozingAndNotPulsing(child)
+ if (child.mustStayOnScreen() && !childViewState.headsUpIsVisible
+ && !ambientState.isDozingAndNotPulsing(child)
&& childViewState.yTranslation < ambientState.getTopPadding()
+ ambientState.getStackTranslation()) {
if (childrenOnTop != 0.0f) {
@@ -546,7 +572,8 @@ public class StackScrollAlgorithm {
} else if (i == 0 && ambientState.isAboveShelf(child)) {
// 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().getIntrinsicHeight();
+ int shelfHeight = ambientState.getShelf() == null ? 0 :
+ ambientState.getShelf().getIntrinsicHeight();
float shelfStart = ambientState.getInnerHeight()
- shelfHeight + ambientState.getTopPadding()
+ ambientState.getStackTranslation();
@@ -585,6 +612,7 @@ public class StackScrollAlgorithm {
* The padding after each child measured in pixels.
*/
public final HashMap<ExpandableView, Float> paddingMap = new HashMap<>();
+ private int indexOfExpandingNotification;
public int getPaddingAfterChild(ExpandableView child) {
Float padding = paddingMap.get(child);
@@ -594,6 +622,10 @@ public class StackScrollAlgorithm {
}
return (int) padding.floatValue();
}
+
+ public int getIndexOfExpandingNotification() {
+ return indexOfExpandingNotification;
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
index e3c746bf0d32..588b75881a4a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
@@ -84,6 +84,7 @@ public class StackScrollState {
viewState.scaleX = view.getScaleX();
viewState.scaleY = view.getScaleY();
viewState.inShelf = false;
+ viewState.headsUpIsVisible = false;
}
public ExpandableViewState getViewStateForView(View requestedView) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
index f78a718eff9f..236c348e539b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
@@ -40,7 +40,7 @@ import java.util.Stack;
public class StackStateAnimator {
public static final int ANIMATION_DURATION_STANDARD = 360;
- public static final int ANIMATION_DURATION_WAKEUP = 200;
+ public static final int ANIMATION_DURATION_WAKEUP = 500;
public static final int ANIMATION_DURATION_GO_TO_FULL_SHADE = 448;
public static final int ANIMATION_DURATION_APPEAR_DISAPPEAR = 464;
public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
index 27b730cdb6c7..04a7bd79c6ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
@@ -21,7 +21,6 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
-import android.app.Notification;
import android.util.Property;
import android.view.View;
import android.view.animation.Interpolator;
@@ -29,9 +28,9 @@ import android.view.animation.Interpolator;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.statusbar.ExpandableView;
-import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.policy.HeadsUpUtil;
/**
* A state of a view. This can be used to apply a set of view properties to a view with
@@ -64,8 +63,8 @@ public class ViewState {
private static final int TAG_START_TRANSLATION_Z = R.id.translation_z_animator_start_value_tag;
private static final int TAG_START_ALPHA = R.id.alpha_animator_start_value_tag;
- private static final PropertyAnimator.AnimatableProperty SCALE_X_PROPERTY
- = new PropertyAnimator.AnimatableProperty() {
+ private static final AnimatableProperty SCALE_X_PROPERTY
+ = new AnimatableProperty() {
@Override
public int getAnimationStartTag() {
@@ -88,8 +87,8 @@ public class ViewState {
}
};
- private static final PropertyAnimator.AnimatableProperty SCALE_Y_PROPERTY
- = new PropertyAnimator.AnimatableProperty() {
+ private static final AnimatableProperty SCALE_Y_PROPERTY
+ = new AnimatableProperty() {
@Override
public int getAnimationStartTag() {
@@ -251,7 +250,7 @@ public class ViewState {
return getChildTag(view, tag) != null;
}
- public static boolean isAnimating(View view, PropertyAnimator.AnimatableProperty property) {
+ public static boolean isAnimating(View view, AnimatableProperty property) {
return getChildTag(view, property.getAnimatorTag()) != null;
}
@@ -403,7 +402,7 @@ public class ViewState {
startZTranslationAnimation(view, NO_NEW_ANIMATIONS);
}
- private void updateAnimation(View view, PropertyAnimator.AnimatableProperty property,
+ private void updateAnimation(View view, AnimatableProperty property,
float endValue) {
PropertyAnimator.startAnimation(view, property, endValue, NO_NEW_ANIMATIONS);
}
@@ -583,7 +582,7 @@ public class ViewState {
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- HeadsUpManager.setIsClickedNotification(child, false);
+ HeadsUpUtil.setIsClickedHeadsUpNotification(child, false);
child.setTag(TAG_ANIMATOR_TRANSLATION_Y, null);
child.setTag(TAG_START_TRANSLATION_Y, null);
child.setTag(TAG_END_TRANSLATION_Y, null);
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java b/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java
index 45abd456ea16..eb0c89b06528 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java
@@ -18,7 +18,7 @@ import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.KEY
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.KEY_CODE_END;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.KEY_CODE_START;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.KEY_IMAGE_DELIM;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.MENU_IME;
+import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.MENU_IME_ROTATE;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.NAVSPACE;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.NAV_BAR_LEFT;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.NAV_BAR_RIGHT;
@@ -29,24 +29,14 @@ import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.ext
import android.annotation.Nullable;
import android.app.AlertDialog;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnClickListener;
-import android.content.res.Resources;
import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Paint.FontMetricsInt;
-import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.os.Handler;
-import android.support.v14.preference.PreferenceFragment;
-import android.support.v7.preference.DropDownPreference;
import android.support.v7.preference.ListPreference;
import android.support.v7.preference.Preference;
import android.support.v7.preference.Preference.OnPreferenceChangeListener;
-import android.support.v7.preference.Preference.OnPreferenceClickListener;
-import android.support.v7.preference.PreferenceCategory;
import android.text.SpannableStringBuilder;
import android.text.style.ImageSpan;
import android.util.Log;
@@ -56,7 +46,6 @@ import android.widget.EditText;
import com.android.systemui.Dependency;
import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.NavigationBarInflaterView;
import com.android.systemui.tuner.TunerService.Tunable;
import java.util.ArrayList;
@@ -100,7 +89,7 @@ public class NavBarTuner extends TunerPreferenceFragment {
addPreferencesFromResource(R.xml.nav_bar_tuner);
bindLayout((ListPreference) findPreference(LAYOUT));
bindButton(NAV_BAR_LEFT, NAVSPACE, LEFT);
- bindButton(NAV_BAR_RIGHT, MENU_IME, RIGHT);
+ bindButton(NAV_BAR_RIGHT, MENU_IME_ROTATE, RIGHT);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java
index f91e45d05e79..27bf534c290e 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java
@@ -169,16 +169,23 @@ public class PluginFragment extends PreferenceFragment {
protected boolean persistBoolean(boolean value) {
final int desiredState = value ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
: PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+ boolean shouldSendBroadcast = false;
for (int i = 0; i < mInfo.services.length; i++) {
ComponentName componentName = new ComponentName(mInfo.packageName,
mInfo.services[i].name);
- mPm.setComponentEnabledSetting(componentName, desiredState,
- PackageManager.DONT_KILL_APP);
+
+ if (mPm.getComponentEnabledSetting(componentName) != desiredState) {
+ mPm.setComponentEnabledSetting(componentName, desiredState,
+ PackageManager.DONT_KILL_APP);
+ shouldSendBroadcast = true;
+ }
+ }
+ if (shouldSendBroadcast) {
+ final String pkg = mInfo.packageName;
+ final Intent intent = new Intent(PluginManager.PLUGIN_CHANGED,
+ pkg != null ? Uri.fromParts("package", pkg, null) : null);
+ getContext().sendBroadcast(intent);
}
- final String pkg = mInfo.packageName;
- final Intent intent = new Intent(PluginManager.PLUGIN_CHANGED,
- pkg != null ? Uri.fromParts("package", pkg, null) : null);
- getContext().sendBroadcast(intent);
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
index 8e584bc362eb..5a4478f072e0 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
@@ -58,7 +58,7 @@ public class TunerServiceImpl extends TunerService {
private static final String TUNER_VERSION = "sysui_tuner_version";
- private static final int CURRENT_TUNER_VERSION = 1;
+ private static final int CURRENT_TUNER_VERSION = 2;
private final Observer mObserver = new Observer();
// Map of Uris we listen on to their settings keys.
@@ -116,6 +116,9 @@ public class TunerServiceImpl extends TunerService {
TextUtils.join(",", iconBlacklist), mCurrentUser);
}
}
+ if (oldVersion < 2) {
+ setTunerEnabled(mContext, false);
+ }
setValue(TUNER_VERSION, newVersion);
}
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java
index 3eccccdd75bf..0a3e34ee951d 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java
@@ -68,13 +68,14 @@ public class UsbConfirmActivity extends AlertActivity
String appName = mResolveInfo.loadLabel(packageManager).toString();
final AlertController.AlertParams ap = mAlertParams;
- ap.mIcon = mResolveInfo.loadIcon(packageManager);
ap.mTitle = appName;
if (mDevice == null) {
- ap.mMessage = getString(R.string.usb_accessory_confirm_prompt, appName);
+ ap.mMessage = getString(R.string.usb_accessory_confirm_prompt, appName,
+ mAccessory.getDescription());
mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mAccessory);
} else {
- ap.mMessage = getString(R.string.usb_device_confirm_prompt, appName);
+ ap.mMessage = getString(R.string.usb_device_confirm_prompt, appName,
+ mDevice.getProductName());
mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mDevice);
}
ap.mPositiveButtonText = getString(android.R.string.ok);
@@ -88,9 +89,11 @@ public class UsbConfirmActivity extends AlertActivity
ap.mView = inflater.inflate(com.android.internal.R.layout.always_use_checkbox, null);
mAlwaysUse = (CheckBox)ap.mView.findViewById(com.android.internal.R.id.alwaysUse);
if (mDevice == null) {
- mAlwaysUse.setText(R.string.always_use_accessory);
+ mAlwaysUse.setText(getString(R.string.always_use_accessory, appName,
+ mAccessory.getDescription()));
} else {
- mAlwaysUse.setText(R.string.always_use_device);
+ mAlwaysUse.setText(getString(R.string.always_use_device, appName,
+ mDevice.getProductName()));
}
mAlwaysUse.setOnCheckedChangeListener(this);
mClearDefaultHint = (TextView)ap.mView.findViewById(
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java
index 1e69fc5ce1fc..238407a9a6f1 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java
@@ -16,13 +16,17 @@
package com.android.systemui.usb;
+import android.annotation.NonNull;
import android.app.AlertDialog;
import android.app.PendingIntent;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.res.XmlResourceParser;
import android.hardware.usb.IUsbManager;
import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbDevice;
@@ -41,8 +45,13 @@ import android.widget.TextView;
import com.android.internal.app.AlertActivity;
import com.android.internal.app.AlertController;
+import com.android.internal.util.XmlUtils;
+import android.hardware.usb.AccessoryFilter;
+import android.hardware.usb.DeviceFilter;
import com.android.systemui.R;
+import org.xmlpull.v1.XmlPullParser;
+
public class UsbPermissionActivity extends AlertActivity
implements DialogInterface.OnClickListener, CheckBox.OnCheckedChangeListener {
@@ -81,13 +90,14 @@ public class UsbPermissionActivity extends AlertActivity
String appName = aInfo.loadLabel(packageManager).toString();
final AlertController.AlertParams ap = mAlertParams;
- ap.mIcon = aInfo.loadIcon(packageManager);
ap.mTitle = appName;
if (mDevice == null) {
- ap.mMessage = getString(R.string.usb_accessory_permission_prompt, appName);
+ ap.mMessage = getString(R.string.usb_accessory_permission_prompt, appName,
+ mAccessory.getDescription());
mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mAccessory);
} else {
- ap.mMessage = getString(R.string.usb_device_permission_prompt, appName);
+ ap.mMessage = getString(R.string.usb_device_permission_prompt, appName,
+ mDevice.getProductName());
mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mDevice);
}
ap.mPositiveButtonText = getString(android.R.string.ok);
@@ -95,25 +105,123 @@ public class UsbPermissionActivity extends AlertActivity
ap.mPositiveButtonListener = this;
ap.mNegativeButtonListener = this;
- // add "always use" checkbox
- LayoutInflater inflater = (LayoutInflater)getSystemService(
- Context.LAYOUT_INFLATER_SERVICE);
- ap.mView = inflater.inflate(com.android.internal.R.layout.always_use_checkbox, null);
- mAlwaysUse = (CheckBox)ap.mView.findViewById(com.android.internal.R.id.alwaysUse);
- if (mDevice == null) {
- mAlwaysUse.setText(R.string.always_use_accessory);
- } else {
- mAlwaysUse.setText(R.string.always_use_device);
+ try {
+ PackageInfo packageInfo = packageManager.getPackageInfo(mPackageName,
+ PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA);
+
+ if ((mDevice != null && canBeDefault(mDevice, packageInfo))
+ || (mAccessory != null && canBeDefault(mAccessory, packageInfo))) {
+ // add "open when" checkbox
+ LayoutInflater inflater = (LayoutInflater) getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+ ap.mView = inflater.inflate(com.android.internal.R.layout.always_use_checkbox, null);
+ mAlwaysUse = (CheckBox) ap.mView.findViewById(com.android.internal.R.id.alwaysUse);
+ if (mDevice == null) {
+ mAlwaysUse.setText(getString(R.string.always_use_accessory, appName,
+ mAccessory.getDescription()));
+ } else {
+ mAlwaysUse.setText(getString(R.string.always_use_device, appName,
+ mDevice.getProductName()));
+ }
+ mAlwaysUse.setOnCheckedChangeListener(this);
+
+ mClearDefaultHint = (TextView)ap.mView.findViewById(
+ com.android.internal.R.id.clearDefaultHint);
+ mClearDefaultHint.setVisibility(View.GONE);
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // ignore
}
- mAlwaysUse.setOnCheckedChangeListener(this);
- mClearDefaultHint = (TextView)ap.mView.findViewById(
- com.android.internal.R.id.clearDefaultHint);
- mClearDefaultHint.setVisibility(View.GONE);
setupAlert();
}
+ /**
+ * Can the app be the default for the USB device. I.e. can the app be launched by default if
+ * the device is plugged in.
+ *
+ * @param device The device the app would be default for
+ * @param packageInfo The package info of the app
+ *
+ * @return {@code true} iff the app can be default
+ */
+ private boolean canBeDefault(@NonNull UsbDevice device, @NonNull PackageInfo packageInfo) {
+ ActivityInfo[] activities = packageInfo.activities;
+ if (activities != null) {
+ int numActivities = activities.length;
+ for (int i = 0; i < numActivities; i++) {
+ ActivityInfo activityInfo = activities[i];
+
+ try (XmlResourceParser parser = activityInfo.loadXmlMetaData(getPackageManager(),
+ UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
+ if (parser == null) {
+ continue;
+ }
+
+ XmlUtils.nextElement(parser);
+ while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
+ if ("usb-device".equals(parser.getName())) {
+ DeviceFilter filter = DeviceFilter.read(parser);
+ if (filter.matches(device)) {
+ return true;
+ }
+ }
+
+ XmlUtils.nextElement(parser);
+ }
+ } catch (Exception e) {
+ Log.w(TAG, "Unable to load component info " + activityInfo.toString(), e);
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Can the app be the default for the USB accessory. I.e. can the app be launched by default if
+ * the accessory is plugged in.
+ *
+ * @param accessory The accessory the app would be default for
+ * @param packageInfo The package info of the app
+ *
+ * @return {@code true} iff the app can be default
+ */
+ private boolean canBeDefault(@NonNull UsbAccessory accessory,
+ @NonNull PackageInfo packageInfo) {
+ ActivityInfo[] activities = packageInfo.activities;
+ if (activities != null) {
+ int numActivities = activities.length;
+ for (int i = 0; i < numActivities; i++) {
+ ActivityInfo activityInfo = activities[i];
+
+ try (XmlResourceParser parser = activityInfo.loadXmlMetaData(getPackageManager(),
+ UsbManager.ACTION_USB_ACCESSORY_ATTACHED)) {
+ if (parser == null) {
+ continue;
+ }
+
+ XmlUtils.nextElement(parser);
+ while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
+ if ("usb-accessory".equals(parser.getName())) {
+ AccessoryFilter filter = AccessoryFilter.read(parser);
+ if (filter.matches(accessory)) {
+ return true;
+ }
+ }
+
+ XmlUtils.nextElement(parser);
+ }
+ } catch (Exception e) {
+ Log.w(TAG, "Unable to load component info " + activityInfo.toString(), e);
+ }
+ }
+ }
+
+ return false;
+ }
+
@Override
public void onDestroy() {
IBinder b = ServiceManager.getService(USB_SERVICE);
@@ -126,7 +234,7 @@ public class UsbPermissionActivity extends AlertActivity
intent.putExtra(UsbManager.EXTRA_DEVICE, mDevice);
if (mPermissionGranted) {
service.grantDevicePermission(mDevice, mUid);
- if (mAlwaysUse.isChecked()) {
+ if (mAlwaysUse != null && mAlwaysUse.isChecked()) {
final int userId = UserHandle.getUserId(mUid);
service.setDevicePackage(mDevice, mPackageName, userId);
}
@@ -136,7 +244,7 @@ public class UsbPermissionActivity extends AlertActivity
intent.putExtra(UsbManager.EXTRA_ACCESSORY, mAccessory);
if (mPermissionGranted) {
service.grantAccessoryPermission(mAccessory, mUid);
- if (mAlwaysUse.isChecked()) {
+ if (mAlwaysUse != null && mAlwaysUse.isChecked()) {
final int userId = UserHandle.getUserId(mUid);
service.setAccessoryPackage(mAccessory, mPackageName, userId);
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
index 87bc0e67f7be..14d5c6f52cd0 100644
--- a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
+++ b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
@@ -31,7 +31,8 @@ import java.util.Arrays;
public class NotificationChannels extends SystemUI {
public static String ALERTS = "ALR";
- public static String SCREENSHOTS = "SCN";
+ public static String SCREENSHOTS_LEGACY = "SCN";
+ public static String SCREENSHOTS_HEADSUP = "SCN_HEADSUP";
public static String GENERAL = "GEN";
public static String STORAGE = "DSK";
public static String TVPIP = "TPP";
@@ -56,10 +57,6 @@ public class NotificationChannels extends SystemUI {
context.getString(R.string.notification_channel_alerts),
NotificationManager.IMPORTANCE_HIGH),
new NotificationChannel(
- SCREENSHOTS,
- context.getString(R.string.notification_channel_screenshot),
- NotificationManager.IMPORTANCE_LOW),
- new NotificationChannel(
GENERAL,
context.getString(R.string.notification_channel_general),
NotificationManager.IMPORTANCE_MIN),
@@ -69,9 +66,18 @@ public class NotificationChannels extends SystemUI {
isTv(context)
? NotificationManager.IMPORTANCE_DEFAULT
: NotificationManager.IMPORTANCE_LOW),
+ createScreenshotChannel(
+ context.getString(R.string.notification_channel_screenshot),
+ nm.getNotificationChannel(SCREENSHOTS_LEGACY)),
batteryChannel
));
+ // Delete older SS channel if present.
+ // Screenshots promoted to heads-up in P, this cleans up the lower priority channel from O.
+ // This line can be deleted in Q.
+ nm.deleteNotificationChannel(SCREENSHOTS_LEGACY);
+
+
if (isTv(context)) {
// TV specific notification channel for TV PIP controls.
// Importance should be {@link NotificationManager#IMPORTANCE_MAX} to have the highest
@@ -83,6 +89,40 @@ public class NotificationChannels extends SystemUI {
}
}
+ /**
+ * Set up screenshot channel, respecting any previously committed user settings on legacy
+ * channel.
+ * @return
+ */
+ @VisibleForTesting static NotificationChannel createScreenshotChannel(
+ String name, NotificationChannel legacySS) {
+ NotificationChannel screenshotChannel = new NotificationChannel(SCREENSHOTS_HEADSUP,
+ name, NotificationManager.IMPORTANCE_HIGH); // pop on screen
+
+ screenshotChannel.setSound(Uri.parse(""), // silent
+ new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION).build());
+
+ if (legacySS != null) {
+ // Respect any user modified fields from the old channel.
+ int userlock = legacySS.getUserLockedFields();
+ if ((userlock & NotificationChannel.USER_LOCKED_IMPORTANCE) != 0) {
+ screenshotChannel.setImportance(legacySS.getImportance());
+ }
+ if ((userlock & NotificationChannel.USER_LOCKED_SOUND) != 0) {
+ screenshotChannel.setSound(legacySS.getSound(), legacySS.getAudioAttributes());
+ }
+ if ((userlock & NotificationChannel.USER_LOCKED_VIBRATION) != 0) {
+ screenshotChannel.setVibrationPattern(legacySS.getVibrationPattern());
+ }
+ if ((userlock & NotificationChannel.USER_LOCKED_LIGHTS) != 0) {
+ screenshotChannel.setLightColor(legacySS.getLightColor());
+ }
+ // skip show_badge, irrelevant for system channel
+ }
+
+ return screenshotChannel;
+ }
+
@Override
public void start() {
createAll(mContext);
diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java
index f4aebae7bdaf..eca612776f21 100644
--- a/packages/SystemUI/src/com/android/systemui/util/Utils.java
+++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java
@@ -14,6 +14,11 @@
package com.android.systemui.util;
+import android.view.View;
+
+import com.android.systemui.SysUiServiceProvider;
+import com.android.systemui.statusbar.CommandQueue;
+
import java.util.List;
import java.util.function.Consumer;
@@ -28,4 +33,52 @@ public class Utils {
c.accept(list.get(i));
}
}
+
+ /**
+ * Sets the visibility of an UI element according to the DISABLE_* flags in
+ * {@link android.app.StatusBarManager}.
+ */
+ public static class DisableStateTracker implements CommandQueue.Callbacks,
+ View.OnAttachStateChangeListener {
+ private final int mMask1;
+ private final int mMask2;
+ private View mView;
+ private boolean mDisabled;
+
+ public DisableStateTracker(int disableMask, int disable2Mask) {
+ mMask1 = disableMask;
+ mMask2 = disable2Mask;
+ }
+
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ mView = v;
+ SysUiServiceProvider.getComponent(v.getContext(), CommandQueue.class)
+ .addCallbacks(this);
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ SysUiServiceProvider.getComponent(mView.getContext(), CommandQueue.class)
+ .removeCallbacks(this);
+ mView = null;
+ }
+
+ /**
+ * Sets visibility of this {@link View} given the states passed from
+ * {@link com.android.systemui.statusbar.CommandQueue.Callbacks#disable(int, int)}.
+ */
+ @Override
+ public void disable(int state1, int state2, boolean animate) {
+ final boolean disabled = ((state1 & mMask1) != 0) || ((state2 & mMask2) != 0);
+ if (disabled == mDisabled) return;
+ mDisabled = disabled;
+ mView.setVisibility(disabled ? View.GONE : View.VISIBLE);
+ }
+
+ /** @return {@code true} if and only if this {@link View} is currently disabled */
+ public boolean isDisabled() {
+ return mDisabled;
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/Events.java b/packages/SystemUI/src/com/android/systemui/volume/Events.java
index 49a12f401c90..2c85bb6e1644 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/Events.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/Events.java
@@ -51,6 +51,7 @@ public class Events {
public static final int EVENT_SUPPRESSOR_CHANGED = 14; // (component|string) (name|string)
public static final int EVENT_MUTE_CHANGED = 15; // (stream|int) (muted|bool)
public static final int EVENT_TOUCH_LEVEL_DONE = 16; // (stream|int) (level|bool)
+ public static final int EVENT_ZEN_CONFIG_CHANGED = 17; // (allow/disallow|string)
private static final String[] EVENT_TAGS = {
"show_dialog",
@@ -70,6 +71,7 @@ public class Events {
"suppressor_changed",
"mute_changed",
"touch_level_done",
+ "zen_mode_config_changed",
};
public static final int DISMISS_REASON_UNKNOWN = 0;
@@ -80,6 +82,7 @@ public class Events {
public static final int DISMISS_REASON_SETTINGS_CLICKED = 5;
public static final int DISMISS_REASON_DONE_CLICKED = 6;
public static final int DISMISS_STREAM_GONE = 7;
+ public static final int DISMISS_REASON_OUTPUT_CHOOSER = 8;
public static final String[] DISMISS_REASONS = {
"unknown",
"touch_outside",
@@ -88,7 +91,8 @@ public class Events {
"screen_off",
"settings_clicked",
"done_clicked",
- "a11y_stream_changed"
+ "a11y_stream_changed",
+ "output_chooser"
};
public static final int SHOW_REASON_UNKNOWN = 0;
diff --git a/packages/SystemUI/src/com/android/systemui/volume/MediaRouterWrapper.java b/packages/SystemUI/src/com/android/systemui/volume/MediaRouterWrapper.java
new file mode 100644
index 000000000000..3423452c2a27
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/MediaRouterWrapper.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume;
+
+import android.support.v7.media.MediaRouteSelector;
+import android.support.v7.media.MediaRouter;
+
+import java.util.List;
+
+/**
+ * Wrapper for final class MediaRouter, for testing.
+ */
+public class MediaRouterWrapper {
+
+ private final MediaRouter mRouter;
+
+ public MediaRouterWrapper(MediaRouter router)
+ {
+ mRouter = router;
+ }
+
+ public void addCallback(MediaRouteSelector selector, MediaRouter.Callback callback, int flags) {
+ mRouter.addCallback(selector, callback, flags);
+ }
+
+ public void removeCallback(MediaRouter.Callback callback) {
+ mRouter.removeCallback(callback);
+ }
+
+ public void unselect(int reason) {
+ mRouter.unselect(reason);
+ }
+
+ public List<MediaRouter.RouteInfo> getRoutes() {
+ return mRouter.getRoutes();
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java b/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java
new file mode 100644
index 000000000000..6ed07f8d2c37
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java
@@ -0,0 +1,534 @@
+/*
+ * 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.volume;
+
+import static android.support.v7.media.MediaRouter.RouteInfo.CONNECTION_STATE_CONNECTED;
+import static android.support.v7.media.MediaRouter.RouteInfo.CONNECTION_STATE_CONNECTING;
+import static android.support.v7.media.MediaRouter.UNSELECT_REASON_DISCONNECTED;
+
+import static com.android.settingslib.bluetooth.Utils.getBtClassDrawableWithDescription;
+
+import android.app.Dialog;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.support.v7.media.MediaControlIntent;
+import android.support.v7.media.MediaRouteSelector;
+import android.support.v7.media.MediaRouter;
+import android.telecom.TelecomManager;
+import android.util.Log;
+import android.util.Pair;
+import android.view.Window;
+import android.view.WindowManager;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settingslib.Utils;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.systemui.Dependency;
+import com.android.systemui.HardwareUiLayout;
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+import com.android.systemui.plugins.VolumeDialogController;
+import com.android.systemui.statusbar.policy.BluetoothController;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.List;
+
+public class OutputChooserDialog extends Dialog
+ implements DialogInterface.OnDismissListener, OutputChooserLayout.Callback {
+
+ private static final String TAG = Util.logTag(OutputChooserDialog.class);
+ private static final int MAX_DEVICES = 10;
+
+ private static final long UPDATE_DELAY_MS = 300L;
+ private static final int MSG_UPDATE_ITEMS = 1;
+
+ private final Context mContext;
+ private final BluetoothController mBluetoothController;
+ private WifiManager mWifiManager;
+ private OutputChooserLayout mView;
+ private final MediaRouterWrapper mRouter;
+ private final MediaRouterCallback mRouterCallback;
+ private long mLastUpdateTime;
+ static final boolean INCLUDE_MEDIA_ROUTES = false;
+ private boolean mIsInCall;
+ protected boolean isAttached;
+
+ private final MediaRouteSelector mRouteSelector;
+ private Drawable mDefaultIcon;
+ private Drawable mTvIcon;
+ private Drawable mSpeakerIcon;
+ private Drawable mSpeakerGroupIcon;
+ private HardwareUiLayout mHardwareLayout;
+ private final VolumeDialogController mController;
+
+ public OutputChooserDialog(Context context, MediaRouterWrapper router) {
+ super(context, com.android.systemui.R.style.qs_theme);
+ mContext = context;
+ mBluetoothController = Dependency.get(BluetoothController.class);
+ mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+ TelecomManager tm = (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE);
+ mIsInCall = tm.isInCall();
+ mRouter = router;
+ mRouterCallback = new MediaRouterCallback();
+ mRouteSelector = new MediaRouteSelector.Builder()
+ .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
+ .build();
+
+ final IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+ context.registerReceiver(mReceiver, filter);
+
+ mController = Dependency.get(VolumeDialogController.class);
+
+ // Window initialization
+ Window window = getWindow();
+ window.requestFeature(Window.FEATURE_NO_TITLE);
+ window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
+ window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND
+ | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
+ window.addFlags(
+ WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+ | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+ | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
+ window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
+ }
+
+ protected void setIsInCall(boolean inCall) {
+ mIsInCall = inCall;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.output_chooser);
+ setCanceledOnTouchOutside(true);
+ setOnDismissListener(this::onDismiss);
+
+ mView = findViewById(R.id.output_chooser);
+ mHardwareLayout = HardwareUiLayout.get(mView);
+ mHardwareLayout.setOutsideTouchListener(view -> dismiss());
+ mHardwareLayout.setSwapOrientation(false);
+ mView.setCallback(this);
+
+ if (mIsInCall) {
+ mView.setTitle(R.string.output_calls_title);
+ } else {
+ mView.setTitle(R.string.output_title);
+ }
+
+ mDefaultIcon = mContext.getDrawable(R.drawable.ic_cast);
+ mTvIcon = mContext.getDrawable(R.drawable.ic_tv);
+ mSpeakerIcon = mContext.getDrawable(R.drawable.ic_speaker);
+ mSpeakerGroupIcon = mContext.getDrawable(R.drawable.ic_speaker_group);
+
+ final boolean wifiOff = !mWifiManager.isWifiEnabled();
+ final boolean btOff = !mBluetoothController.isBluetoothEnabled();
+ if (wifiOff && btOff) {
+ mView.setEmptyState(getDisabledServicesMessage(wifiOff, btOff));
+ }
+ // time out after 5 seconds
+ mView.postDelayed(() -> updateItems(true), 5000);
+ }
+
+ protected void cleanUp() {}
+
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ if (!mIsInCall && INCLUDE_MEDIA_ROUTES) {
+ mRouter.addCallback(mRouteSelector, mRouterCallback,
+ MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
+ }
+ mBluetoothController.addCallback(mCallback);
+ mController.addCallback(mControllerCallbackH, mHandler);
+ isAttached = true;
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ isAttached = false;
+ mRouter.removeCallback(mRouterCallback);
+ mController.removeCallback(mControllerCallbackH);
+ mBluetoothController.removeCallback(mCallback);
+ super.onDetachedFromWindow();
+ }
+
+ @Override
+ public void onDismiss(DialogInterface unused) {
+ mContext.unregisterReceiver(mReceiver);
+ cleanUp();
+ }
+
+ @Override
+ public void show() {
+ super.show();
+ Dependency.get(MetricsLogger.class).visible(MetricsProto.MetricsEvent.OUTPUT_CHOOSER);
+ mHardwareLayout.setTranslationX(getAnimTranslation());
+ mHardwareLayout.setAlpha(0);
+ mHardwareLayout.animate()
+ .alpha(1)
+ .translationX(0)
+ .setDuration(300)
+ .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+ .withEndAction(() -> getWindow().getDecorView().requestAccessibilityFocus())
+ .start();
+ }
+
+ @Override
+ public void dismiss() {
+ Dependency.get(MetricsLogger.class).hidden(MetricsProto.MetricsEvent.OUTPUT_CHOOSER);
+ mHardwareLayout.setTranslationX(0);
+ mHardwareLayout.setAlpha(1);
+ mHardwareLayout.animate()
+ .alpha(0)
+ .translationX(getAnimTranslation())
+ .setDuration(300)
+ .withEndAction(() -> super.dismiss())
+ .setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator())
+ .start();
+ }
+
+ private float getAnimTranslation() {
+ return getContext().getResources().getDimension(
+ com.android.systemui.R.dimen.output_chooser_panel_width) / 2;
+ }
+
+ @Override
+ public void onDetailItemClick(OutputChooserLayout.Item item) {
+ if (item == null || item.tag == null) return;
+ if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_BT) {
+ final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag;
+ if (device.getMaxConnectionState() == BluetoothProfile.STATE_DISCONNECTED) {
+ Dependency.get(MetricsLogger.class).action(
+ MetricsProto.MetricsEvent.ACTION_OUTPUT_CHOOSER_CONNECT);
+ mBluetoothController.connect(device);
+ }
+ } else if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_MEDIA_ROUTER) {
+ final MediaRouter.RouteInfo route = (MediaRouter.RouteInfo) item.tag;
+ if (route.isEnabled()) {
+ Dependency.get(MetricsLogger.class).action(
+ MetricsProto.MetricsEvent.ACTION_OUTPUT_CHOOSER_CONNECT);
+ route.select();
+ }
+ }
+ }
+
+ @Override
+ public void onDetailItemDisconnect(OutputChooserLayout.Item item) {
+ if (item == null || item.tag == null) return;
+ if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_BT) {
+ final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag;
+ Dependency.get(MetricsLogger.class).action(
+ MetricsProto.MetricsEvent.ACTION_OUTPUT_CHOOSER_DISCONNECT);
+ mBluetoothController.disconnect(device);
+ } else if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_MEDIA_ROUTER) {
+ Dependency.get(MetricsLogger.class).action(
+ MetricsProto.MetricsEvent.ACTION_OUTPUT_CHOOSER_DISCONNECT);
+ mRouter.unselect(UNSELECT_REASON_DISCONNECTED);
+ }
+ }
+
+ private void updateItems(boolean timeout) {
+ if (SystemClock.uptimeMillis() - mLastUpdateTime < UPDATE_DELAY_MS) {
+ mHandler.removeMessages(MSG_UPDATE_ITEMS);
+ mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_UPDATE_ITEMS, timeout),
+ mLastUpdateTime + UPDATE_DELAY_MS);
+ return;
+ }
+ mLastUpdateTime = SystemClock.uptimeMillis();
+ if (mView == null) return;
+ ArrayList<OutputChooserLayout.Item> items = new ArrayList<>();
+
+ // Add bluetooth devices
+ addBluetoothDevices(items);
+
+ // Add remote displays
+ if (!mIsInCall && INCLUDE_MEDIA_ROUTES) {
+ addRemoteDisplayRoutes(items);
+ }
+
+ items.sort(ItemComparator.sInstance);
+
+ if (items.size() == 0 && timeout) {
+ String emptyMessage = mContext.getString(R.string.output_none_found);
+ final boolean wifiOff = !mWifiManager.isWifiEnabled();
+ final boolean btOff = !mBluetoothController.isBluetoothEnabled();
+ if (wifiOff || btOff) {
+ emptyMessage = getDisabledServicesMessage(wifiOff, btOff);
+ }
+ mView.setEmptyState(emptyMessage);
+ }
+
+ mView.setItems(items.toArray(new OutputChooserLayout.Item[items.size()]));
+ }
+
+ private String getDisabledServicesMessage(boolean wifiOff, boolean btOff) {
+ return mContext.getString(R.string.output_none_found_service_off,
+ wifiOff && btOff ? mContext.getString(R.string.output_service_bt_wifi)
+ : wifiOff ? mContext.getString(R.string.output_service_wifi)
+ : mContext.getString(R.string.output_service_bt));
+ }
+
+ private void addBluetoothDevices(List<OutputChooserLayout.Item> items) {
+ final Collection<CachedBluetoothDevice> devices = mBluetoothController.getDevices();
+ if (devices != null) {
+ int connectedDevices = 0;
+ int count = 0;
+ for (CachedBluetoothDevice device : devices) {
+ if (mBluetoothController.getBondState(device) == BluetoothDevice.BOND_NONE) continue;
+ final int majorClass = device.getBtClass().getMajorDeviceClass();
+ if (majorClass != BluetoothClass.Device.Major.AUDIO_VIDEO
+ && majorClass != BluetoothClass.Device.Major.UNCATEGORIZED) {
+ continue;
+ }
+ final OutputChooserLayout.Item item = new OutputChooserLayout.Item();
+ item.iconResId = R.drawable.ic_qs_bluetooth_on;
+ item.line1 = device.getName();
+ item.tag = device;
+ item.deviceType = OutputChooserLayout.Item.DEVICE_TYPE_BT;
+ int state = device.getMaxConnectionState();
+ if (state == BluetoothProfile.STATE_CONNECTED) {
+ item.iconResId = R.drawable.ic_qs_bluetooth_connected;
+ int batteryLevel = device.getBatteryLevel();
+ if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
+ Pair<Drawable, String> pair =
+ getBtClassDrawableWithDescription(getContext(), device);
+ item.icon = pair.first;
+ item.line2 = mContext.getString(
+ R.string.quick_settings_connected_battery_level,
+ Utils.formatPercentage(batteryLevel));
+ } else {
+ item.line2 = mContext.getString(R.string.quick_settings_connected);
+ }
+ item.canDisconnect = true;
+ items.add(connectedDevices, item);
+ connectedDevices++;
+ } else if (state == BluetoothProfile.STATE_CONNECTING) {
+ item.iconResId = R.drawable.ic_qs_bluetooth_connecting;
+ item.line2 = mContext.getString(R.string.quick_settings_connecting);
+ items.add(connectedDevices, item);
+ } else {
+ items.add(item);
+ }
+ if (++count == MAX_DEVICES) {
+ break;
+ }
+ }
+ }
+ }
+
+ private void addRemoteDisplayRoutes(List<OutputChooserLayout.Item> items) {
+ List<MediaRouter.RouteInfo> routes = mRouter.getRoutes();
+ for(MediaRouter.RouteInfo route : routes) {
+ if (route.isDefaultOrBluetooth() || !route.isEnabled()
+ || !route.matchesSelector(mRouteSelector)) {
+ continue;
+ }
+ final OutputChooserLayout.Item item = new OutputChooserLayout.Item();
+ item.icon = getIconDrawable(route);
+ item.line1 = route.getName();
+ item.tag = route;
+ item.deviceType = OutputChooserLayout.Item.DEVICE_TYPE_MEDIA_ROUTER;
+ if (route.getConnectionState() == CONNECTION_STATE_CONNECTING) {
+ mContext.getString(R.string.quick_settings_connecting);
+ } else {
+ item.line2 = route.getDescription();
+ }
+
+ if (route.getConnectionState() == CONNECTION_STATE_CONNECTED) {
+ item.canDisconnect = true;
+ }
+ items.add(item);
+ }
+ }
+
+ private Drawable getIconDrawable(MediaRouter.RouteInfo route) {
+ Uri iconUri = route.getIconUri();
+ if (iconUri != null) {
+ try {
+ InputStream is = getContext().getContentResolver().openInputStream(iconUri);
+ Drawable drawable = Drawable.createFromStream(is, null);
+ if (drawable != null) {
+ return drawable;
+ }
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to load " + iconUri, e);
+ // Falls back.
+ }
+ }
+ return getDefaultIconDrawable(route);
+ }
+
+ private Drawable getDefaultIconDrawable(MediaRouter.RouteInfo route) {
+ // If the type of the receiver device is specified, use it.
+ switch (route.getDeviceType()) {
+ case MediaRouter.RouteInfo.DEVICE_TYPE_TV:
+ return mTvIcon;
+ case MediaRouter.RouteInfo.DEVICE_TYPE_SPEAKER:
+ return mSpeakerIcon;
+ }
+
+ // Otherwise, make the best guess based on other route information.
+ if (route instanceof MediaRouter.RouteGroup) {
+ // Only speakers can be grouped for now.
+ return mSpeakerGroupIcon;
+ }
+ return mDefaultIcon;
+ }
+
+ private final class MediaRouterCallback extends MediaRouter.Callback {
+ @Override
+ public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo info) {
+ updateItems(false);
+ }
+
+ @Override
+ public void onRouteRemoved(MediaRouter router, MediaRouter.RouteInfo info) {
+ updateItems(false);
+ }
+
+ @Override
+ public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo info) {
+ updateItems(false);
+ }
+
+ @Override
+ public void onRouteSelected(MediaRouter router, MediaRouter.RouteInfo route) {
+ updateItems(false);
+ }
+ }
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
+ if (D.BUG) Log.d(TAG, "Received ACTION_CLOSE_SYSTEM_DIALOGS");
+ cancel();
+ cleanUp();
+ }
+ }
+ };
+
+ private final BluetoothController.Callback mCallback = new BluetoothController.Callback() {
+ @Override
+ public void onBluetoothStateChange(boolean enabled) {
+ updateItems(false);
+ }
+
+ @Override
+ public void onBluetoothDevicesChanged() {
+ updateItems(false);
+ }
+ };
+
+ static final class ItemComparator implements Comparator<OutputChooserLayout.Item> {
+ public static final ItemComparator sInstance = new ItemComparator();
+
+ @Override
+ public int compare(OutputChooserLayout.Item lhs, OutputChooserLayout.Item rhs) {
+ // Connected item(s) first
+ if (lhs.canDisconnect != rhs.canDisconnect) {
+ return Boolean.compare(rhs.canDisconnect, lhs.canDisconnect);
+ }
+ // Bluetooth items before media routes
+ if (lhs.deviceType != rhs.deviceType) {
+ return Integer.compare(lhs.deviceType, rhs.deviceType);
+ }
+ // then by name
+ return lhs.line1.toString().compareToIgnoreCase(rhs.line1.toString());
+ }
+ }
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MSG_UPDATE_ITEMS:
+ updateItems((Boolean) message.obj);
+ break;
+ }
+ }
+ };
+
+ private final VolumeDialogController.Callbacks mControllerCallbackH
+ = new VolumeDialogController.Callbacks() {
+ @Override
+ public void onShowRequested(int reason) {
+ dismiss();
+ }
+
+ @Override
+ public void onDismissRequested(int reason) {}
+
+ @Override
+ public void onScreenOff() {
+ dismiss();
+ }
+
+ @Override
+ public void onStateChanged(VolumeDialogController.State state) {}
+
+ @Override
+ public void onLayoutDirectionChanged(int layoutDirection) {}
+
+ @Override
+ public void onConfigurationChanged() {}
+
+ @Override
+ public void onShowVibrateHint() {}
+
+ @Override
+ public void onShowSilentHint() {}
+
+ @Override
+ public void onShowSafetyWarning(int flags) {}
+
+ @Override
+ public void onAccessibilityModeChanged(Boolean showA11yStream) {}
+
+ @Override
+ public void onConnectedDeviceChanged(String deviceName) {}
+ };
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/volume/OutputChooserLayout.java b/packages/SystemUI/src/com/android/systemui/volume/OutputChooserLayout.java
new file mode 100644
index 000000000000..d4c6f897846e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/OutputChooserLayout.java
@@ -0,0 +1,256 @@
+/*
+ * 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.volume;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.systemui.FontSizeUtils;
+import com.android.systemui.R;
+import com.android.systemui.qs.AutoSizingList;
+
+/**
+ * Limited height list of devices.
+ */
+public class OutputChooserLayout extends LinearLayout {
+ private static final String TAG = "OutputChooserLayout";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private final Context mContext;
+ private final H mHandler = new H();
+ private final Adapter mAdapter = new Adapter();
+
+ private String mTag;
+ private Callback mCallback;
+ private boolean mItemsVisible = true;
+ private AutoSizingList mItemList;
+ private View mEmpty;
+ private TextView mEmptyText;
+ private TextView mTitle;
+
+ private Item[] mItems;
+
+ public OutputChooserLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mContext = context;
+ mTag = TAG;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mItemList = findViewById(android.R.id.list);
+ mItemList.setVisibility(GONE);
+ mItemList.setAdapter(mAdapter);
+ mEmpty = findViewById(android.R.id.empty);
+ mEmpty.setVisibility(GONE);
+ mEmptyText = mEmpty.findViewById(R.id.empty_text);
+ mTitle = findViewById(R.id.title);
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ FontSizeUtils.updateFontSize(mEmptyText, R.dimen.qs_detail_empty_text_size);
+ int count = mItemList.getChildCount();
+ for (int i = 0; i < count; i++) {
+ View item = mItemList.getChildAt(i);
+ FontSizeUtils.updateFontSize(item, R.id.empty_text,
+ R.dimen.qs_detail_item_primary_text_size);
+ FontSizeUtils.updateFontSize(item, android.R.id.summary,
+ R.dimen.qs_detail_item_secondary_text_size);
+ FontSizeUtils.updateFontSize(item, android.R.id.title,
+ R.dimen.qs_detail_header_text_size);
+ }
+ }
+
+ public void setTitle(int title) {
+ mTitle.setText(title);
+ }
+
+ public void setEmptyState(String text) {
+ mEmptyText.setText(text);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ if (DEBUG) Log.d(mTag, "onAttachedToWindow");
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ if (DEBUG) Log.d(mTag, "onDetachedFromWindow");
+ mCallback = null;
+ }
+
+ public void setCallback(Callback callback) {
+ mHandler.removeMessages(H.SET_CALLBACK);
+ mHandler.obtainMessage(H.SET_CALLBACK, callback).sendToTarget();
+ }
+
+ public void setItems(Item[] items) {
+ mHandler.removeMessages(H.SET_ITEMS);
+ mHandler.obtainMessage(H.SET_ITEMS, items).sendToTarget();
+ }
+
+ public void setItemsVisible(boolean visible) {
+ mHandler.removeMessages(H.SET_ITEMS_VISIBLE);
+ mHandler.obtainMessage(H.SET_ITEMS_VISIBLE, visible ? 1 : 0, 0).sendToTarget();
+ }
+
+ private void handleSetCallback(Callback callback) {
+ mCallback = callback;
+ }
+
+ private void handleSetItems(Item[] items) {
+ final int itemCount = items != null ? items.length : 0;
+ mEmpty.setVisibility(itemCount == 0 ? VISIBLE : GONE);
+ mItemList.setVisibility(itemCount == 0 ? GONE : VISIBLE);
+ mItems = items;
+ mAdapter.notifyDataSetChanged();
+ }
+
+ private void handleSetItemsVisible(boolean visible) {
+ if (mItemsVisible == visible) return;
+ mItemsVisible = visible;
+ for (int i = 0; i < mItemList.getChildCount(); i++) {
+ mItemList.getChildAt(i).setVisibility(mItemsVisible ? VISIBLE : INVISIBLE);
+ }
+ }
+
+ private class Adapter extends BaseAdapter {
+
+ @Override
+ public int getCount() {
+ return mItems != null ? mItems.length : 0;
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return mItems[position];
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return 0;
+ }
+
+ @Override
+ public View getView(int position, View view, ViewGroup parent) {
+ final Item item = mItems[position];
+ if (view == null) {
+ view = LayoutInflater.from(mContext).inflate(R.layout.output_chooser_item, parent,
+ false);
+ }
+ view.setVisibility(mItemsVisible ? VISIBLE : INVISIBLE);
+ final ImageView iv = view.findViewById(android.R.id.icon);
+ if (item.icon != null) {
+ iv.setImageDrawable(item.icon);
+ } else {
+ iv.setImageResource(item.iconResId);
+ }
+ final TextView title = view.findViewById(android.R.id.title);
+ title.setText(item.line1);
+ final TextView summary = view.findViewById(android.R.id.summary);
+ final boolean twoLines = !TextUtils.isEmpty(item.line2);
+ title.setMaxLines(twoLines ? 1 : 2);
+ summary.setVisibility(twoLines ? VISIBLE : GONE);
+ summary.setText(twoLines ? item.line2 : null);
+ view.setOnClickListener(v -> {
+ if (mCallback != null) {
+ mCallback.onDetailItemClick(item);
+ }
+ });
+
+ final ImageView icon2 = view.findViewById(android.R.id.icon2);
+ if (item.canDisconnect) {
+ icon2.setImageResource(R.drawable.ic_qs_cancel);
+ icon2.setVisibility(VISIBLE);
+ icon2.setClickable(true);
+ icon2.setOnClickListener(v -> {
+ if (mCallback != null) {
+ mCallback.onDetailItemDisconnect(item);
+ }
+ });
+ } else if (item.icon2 != -1) {
+ icon2.setVisibility(VISIBLE);
+ icon2.setImageResource(item.icon2);
+ icon2.setClickable(false);
+ } else {
+ icon2.setVisibility(GONE);
+ }
+
+ return view;
+ }
+ };
+
+ private class H extends Handler {
+ private static final int SET_ITEMS = 1;
+ private static final int SET_CALLBACK = 2;
+ private static final int SET_ITEMS_VISIBLE = 3;
+
+ public H() {
+ super(Looper.getMainLooper());
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == SET_ITEMS) {
+ handleSetItems((Item[]) msg.obj);
+ } else if (msg.what == SET_CALLBACK) {
+ handleSetCallback((OutputChooserLayout.Callback) msg.obj);
+ } else if (msg.what == SET_ITEMS_VISIBLE) {
+ handleSetItemsVisible(msg.arg1 != 0);
+ }
+ }
+ }
+
+ public static class Item {
+ public static int DEVICE_TYPE_BT = 1;
+ public static int DEVICE_TYPE_MEDIA_ROUTER = 2;
+ public int iconResId;
+ public Drawable icon;
+ public Drawable overlay;
+ public CharSequence line1;
+ public CharSequence line2;
+ public Object tag;
+ public boolean canDisconnect;
+ public int icon2 = -1;
+ public int deviceType = 0;
+ }
+
+ public interface Callback {
+ void onDetailItemClick(Item item);
+ void onDetailItemDisconnect(Item item);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/SystemUIInterpolators.java b/packages/SystemUI/src/com/android/systemui/volume/SystemUIInterpolators.java
new file mode 100644
index 000000000000..5ad8840a9dd1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/SystemUIInterpolators.java
@@ -0,0 +1,78 @@
+/*
+ * 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.volume;
+
+import android.animation.TimeInterpolator;
+
+public class SystemUIInterpolators {
+ public static final class LogDecelerateInterpolator implements TimeInterpolator {
+ private final float mBase;
+ private final float mDrift;
+ private final float mTimeScale;
+ private final float mOutputScale;
+
+ public LogDecelerateInterpolator() {
+ this(400f, 1.4f, 0);
+ }
+
+ private LogDecelerateInterpolator(float base, float timeScale, float drift) {
+ mBase = base;
+ mDrift = drift;
+ mTimeScale = 1f / timeScale;
+
+ mOutputScale = 1f / computeLog(1f);
+ }
+
+ private float computeLog(float t) {
+ return 1f - (float) Math.pow(mBase, -t * mTimeScale) + (mDrift * t);
+ }
+
+ @Override
+ public float getInterpolation(float t) {
+ return computeLog(t) * mOutputScale;
+ }
+ }
+
+ public static final class LogAccelerateInterpolator implements TimeInterpolator {
+ private final int mBase;
+ private final int mDrift;
+ private final float mLogScale;
+
+ public LogAccelerateInterpolator() {
+ this(100, 0);
+ }
+
+ private LogAccelerateInterpolator(int base, int drift) {
+ mBase = base;
+ mDrift = drift;
+ mLogScale = 1f / computeLog(1, mBase, mDrift);
+ }
+
+ private static float computeLog(float t, int base, int drift) {
+ return (float) -Math.pow(base, -t) + 1 + (drift * t);
+ }
+
+ @Override
+ public float getInterpolation(float t) {
+ return 1 - computeLog(1 - t, mBase, mDrift) * mLogScale;
+ }
+ }
+
+ public interface Callback {
+ void onAnimatingChanged(boolean animating);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
index f6d36e855964..0203c43d3683 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
@@ -27,16 +27,15 @@ import android.os.Handler;
import android.view.WindowManager.LayoutParams;
import com.android.settingslib.applications.InterestingConfigChanges;
-import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.Dependency;
import com.android.systemui.SystemUI;
import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.PluginDependencyProvider;
import com.android.systemui.plugins.VolumeDialog;
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.qs.tiles.DndTile;
import com.android.systemui.statusbar.policy.ExtensionController;
-import com.android.systemui.statusbar.policy.ExtensionController.Extension;
import com.android.systemui.tuner.TunerService;
import java.io.FileDescriptor;
@@ -52,9 +51,9 @@ public class VolumeDialogComponent implements VolumeComponent, TunerService.Tuna
public static final String VOLUME_UP_SILENT = "sysui_volume_up_silent";
public static final String VOLUME_SILENT_DO_NOT_DISTURB = "sysui_do_not_disturb";
- public static final boolean DEFAULT_VOLUME_DOWN_TO_ENTER_SILENT = true;
- public static final boolean DEFAULT_VOLUME_UP_TO_EXIT_SILENT = true;
- public static final boolean DEFAULT_DO_NOT_DISTURB_WHEN_SILENT = true;
+ public static final boolean DEFAULT_VOLUME_DOWN_TO_ENTER_SILENT = false;
+ public static final boolean DEFAULT_VOLUME_UP_TO_EXIT_SILENT = false;
+ public static final boolean DEFAULT_DO_NOT_DISTURB_WHEN_SILENT = false;
private final SystemUI mSysui;
private final Context mContext;
@@ -62,7 +61,6 @@ public class VolumeDialogComponent implements VolumeComponent, TunerService.Tuna
private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges(
ActivityInfo.CONFIG_FONT_SCALE | ActivityInfo.CONFIG_LOCALE
| ActivityInfo.CONFIG_ASSETS_PATHS);
- private final Extension mExtension;
private VolumeDialog mDialog;
private VolumePolicy mVolumePolicy = new VolumePolicy(
DEFAULT_VOLUME_DOWN_TO_ENTER_SILENT, // volumeDownToEnterSilent
@@ -79,7 +77,7 @@ public class VolumeDialogComponent implements VolumeComponent, TunerService.Tuna
// Allow plugins to reference the VolumeDialogController.
Dependency.get(PluginDependencyProvider.class)
.allowPluginDependency(VolumeDialogController.class);
- mExtension = Dependency.get(ExtensionController.class).newExtension(VolumeDialog.class)
+ Dependency.get(ExtensionController.class).newExtension(VolumeDialog.class)
.withPlugin(VolumeDialog.class)
.withDefault(this::createDefault)
.withCallback(dialog -> {
@@ -96,7 +94,6 @@ public class VolumeDialogComponent implements VolumeComponent, TunerService.Tuna
private VolumeDialog createDefault() {
VolumeDialogImpl impl = new VolumeDialogImpl(mContext);
- impl.setStreamImportant(AudioManager.STREAM_ALARM, true);
impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
impl.setAutomute(true);
impl.setSilentMode(false);
@@ -136,6 +133,10 @@ public class VolumeDialogComponent implements VolumeComponent, TunerService.Tuna
mController.setVolumePolicy(mVolumePolicy);
}
+ void setEnableDialogs(boolean volumeUi, boolean safetyWarning) {
+ mController.setEnableDialogs(volumeUi, safetyWarning);
+ }
+
@Override
public void onUserActivity() {
final KeyguardViewMediator kvm = mSysui.getComponent(KeyguardViewMediator.class);
@@ -152,7 +153,7 @@ public class VolumeDialogComponent implements VolumeComponent, TunerService.Tuna
@Override
public void onConfigurationChanged(Configuration newConfig) {
if (mConfigChanges.applyNewConfig(mContext.getResources())) {
- mExtension.reload();
+ mController.mCallbacks.onConfigurationChanged();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index b08b26d77b76..3c29b7740102 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -26,6 +26,8 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.database.ContentObserver;
+import android.media.AudioDeviceCallback;
+import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.media.AudioSystem;
import android.media.IVolumeController;
@@ -41,6 +43,7 @@ import android.os.RemoteException;
import android.os.Vibrator;
import android.provider.Settings;
import android.service.notification.Condition;
+import android.service.notification.ZenModeConfig;
import android.util.ArrayMap;
import android.util.Log;
import android.view.accessibility.AccessibilityManager;
@@ -49,7 +52,6 @@ import com.android.internal.annotations.GuardedBy;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.SysUiServiceProvider;
-import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.qs.tiles.DndTile;
@@ -57,7 +59,9 @@ import com.android.systemui.statusbar.phone.StatusBar;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -74,7 +78,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
private static final int DYNAMIC_STREAM_START_INDEX = 100;
private static final int VIBRATE_HINT_DURATION = 50;
- private static final ArrayMap<Integer, Integer> STREAMS = new ArrayMap<>();
+ static final ArrayMap<Integer, Integer> STREAMS = new ArrayMap<>();
static {
STREAMS.put(AudioSystem.STREAM_ALARM, R.string.stream_alarm);
STREAMS.put(AudioSystem.STREAM_BLUETOOTH_SCO, R.string.stream_bluetooth_sco);
@@ -104,6 +108,13 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
private final Vibrator mVibrator;
private final boolean mHasVibrator;
private boolean mShowA11yStream;
+ private boolean mShowVolumeDialog;
+ private boolean mShowSafetyWarning;
+ private DeviceCallback mDeviceCallback = new DeviceCallback();
+ private final NotificationManager mNotificationManager;
+ @GuardedBy("mLock")
+ private List<AudioDeviceInfo> mConnectedDevices = new ArrayList<>();
+ private Object mLock = new Object();
private boolean mDestroyed;
private VolumePolicy mVolumePolicy;
@@ -115,6 +126,8 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
public VolumeDialogControllerImpl(Context context) {
mContext = context.getApplicationContext();
+ mNotificationManager = (NotificationManager) mContext.getSystemService(
+ Context.NOTIFICATION_SERVICE);
Events.writeEvent(mContext, Events.EVENT_COLLECTION_STARTED);
mWorkerThread = new HandlerThread(VolumeDialogControllerImpl.class.getSimpleName());
mWorkerThread.start();
@@ -167,7 +180,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
}
protected int getAudioManagerStreamMinVolume(int stream) {
- return mAudio.getStreamMinVolume(stream);
+ return mAudio.getStreamMinVolumeInt(stream);
}
public void register() {
@@ -179,6 +192,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
} catch (SecurityException e) {
Log.w(TAG, "No access to media sessions", e);
}
+ mAudio.registerAudioDeviceCallback(mDeviceCallback, mWorker);
}
public void setVolumePolicy(VolumePolicy policy) {
@@ -204,6 +218,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
mMediaSessions.destroy();
mObserver.destroy();
mReceiver.destroy();
+ mAudio.unregisterAudioDeviceCallback(mDeviceCallback);
mWorkerThread.quitSafely();
}
@@ -283,6 +298,11 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
mWorker.obtainMessage(W.SET_ACTIVE_STREAM, stream, 0).sendToTarget();
}
+ public void setEnableDialogs(boolean volumeUi, boolean safetyWarning) {
+ mShowVolumeDialog = volumeUi;
+ mShowSafetyWarning = safetyWarning;
+ }
+
public void vibrate() {
if (mHasVibrator) {
mVibrator.vibrate(VIBRATE_HINT_DURATION);
@@ -312,7 +332,9 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
}
private void onShowSafetyWarningW(int flags) {
- mCallbacks.onShowSafetyWarning(flags);
+ if (mShowSafetyWarning) {
+ mCallbacks.onShowSafetyWarning(flags);
+ }
}
private void onAccessibilityModeChanged(Boolean showA11yStream) {
@@ -340,11 +362,15 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
private boolean shouldShowUI(int flags) {
updateStatusBar();
- return mStatusBar != null
- && mStatusBar.getWakefulnessState() != WakefulnessLifecycle.WAKEFULNESS_ASLEEP
- && mStatusBar.getWakefulnessState() != WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP
+ // if status bar isn't null, check if phone is in AOD, else check flags
+ // since we could be using a different status bar
+ return mStatusBar != null ?
+ mStatusBar.getWakefulnessState() != WakefulnessLifecycle.WAKEFULNESS_ASLEEP
+ && mStatusBar.getWakefulnessState() !=
+ WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP
&& mStatusBar.isDeviceInteractive()
- && (flags & AudioManager.FLAG_SHOW_UI) != 0;
+ && (flags & AudioManager.FLAG_SHOW_UI) != 0 && mShowVolumeDialog
+ : mShowVolumeDialog && (flags & AudioManager.FLAG_SHOW_UI) != 0;
}
boolean onVolumeChangedW(int stream, int flags) {
@@ -410,6 +436,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
}
updateRingerModeExternalW(mAudio.getRingerMode());
updateZenModeW();
+ updateZenConfig();
updateEffectsSuppressorW(mNoMan.getEffectsSuppressor());
mCallbacks.onStateChanged(mState);
}
@@ -495,6 +522,26 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
return true;
}
+ private boolean updateZenConfig() {
+ final NotificationManager.Policy policy = mNotificationManager.getNotificationPolicy();
+ boolean disallowAlarms = (policy.priorityCategories & NotificationManager.Policy
+ .PRIORITY_CATEGORY_ALARMS) == 0;
+ boolean disallowMedia = (policy.priorityCategories & NotificationManager.Policy
+ .PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER) == 0;
+ boolean disallowRinger = ZenModeConfig.areAllPriorityOnlyNotificationZenSoundsMuted(policy);
+ if (mState.disallowAlarms == disallowAlarms && mState.disallowMedia == disallowMedia
+ && mState.disallowRinger == disallowRinger) {
+ return false;
+ }
+ mState.disallowAlarms = disallowAlarms;
+ mState.disallowMedia = disallowMedia;
+ mState.disallowRinger = disallowRinger;
+ Events.writeEvent(mContext, Events.EVENT_ZEN_CONFIG_CHANGED, "disallowAlarms=" +
+ disallowAlarms + " disallowMedia=" + disallowMedia + " disallowRinger=" +
+ disallowRinger);
+ return true;
+ }
+
private boolean updateRingerModeExternalW(int rm) {
if (rm == mState.ringerModeExternal) return false;
mState.ringerModeExternal = rm;
@@ -655,6 +702,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
case USER_ACTIVITY: onUserActivityW(); break;
case SHOW_SAFETY_WARNING: onShowSafetyWarningW(msg.arg1); break;
case ACCESSIBILITY_MODE_CHANGED: onAccessibilityModeChanged((Boolean) msg.obj);
+
}
}
}
@@ -794,6 +842,18 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
});
}
}
+
+ @Override
+ public void onConnectedDeviceChanged(String deviceName) {
+ for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
+ entry.getValue().post(new Runnable() {
+ @Override
+ public void run() {
+ entry.getKey().onConnectedDeviceChanged(deviceName);
+ }
+ });
+ }
+ }
}
@@ -822,6 +882,10 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
if (ZEN_MODE_URI.equals(uri)) {
changed = updateZenModeW();
}
+ if (ZEN_MODE_CONFIG_URI.equals(uri)) {
+ changed |= updateZenConfig();
+ }
+
if (changed) {
mCallbacks.onStateChanged(mState);
}
@@ -996,6 +1060,33 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
}
}
+ protected final class DeviceCallback extends AudioDeviceCallback {
+ public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
+ synchronized (mLock) {
+ for (AudioDeviceInfo info : addedDevices) {
+ if (info.isSink()
+ && (info.getType() == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP
+ || info.getType() == AudioDeviceInfo.TYPE_BLUETOOTH_SCO)) {
+ mConnectedDevices.add(info);
+ mCallbacks.onConnectedDeviceChanged(info.getProductName().toString());
+ }
+ }
+ }
+ }
+
+ public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
+ synchronized (mLock) {
+ for (AudioDeviceInfo info : removedDevices) {
+ mConnectedDevices.remove(info);
+ }
+
+ if (mConnectedDevices.size() == 0) {
+ mCallbacks.onConnectedDeviceChanged(null);
+ }
+ }
+ }
+ }
+
public interface UserActivityListener {
void onUserActivity();
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 41a5dc84b713..1e8e98ca2e42 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -18,6 +18,11 @@ package com.android.systemui.volume;
import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK;
import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_GENERIC;
+import static android.media.AudioManager.STREAM_ACCESSIBILITY;
+
+import static com.android.systemui.volume.Events.DISMISS_REASON_OUTPUT_CHOOSER;
+import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED;
+import static com.android.systemui.volume.Events.DISMISS_REASON_TOUCH_OUTSIDE;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.animation.ObjectAnimator;
@@ -26,16 +31,14 @@ import android.annotation.SuppressLint;
import android.app.Dialog;
import android.app.KeyguardManager;
import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
-import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.media.AudioSystem;
import android.os.Debug;
@@ -43,11 +46,11 @@ import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
+import android.provider.Settings;
import android.provider.Settings.Global;
-import android.transition.AutoTransition;
-import android.transition.Transition;
-import android.transition.TransitionManager;
-import android.util.DisplayMetrics;
+import android.support.v7.media.MediaRouter;
+import android.text.InputFilter;
+import android.text.TextUtils;
import android.util.Log;
import android.util.Slog;
import android.util.SparseBooleanArray;
@@ -58,32 +61,27 @@ import android.view.View;
import android.view.View.AccessibilityDelegate;
import android.view.View.OnAttachStateChangeListener;
import android.view.View.OnClickListener;
-import android.view.View.OnTouchListener;
import android.view.ViewGroup;
-import android.view.ViewGroup.MarginLayoutParams;
import android.view.Window;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
import android.view.animation.DecelerateInterpolator;
import android.widget.ImageButton;
+import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
import com.android.settingslib.Utils;
import com.android.systemui.Dependency;
-import com.android.systemui.Interpolators;
-import com.android.systemui.Prefs;
import com.android.systemui.R;
+import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.VolumeDialog;
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.plugins.VolumeDialogController.State;
import com.android.systemui.plugins.VolumeDialogController.StreamState;
-import com.android.systemui.statusbar.policy.ZenModeController;
-import com.android.systemui.tuner.TunerService;
-import com.android.systemui.tuner.TunerZenModePanel;
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -96,11 +94,9 @@ import java.util.List;
*
* Methods ending in "H" must be called on the (ui) handler.
*/
-public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
+public class VolumeDialogImpl implements VolumeDialog {
private static final String TAG = Util.logTag(VolumeDialogImpl.class);
- public static final String SHOW_FULL_ZEN = "sysui_show_full_zen";
-
private static final long USER_ATTEMPT_GRACE_PERIOD = 1000;
private static final int UPDATE_ANIMATION_DURATION = 80;
@@ -112,26 +108,23 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
private CustomDialog mDialog;
private ViewGroup mDialogView;
private ViewGroup mDialogRowsView;
- private ViewGroup mDialogContentView;
- private ImageButton mExpandButton;
+ private ViewGroup mFooter;
+ private ImageButton mRingerIcon;
+ private ImageView mZenIcon;
+ private TextView mRingerStatus;
+ private TextView mRingerTitle;
private final List<VolumeRow> mRows = new ArrayList<>();
private ConfigurableTexts mConfigurableTexts;
private final SparseBooleanArray mDynamic = new SparseBooleanArray();
private final KeyguardManager mKeyguard;
- private final AudioManager mAudioManager;
- private final AccessibilityManager mAccessibilityMgr;
- private int mExpandButtonAnimationDuration;
- private ZenFooter mZenFooter;
+ private final AccessibilityManagerWrapper mAccessibilityMgr;
private final Object mSafetyWarningLock = new Object();
+ private final Object mOutputChooserLock = new Object();
private final Accessibility mAccessibility = new Accessibility();
private final ColorStateList mActiveSliderTint;
private final ColorStateList mInactiveSliderTint;
- private VolumeDialogMotion mMotion;
- private int mWindowType;
- private final ZenModeController mZenModeController;
private boolean mShowing;
- private boolean mExpanded;
private boolean mShowA11yStream;
private int mActiveStream;
@@ -139,54 +132,32 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
private boolean mAutomute = VolumePrefs.DEFAULT_ENABLE_AUTOMUTE;
private boolean mSilentMode = VolumePrefs.DEFAULT_ENABLE_SILENT_MODE;
private State mState;
- private boolean mExpandButtonAnimationRunning;
private SafetyWarningDialog mSafetyWarning;
- private Callback mCallback;
- private boolean mPendingStateChanged;
- private boolean mPendingRecheckAll;
- private long mCollapseTime;
+ private OutputChooserDialog mOutputChooserDialog;
private boolean mHovering = false;
- private int mDensity;
-
- private boolean mShowFullZen;
- private TunerZenModePanel mZenPanel;
public VolumeDialogImpl(Context context) {
mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme);
- mZenModeController = Dependency.get(ZenModeController.class);
mController = Dependency.get(VolumeDialogController.class);
mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
- mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
- mAccessibilityMgr =
- (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
+ mAccessibilityMgr = Dependency.get(AccessibilityManagerWrapper.class);
mActiveSliderTint = ColorStateList.valueOf(Utils.getColorAccent(mContext));
mInactiveSliderTint = loadColorStateList(R.color.volume_slider_inactive);
}
public void init(int windowType, Callback callback) {
- mCallback = callback;
- mWindowType = windowType;
-
initDialog();
mAccessibility.init();
mController.addCallback(mControllerCallbackH, mHandler);
mController.getState();
- Dependency.get(TunerService.class).addTunable(this, SHOW_FULL_ZEN);
-
- final Configuration currentConfig = mContext.getResources().getConfiguration();
- mDensity = currentConfig.densityDpi;
}
@Override
public void destroy() {
mAccessibility.destroy();
mController.removeCallback(mControllerCallbackH);
- if (mZenFooter != null) {
- mZenFooter.cleanup();
- }
- Dependency.get(TunerService.class).removeTunable(this);
mHandler.removeCallbacksAndMessages(null);
}
@@ -199,119 +170,90 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
mWindow = mDialog.getWindow();
mWindow.requestFeature(Window.FEATURE_NO_TITLE);
mWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
- mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+ mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND
+ | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
| WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
- mDialog.setCanceledOnTouchOutside(true);
- final Resources res = mContext.getResources();
+ mWindow.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
+ mWindow.setWindowAnimations(com.android.internal.R.style.Animation_Toast);
final WindowManager.LayoutParams lp = mWindow.getAttributes();
- lp.type = mWindowType;
lp.format = PixelFormat.TRANSLUCENT;
lp.setTitle(VolumeDialogImpl.class.getSimpleName());
- lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
- lp.y = res.getDimensionPixelSize(R.dimen.volume_offset_top);
- lp.gravity = Gravity.TOP;
+ lp.gravity = Gravity.RIGHT | Gravity.CENTER_VERTICAL;
lp.windowAnimations = -1;
mWindow.setAttributes(lp);
- mWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
+ mWindow.setLayout(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+ mDialog.setCanceledOnTouchOutside(true);
mDialog.setContentView(R.layout.volume_dialog);
- mDialogView = (ViewGroup) mDialog.findViewById(R.id.volume_dialog);
- mDialogView.setOnHoverListener(new View.OnHoverListener() {
- @Override
- public boolean onHover(View v, MotionEvent event) {
- int action = event.getActionMasked();
- mHovering = (action == MotionEvent.ACTION_HOVER_ENTER)
- || (action == MotionEvent.ACTION_HOVER_MOVE);
- rescheduleTimeoutH();
- return true;
- }
+ mDialog.setOnShowListener(dialog -> {
+ mDialogView.setTranslationX(mDialogView.getWidth() / 2);
+ mDialogView.setAlpha(0);
+ mDialogView.animate()
+ .alpha(1)
+ .translationX(0)
+ .setDuration(300)
+ .setInterpolator(new SystemUIInterpolators.LogDecelerateInterpolator())
+ .withEndAction(() -> {
+ mWindow.getDecorView().requestAccessibilityFocus();
+ })
+ .start();
});
+ mDialogView = mDialog.findViewById(R.id.volume_dialog);
+ mDialogView.setOnHoverListener((v, event) -> {
+ int action = event.getActionMasked();
+ mHovering = (action == MotionEvent.ACTION_HOVER_ENTER)
+ || (action == MotionEvent.ACTION_HOVER_MOVE);
+ rescheduleTimeoutH();
+ return true;
+ });
+ VolumeUiLayout uiLayout = VolumeUiLayout.get(mDialogView);
+ uiLayout.updateRotation();
- mDialogContentView = mDialog.findViewById(R.id.volume_dialog_content);
- mDialogRowsView = mDialogContentView.findViewById(R.id.volume_dialog_rows);
- mExpanded = false;
- mExpandButton = (ImageButton) mDialogView.findViewById(R.id.volume_expand_button);
- mExpandButton.setOnClickListener(mClickExpand);
-
- mExpandButton.setVisibility(
- AudioSystem.isSingleVolume(mContext) ? View.GONE : View.VISIBLE);
- updateWindowWidthH();
- updateExpandButtonH();
-
- mMotion = new VolumeDialogMotion(mDialog, mDialogView, mDialogContentView, mExpandButton,
- new VolumeDialogMotion.Callback() {
- @Override
- public void onAnimatingChanged(boolean animating) {
- if (animating) return;
- if (mPendingStateChanged) {
- mHandler.sendEmptyMessage(H.STATE_CHANGED);
- mPendingStateChanged = false;
- }
- if (mPendingRecheckAll) {
- mHandler.sendEmptyMessage(H.RECHECK_ALL);
- mPendingRecheckAll = false;
- }
- }
- });
+ mDialogRowsView = mDialog.findViewById(R.id.volume_dialog_rows);
+ mFooter = mDialog.findViewById(R.id.footer);
+ mRingerIcon = mFooter.findViewById(R.id.ringer_icon);
+ mRingerStatus = mFooter.findViewById(R.id.ringer_status);
+ mRingerTitle = mFooter.findViewById(R.id.ringer_title);
+ mZenIcon = mFooter.findViewById(R.id.dnd_icon);
if (mRows.isEmpty()) {
addRow(AudioManager.STREAM_MUSIC,
- R.drawable.ic_volume_media, R.drawable.ic_volume_media_mute, true);
+ R.drawable.ic_volume_media, R.drawable.ic_volume_media_mute, true, true);
if (!AudioSystem.isSingleVolume(mContext)) {
addRow(AudioManager.STREAM_RING,
- R.drawable.ic_volume_ringer, R.drawable.ic_volume_ringer_mute, true);
+ R.drawable.ic_volume_ringer, R.drawable.ic_volume_ringer_mute, true, false);
addRow(AudioManager.STREAM_ALARM,
- R.drawable.ic_volume_alarm, R.drawable.ic_volume_alarm_mute, false);
+ R.drawable.ic_volume_alarm, R.drawable.ic_volume_alarm_mute, true, false);
addRow(AudioManager.STREAM_VOICE_CALL,
- R.drawable.ic_volume_voice, R.drawable.ic_volume_voice, false);
+ R.drawable.ic_volume_voice, R.drawable.ic_volume_voice, false, false);
addRow(AudioManager.STREAM_BLUETOOTH_SCO,
- R.drawable.ic_volume_bt_sco, R.drawable.ic_volume_bt_sco, false);
- addRow(AudioManager.STREAM_SYSTEM,
- R.drawable.ic_volume_system, R.drawable.ic_volume_system_mute, false);
- addRow(AudioManager.STREAM_ACCESSIBILITY, R.drawable.ic_volume_accessibility,
- R.drawable.ic_volume_accessibility, true);
+ R.drawable.ic_volume_bt_sco, R.drawable.ic_volume_bt_sco, false, false);
+ addRow(AudioManager.STREAM_SYSTEM, R.drawable.ic_volume_system,
+ R.drawable.ic_volume_system_mute, false, false);
+ addRow(STREAM_ACCESSIBILITY, R.drawable.ic_volume_accessibility,
+ R.drawable.ic_volume_accessibility, true, false);
}
} else {
addExistingRows();
}
- mExpandButtonAnimationDuration = res.getInteger(R.integer.volume_expand_animation_duration);
- mZenFooter = (ZenFooter) mDialog.findViewById(R.id.volume_zen_footer);
- mZenFooter.init(mZenModeController);
- mZenPanel = (TunerZenModePanel) mDialog.findViewById(R.id.tuner_zen_mode_panel);
- mZenPanel.init(mZenModeController);
- mZenPanel.setCallback(mZenPanelCallback);
+
+ updateRowsH(getActiveRow());
+ initRingerH();
}
- @Override
- public void onTuningChanged(String key, String newValue) {
- if (SHOW_FULL_ZEN.equals(key)) {
- mShowFullZen = newValue != null && Integer.parseInt(newValue) != 0;
- }
+ protected ViewGroup getDialogView() {
+ return mDialogView;
}
private ColorStateList loadColorStateList(int colorResId) {
return ColorStateList.valueOf(mContext.getColor(colorResId));
}
- private void updateWindowWidthH() {
- final ViewGroup.LayoutParams lp = mDialogView.getLayoutParams();
- final DisplayMetrics dm = mContext.getResources().getDisplayMetrics();
- if (D.BUG) Log.d(TAG, "updateWindowWidth dm.w=" + dm.widthPixels);
- int w = dm.widthPixels;
- final int max = mContext.getResources()
- .getDimensionPixelSize(R.dimen.volume_dialog_panel_width);
- if (w > max) {
- w = max;
- }
- lp.width = w;
- mDialogView.setLayoutParams(lp);
- }
-
public void setStreamImportant(int stream, boolean important) {
mHandler.obtainMessage(H.SET_STREAM_IMPORTANT, stream, important ? 1 : 0).sendToTarget();
}
@@ -328,20 +270,20 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
mHandler.sendEmptyMessage(H.RECHECK_ALL);
}
- private void addRow(int stream, int iconRes, int iconMuteRes, boolean important) {
- addRow(stream, iconRes, iconMuteRes, important, false);
+ private void addRow(int stream, int iconRes, int iconMuteRes, boolean important,
+ boolean defaultStream) {
+ addRow(stream, iconRes, iconMuteRes, important, defaultStream, false);
}
private void addRow(int stream, int iconRes, int iconMuteRes, boolean important,
- boolean dynamic) {
+ boolean defaultStream, boolean dynamic) {
+ if (D.BUG) Slog.d(TAG, "Adding row for stream " + stream);
VolumeRow row = new VolumeRow();
- initRow(row, stream, iconRes, iconMuteRes, important);
+ initRow(row, stream, iconRes, iconMuteRes, important, defaultStream);
int rowSize;
- int viewSize;
- if (mShowA11yStream && dynamic && (rowSize = mRows.size()) > 1
- && (viewSize = mDialogRowsView.getChildCount()) > 1) {
- // A11y Stream should be the last in the list
- mDialogRowsView.addView(row.view, viewSize - 2);
+ if (mShowA11yStream && dynamic && (rowSize = mRows.size()) > 1) {
+ // A11y Stream should be the first in the list, so it's shown to start of other rows
+ mDialogRowsView.addView(row.view, 0);
mRows.add(rowSize - 2, row);
} else {
mDialogRowsView.addView(row.view);
@@ -353,16 +295,13 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
int N = mRows.size();
for (int i = 0; i < N; i++) {
final VolumeRow row = mRows.get(i);
- initRow(row, row.stream, row.iconRes, row.iconMuteRes, row.important);
+ initRow(row, row.stream, row.iconRes, row.iconMuteRes, row.important,
+ row.defaultStream);
mDialogRowsView.addView(row.view);
+ updateVolumeRowH(row);
}
}
-
- private boolean isAttached() {
- return mDialogContentView != null && mDialogContentView.isAttachedToWindow();
- }
-
private VolumeRow getActiveRow() {
for (VolumeRow row : mRows) {
if (row.stream == mActiveStream) {
@@ -382,14 +321,10 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
public void dump(PrintWriter writer) {
writer.println(VolumeDialogImpl.class.getSimpleName() + " state:");
writer.print(" mShowing: "); writer.println(mShowing);
- writer.print(" mExpanded: "); writer.println(mExpanded);
- writer.print(" mExpandButtonAnimationRunning: ");
- writer.println(mExpandButtonAnimationRunning);
writer.print(" mActiveStream: "); writer.println(mActiveStream);
writer.print(" mDynamic: "); writer.println(mDynamic);
writer.print(" mAutomute: "); writer.println(mAutomute);
writer.print(" mSilentMode: "); writer.println(mSilentMode);
- writer.print(" mCollapseTime: "); writer.println(mCollapseTime);
writer.print(" mAccessibility.mFeedbackEnabled: ");
writer.println(mAccessibility.mFeedbackEnabled);
}
@@ -404,82 +339,101 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
@SuppressLint("InflateParams")
private void initRow(final VolumeRow row, final int stream, int iconRes, int iconMuteRes,
- boolean important) {
+ boolean important, boolean defaultStream) {
row.stream = stream;
row.iconRes = iconRes;
row.iconMuteRes = iconMuteRes;
row.important = important;
+ row.defaultStream = defaultStream;
row.view = mDialog.getLayoutInflater().inflate(R.layout.volume_dialog_row, null);
row.view.setId(row.stream);
row.view.setTag(row);
- row.header = (TextView) row.view.findViewById(R.id.volume_row_header);
+ row.header = row.view.findViewById(R.id.volume_row_header);
row.header.setId(20 * row.stream);
- row.slider = (SeekBar) row.view.findViewById(R.id.volume_row_slider);
+ if (stream == STREAM_ACCESSIBILITY) {
+ row.header.setFilters(new InputFilter[] {new InputFilter.LengthFilter(13)});
+ }
+ row.dndIcon = row.view.findViewById(R.id.dnd_icon);
+ row.slider = row.view.findViewById(R.id.volume_row_slider);
row.slider.setOnSeekBarChangeListener(new VolumeSeekBarChangeListener(row));
row.anim = null;
- // forward events above the slider into the slider
- row.view.setOnTouchListener(new OnTouchListener() {
- private final Rect mSliderHitRect = new Rect();
- private boolean mDragging;
-
- @SuppressLint("ClickableViewAccessibility")
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- row.slider.getHitRect(mSliderHitRect);
- if (!mDragging && event.getActionMasked() == MotionEvent.ACTION_DOWN
- && event.getY() < mSliderHitRect.top) {
- mDragging = true;
- }
- if (mDragging) {
- event.offsetLocation(-mSliderHitRect.left, -mSliderHitRect.top);
- row.slider.dispatchTouchEvent(event);
- if (event.getActionMasked() == MotionEvent.ACTION_UP
- || event.getActionMasked() == MotionEvent.ACTION_CANCEL) {
- mDragging = false;
- }
- return true;
- }
- return false;
- }
- });
+ row.outputChooser = row.view.findViewById(R.id.output_chooser);
+ row.outputChooser.setOnClickListener(mClickOutputChooser);
+ row.connectedDevice = row.view.findViewById(R.id.volume_row_connected_device);
+
row.icon = row.view.findViewById(R.id.volume_row_icon);
row.icon.setImageResource(iconRes);
if (row.stream != AudioSystem.STREAM_ACCESSIBILITY) {
- row.icon.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- Events.writeEvent(mContext, Events.EVENT_ICON_CLICK, row.stream, row.iconState);
- mController.setActiveStream(row.stream);
- if (row.stream == AudioManager.STREAM_RING) {
- final boolean hasVibrator = mController.hasVibrator();
- if (mState.ringerModeInternal == AudioManager.RINGER_MODE_NORMAL) {
- if (hasVibrator) {
- mController.setRingerMode(AudioManager.RINGER_MODE_VIBRATE, false);
- } else {
- final boolean wasZero = row.ss.level == 0;
- mController.setStreamVolume(stream,
- wasZero ? row.lastAudibleLevel : 0);
- }
+ row.icon.setOnClickListener(v -> {
+ Events.writeEvent(mContext, Events.EVENT_ICON_CLICK, row.stream, row.iconState);
+ mController.setActiveStream(row.stream);
+ if (row.stream == AudioManager.STREAM_RING) {
+ final boolean hasVibrator = mController.hasVibrator();
+ if (mState.ringerModeInternal == AudioManager.RINGER_MODE_NORMAL) {
+ if (hasVibrator) {
+ mController.setRingerMode(AudioManager.RINGER_MODE_VIBRATE, false);
} else {
- mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false);
- if (row.ss.level == 0) {
- mController.setStreamVolume(stream, 1);
- }
+ final boolean wasZero = row.ss.level == 0;
+ mController.setStreamVolume(stream,
+ wasZero ? row.lastAudibleLevel : 0);
}
} else {
- final boolean vmute = row.ss.level == row.ss.levelMin;
- mController.setStreamVolume(stream,
- vmute ? row.lastAudibleLevel : row.ss.levelMin);
+ mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false);
+ if (row.ss.level == 0) {
+ mController.setStreamVolume(stream, 1);
+ }
}
- row.userAttempt = 0; // reset the grace period, slider updates immediately
+ } else {
+ final boolean vmute = row.ss.level == row.ss.levelMin;
+ mController.setStreamVolume(stream,
+ vmute ? row.lastAudibleLevel : row.ss.levelMin);
}
+ row.userAttempt = 0; // reset the grace period, slider updates immediately
});
} else {
row.icon.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
}
}
+ public void initRingerH() {
+ mRingerIcon.setOnClickListener(v -> {
+ Events.writeEvent(mContext, Events.EVENT_ICON_CLICK, AudioManager.STREAM_RING,
+ mRingerIcon.getTag());
+ final StreamState ss = mState.states.get(AudioManager.STREAM_RING);
+ if (ss == null) {
+ return;
+ }
+ // normal -> vibrate -> silent -> normal (skip vibrate if device doesn't have
+ // a vibrator.
+ final boolean hasVibrator = mController.hasVibrator();
+ if (mState.ringerModeInternal == AudioManager.RINGER_MODE_NORMAL) {
+ if (hasVibrator) {
+ mController.vibrate();
+ mController.setRingerMode(AudioManager.RINGER_MODE_VIBRATE, false);
+ } else {
+ mController.setRingerMode(AudioManager.RINGER_MODE_SILENT, false);
+ }
+ } else if (mState.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE) {
+ mController.setRingerMode(AudioManager.RINGER_MODE_SILENT, false);
+ } else {
+ mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false);
+ if (ss.level == 0) {
+ mController.setStreamVolume(AudioManager.STREAM_RING, 1);
+ }
+ }
+ updateRingerH();
+ });
+ mRingerIcon.setOnLongClickListener(v -> {
+ Intent intent = new Intent(Settings.ACTION_SOUND_SETTINGS);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ dismissH(DISMISS_REASON_SETTINGS_CLICKED);
+ Dependency.get(ActivityStarter.class).startActivity(intent, true /* dismissShade */);
+ return true;
+ });
+ updateRingerH();
+ }
+
public void show(int reason) {
mHandler.obtainMessage(H.SHOW, reason, 0).sendToTarget();
}
@@ -495,7 +449,8 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
rescheduleTimeoutH();
if (mShowing) return;
mShowing = true;
- mMotion.startShow();
+
+ mDialog.show();
Events.writeEvent(mContext, Events.EVENT_SHOW_DIALOG, reason, mKeyguard.isKeyguardLocked());
mController.notifyVisible(true);
}
@@ -513,37 +468,29 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
if (mAccessibility.mFeedbackEnabled) return 20000;
if (mHovering) return 16000;
if (mSafetyWarning != null) return 5000;
- if (mExpanded || mExpandButtonAnimationRunning) return 5000;
- if (mActiveStream == AudioManager.STREAM_MUSIC) return 1500;
- if (mZenFooter.shouldShowIntroduction()) {
- return 6000;
- }
return 3000;
}
protected void dismissH(int reason) {
- if (mMotion.isAnimating()) {
- return;
- }
mHandler.removeMessages(H.DISMISS);
mHandler.removeMessages(H.SHOW);
if (!mShowing) return;
+ mDialogView.animate().cancel();
mShowing = false;
- mMotion.startDismiss(new Runnable() {
- @Override
- public void run() {
- updateExpandedH(false /* expanding */, true /* dismissing */);
- }
- });
- if (mAccessibilityMgr.isEnabled()) {
- AccessibilityEvent event =
- AccessibilityEvent.obtain(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
- event.setPackageName(mContext.getPackageName());
- event.setClassName(CustomDialog.class.getSuperclass().getName());
- event.getText().add(mContext.getString(
- R.string.volume_dialog_accessibility_dismissed_message));
- mAccessibilityMgr.sendAccessibilityEvent(event);
- }
+
+ mDialogView.setTranslationX(0);
+ mDialogView.setAlpha(1);
+ mDialogView.animate()
+ .alpha(0)
+ .translationX(mDialogView.getWidth() / 2)
+ .setDuration(250)
+ .setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator())
+ .withEndAction(() -> mHandler.postDelayed(() -> {
+ if (D.BUG) Log.d(TAG, "mDialog.dismiss()");
+ mDialog.dismiss();
+ }, 50))
+ .start();
+
Events.writeEvent(mContext, Events.EVENT_DISMISS_DIALOG, reason);
mController.notifyVisible(false);
synchronized (mSafetyWarningLock) {
@@ -554,81 +501,13 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
}
}
- private void updateDialogBottomMarginH() {
- final long diff = System.currentTimeMillis() - mCollapseTime;
- final boolean collapsing = mCollapseTime != 0 && diff < getConservativeCollapseDuration();
- final ViewGroup.MarginLayoutParams mlp = (MarginLayoutParams) mDialogView.getLayoutParams();
- final int bottomMargin = collapsing ? mDialogContentView.getHeight() :
- mContext.getResources().getDimensionPixelSize(R.dimen.volume_dialog_margin_bottom);
- if (bottomMargin != mlp.bottomMargin) {
- if (D.BUG) Log.d(TAG, "bottomMargin " + mlp.bottomMargin + " -> " + bottomMargin);
- mlp.bottomMargin = bottomMargin;
- mDialogView.setLayoutParams(mlp);
- }
- }
-
- private long getConservativeCollapseDuration() {
- return mExpandButtonAnimationDuration * 3;
- }
-
- private void prepareForCollapse() {
- mHandler.removeMessages(H.UPDATE_BOTTOM_MARGIN);
- mCollapseTime = System.currentTimeMillis();
- updateDialogBottomMarginH();
- mHandler.sendEmptyMessageDelayed(H.UPDATE_BOTTOM_MARGIN, getConservativeCollapseDuration());
- }
-
- private void updateExpandedH(final boolean expanded, final boolean dismissing) {
- if (mExpanded == expanded) return;
- mExpanded = expanded;
- mExpandButtonAnimationRunning = isAttached();
- if (D.BUG) Log.d(TAG, "updateExpandedH " + expanded);
- updateExpandButtonH();
- updateFooterH();
- TransitionManager.endTransitions(mDialogView);
- final VolumeRow activeRow = getActiveRow();
- if (!dismissing) {
- mWindow.setLayout(mWindow.getAttributes().width, ViewGroup.LayoutParams.MATCH_PARENT);
- TransitionManager.beginDelayedTransition(mDialogView, getTransition());
- }
- updateRowsH(activeRow);
- rescheduleTimeoutH();
+ private boolean isAttached() {
+ return mDialogView != null && mDialogView.isAttachedToWindow();
}
- private void updateExpandButtonH() {
- if (D.BUG) Log.d(TAG, "updateExpandButtonH");
- mExpandButton.setClickable(!mExpandButtonAnimationRunning);
- if (!(mExpandButtonAnimationRunning && isAttached())) {
- final int res = mExpanded ? R.drawable.ic_volume_collapse_animation
- : R.drawable.ic_volume_expand_animation;
- if (hasTouchFeature()) {
- mExpandButton.setImageResource(res);
- } else {
- // if there is no touch feature, show the volume ringer instead
- mExpandButton.setImageResource(R.drawable.ic_volume_ringer);
- mExpandButton.setBackgroundResource(0); // remove gray background emphasis
- }
- mExpandButton.setContentDescription(mContext.getString(mExpanded ?
- R.string.accessibility_volume_collapse : R.string.accessibility_volume_expand));
- }
- if (mExpandButtonAnimationRunning) {
- final Drawable d = mExpandButton.getDrawable();
- if (d instanceof AnimatedVectorDrawable) {
- // workaround to reset drawable
- final AnimatedVectorDrawable avd = (AnimatedVectorDrawable) d.getConstantState()
- .newDrawable();
- mExpandButton.setImageDrawable(avd);
- avd.start();
- mHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- mExpandButtonAnimationRunning = false;
- updateExpandButtonH();
- rescheduleTimeoutH();
- }
- }, mExpandButtonAnimationDuration);
- }
- }
+ private boolean hasTouchFeature() {
+ final PackageManager pm = mContext.getPackageManager();
+ return pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN);
}
private boolean shouldBeVisibleH(VolumeRow row, VolumeRow activeRow) {
@@ -638,15 +517,13 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
}
// if the active row is accessibility, then continue to display previous
- // active row since accessibility is dispalyed under it
+ // active row since accessibility is displayed under it
if (activeRow.stream == AudioSystem.STREAM_ACCESSIBILITY &&
row.stream == mPrevActiveStream) {
return true;
}
- return mExpanded && row.view.getVisibility() == View.VISIBLE
- || (mExpanded && (row.important || isActive))
- || !mExpanded && isActive;
+ return row.defaultStream || isActive;
}
private void updateRowsH(final VolumeRow activeRow) {
@@ -666,6 +543,87 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
}
}
+ protected void updateConnectedDeviceH(String deviceName) {
+ for (final VolumeRow row : mRows) {
+ row.connectedDevice.setText(deviceName);
+ Util.setVisOrGone(row.connectedDevice, !TextUtils.isEmpty(deviceName));
+ }
+ }
+
+ protected void updateRingerH() {
+ if (mState != null) {
+ final StreamState ss = mState.states.get(AudioManager.STREAM_RING);
+ if (ss == null) {
+ return;
+ }
+
+ enableRingerViewsH(mState.zenMode == Global.ZEN_MODE_OFF || !mState.disallowRinger);
+ switch (mState.ringerModeInternal) {
+ case AudioManager.RINGER_MODE_VIBRATE:
+ mRingerStatus.setText(R.string.volume_ringer_status_vibrate);
+ mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_vibrate);
+ mRingerIcon.setTag(Events.ICON_STATE_VIBRATE);
+ break;
+ case AudioManager.RINGER_MODE_SILENT:
+ mRingerStatus.setText(R.string.volume_ringer_status_silent);
+ mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute);
+ mRingerIcon.setContentDescription(mContext.getString(
+ R.string.volume_stream_content_description_unmute,
+ getStreamLabelH(ss)));
+ mRingerIcon.setTag(Events.ICON_STATE_MUTE);
+ break;
+ case AudioManager.RINGER_MODE_NORMAL:
+ default:
+ boolean muted = (mAutomute && ss.level == 0) || ss.muted;
+ if (muted) {
+ mRingerStatus.setText(R.string.volume_ringer_status_silent);
+ mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute);
+ mRingerIcon.setContentDescription(mContext.getString(
+ R.string.volume_stream_content_description_unmute,
+ getStreamLabelH(ss)));
+ mRingerIcon.setTag(Events.ICON_STATE_MUTE);
+ } else {
+ mRingerStatus.setText(R.string.volume_ringer_status_normal);
+ mRingerIcon.setImageResource(R.drawable.ic_volume_ringer);
+ if (mController.hasVibrator()) {
+ mRingerIcon.setContentDescription(mContext.getString(
+ mShowA11yStream
+ ? R.string.volume_stream_content_description_vibrate_a11y
+ : R.string.volume_stream_content_description_vibrate,
+ getStreamLabelH(ss)));
+
+ } else {
+ mRingerIcon.setContentDescription(getStreamLabelH(ss));
+ }
+ mRingerIcon.setTag(Events.ICON_STATE_UNMUTE);
+ }
+ break;
+ }
+ }
+ }
+
+ /**
+ * Toggles enable state of views in a VolumeRow (not including seekbar, outputChooser or icon)
+ * Hides/shows zen icon
+ * @param enable whether to enable volume row views and hide dnd icon
+ */
+ private void enableVolumeRowViewsH(VolumeRow row, boolean enable) {
+ row.header.setEnabled(enable);
+ row.dndIcon.setVisibility(enable ? View.GONE : View.VISIBLE);
+ }
+
+ /**
+ * Toggles enable state of footer/ringer views
+ * Hides/shows zen icon
+ * @param enable whether to enable ringer views and hide dnd icon
+ */
+ private void enableRingerViewsH(boolean enable) {
+ mRingerTitle.setEnabled(enable);
+ mRingerStatus.setEnabled(enable);
+ mRingerIcon.setEnabled(enable);
+ mZenIcon.setVisibility(enable ? View.GONE : View.VISIBLE);
+ }
+
private void trimObsoleteH() {
if (D.BUG) Log.d(TAG, "trimObsoleteH");
for (int i = mRows.size() - 1; i >= 0; i--) {
@@ -678,14 +636,8 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
}
}
- private void onStateChangedH(State state) {
- final boolean animating = mMotion.isAnimating();
- if (D.BUG) Log.d(TAG, "onStateChangedH animating=" + animating);
+ protected void onStateChangedH(State state) {
mState = state;
- if (animating) {
- mPendingStateChanged = true;
- return;
- }
mDynamic.clear();
// add any new dynamic rows
for (int i = 0; i < state.states.size(); i++) {
@@ -695,7 +647,7 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
mDynamic.put(stream, true);
if (findRow(stream) == null) {
addRow(stream, R.drawable.ic_volume_remote, R.drawable.ic_volume_remote_mute, true,
- true);
+ false, true);
}
}
@@ -708,38 +660,9 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
for (VolumeRow row : mRows) {
updateVolumeRowH(row);
}
- updateFooterH();
- }
-
- private void updateFooterH() {
- if (D.BUG) Log.d(TAG, "updateFooterH");
- final boolean wasVisible = mZenFooter.getVisibility() == View.VISIBLE;
- final boolean visible = mState.zenMode != Global.ZEN_MODE_OFF
- && (mAudioManager.isStreamAffectedByRingerMode(mActiveStream) || mExpanded)
- && !mZenPanel.isEditing();
-
- TransitionManager.endTransitions(mDialogView);
- TransitionManager.beginDelayedTransition(mDialogView, getTransition());
- if (wasVisible != visible && !visible) {
- prepareForCollapse();
- }
- Util.setVisOrGone(mZenFooter, visible);
- mZenFooter.update();
-
- final boolean fullWasVisible = mZenPanel.getVisibility() == View.VISIBLE;
- final boolean fullVisible = mShowFullZen && !visible;
- if (fullWasVisible != fullVisible) {
- Util.setVisOrGone(mZenPanel, fullVisible);
- if (fullVisible) {
- mZenPanel.setZenState(mState.zenMode);
- mZenPanel.setDoneListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- mHandler.sendEmptyMessage(H.UPDATE_FOOTER);
- }
- });
- }
- }
+ updateRingerH();
+ mWindow.setTitle(mContext.getString(R.string.volume_dialog_title,
+ getStreamLabelH(getActiveRow().ss)));
}
private void updateVolumeRowH(VolumeRow row) {
@@ -754,7 +677,7 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
if (ss.level == row.requestedLevel) {
row.requestedLevel = -1;
}
- final boolean isA11yStream = row.stream == AudioManager.STREAM_ACCESSIBILITY;
+ final boolean isA11yStream = row.stream == STREAM_ACCESSIBILITY;
final boolean isRingStream = row.stream == AudioManager.STREAM_RING;
final boolean isSystemStream = row.stream == AudioManager.STREAM_SYSTEM;
final boolean isAlarmStream = row.stream == AudioManager.STREAM_ALARM;
@@ -763,10 +686,14 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
&& mState.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE;
final boolean isRingSilent = isRingStream
&& mState.ringerModeInternal == AudioManager.RINGER_MODE_SILENT;
+ final boolean isZenPriorityOnly = mState.zenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
final boolean isZenAlarms = mState.zenMode == Global.ZEN_MODE_ALARMS;
final boolean isZenNone = mState.zenMode == Global.ZEN_MODE_NO_INTERRUPTIONS;
final boolean zenMuted = isZenAlarms ? (isRingStream || isSystemStream)
: isZenNone ? (isRingStream || isSystemStream || isAlarmStream || isMusicStream)
+ : isZenPriorityOnly ? ((isAlarmStream && mState.disallowAlarms) ||
+ (isMusicStream && mState.disallowMedia) ||
+ (isRingStream && mState.disallowRinger))
: false;
// update slider max
@@ -786,19 +713,13 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
row.icon.setAlpha(iconEnabled ? 1 : 0.5f);
final int iconRes =
isRingVibrate ? R.drawable.ic_volume_ringer_vibrate
- : isRingSilent || zenMuted ? row.cachedIconRes
+ : isRingSilent || zenMuted ? row.iconMuteRes
: ss.routedToBluetooth ?
(ss.muted ? R.drawable.ic_volume_media_bt_mute
: R.drawable.ic_volume_media_bt)
: mAutomute && ss.level == 0 ? row.iconMuteRes
: (ss.muted ? row.iconMuteRes : row.iconRes);
- if (iconRes != row.cachedIconRes) {
- if (row.cachedIconRes != 0 && isRingVibrate) {
- mController.vibrate();
- }
- row.cachedIconRes = iconRes;
- row.icon.setImageResource(iconRes);
- }
+ row.icon.setImageResource(iconRes);
row.iconState =
iconRes == R.drawable.ic_volume_ringer_vibrate ? Events.ICON_STATE_VIBRATE
: (iconRes == R.drawable.ic_volume_media_bt_mute || iconRes == row.iconMuteRes)
@@ -850,6 +771,7 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
if (zenMuted) {
row.tracking = false;
}
+ enableVolumeRowViewsH(row, !zenMuted);
// update slider
final boolean enableSlider = !zenMuted;
@@ -859,7 +781,7 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
}
private void updateVolumeRowSliderTintH(VolumeRow row, boolean isActive) {
- if (isActive && mExpanded) {
+ if (isActive) {
row.slider.requestFocus();
}
final ColorStateList tint = isActive && row.slider.isEnabled() ? mActiveSliderTint
@@ -967,54 +889,43 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
rescheduleTimeoutH();
}
+ private void showOutputChooserH() {
+ synchronized (mOutputChooserLock) {
+ if (mOutputChooserDialog != null) {
+ return;
+ }
+ mOutputChooserDialog = new OutputChooserDialog(mContext,
+ new MediaRouterWrapper(MediaRouter.getInstance(mContext))) {
+ @Override
+ protected void cleanUp() {
+ synchronized (mOutputChooserLock) {
+ mOutputChooserDialog = null;
+ }
+ }
+ };
+ mOutputChooserDialog.show();
+ }
+ }
+
private String getStreamLabelH(StreamState ss) {
if (ss.remoteLabel != null) {
return ss.remoteLabel;
}
try {
- return mContext.getString(ss.name);
+ return mContext.getResources().getString(ss.name);
} catch (Resources.NotFoundException e) {
Slog.e(TAG, "Can't find translation for stream " + ss);
return "";
}
}
- private AutoTransition getTransition() {
- AutoTransition transition = new AutoTransition();
- transition.setDuration(mExpandButtonAnimationDuration);
- transition.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
- transition.addListener(new Transition.TransitionListener() {
- @Override
- public void onTransitionStart(Transition transition) {
- }
-
- @Override
- public void onTransitionEnd(Transition transition) {
- mWindow.setLayout(
- mWindow.getAttributes().width, ViewGroup.LayoutParams.WRAP_CONTENT);
- }
-
- @Override
- public void onTransitionCancel(Transition transition) {
- }
-
- @Override
- public void onTransitionPause(Transition transition) {
- mWindow.setLayout(
- mWindow.getAttributes().width, ViewGroup.LayoutParams.WRAP_CONTENT);
- }
-
- @Override
- public void onTransitionResume(Transition transition) {
- }
- });
- return transition;
- }
-
- private boolean hasTouchFeature() {
- final PackageManager pm = mContext.getPackageManager();
- return pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN);
- }
+ private final OnClickListener mClickOutputChooser = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ dismissH(DISMISS_REASON_OUTPUT_CHOOSER);
+ showOutputChooserH();
+ }
+ };
private final VolumeDialogController.Callbacks mControllerCallbackH
= new VolumeDialogController.Callbacks() {
@@ -1045,17 +956,9 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
@Override
public void onConfigurationChanged() {
- Configuration newConfig = mContext.getResources().getConfiguration();
- final int density = newConfig.densityDpi;
- if (density != mDensity) {
- mDialog.dismiss();
- mZenFooter.cleanup();
- initDialog();
- mDensity = density;
- }
- updateWindowWidthH();
+ mDialog.dismiss();
+ initDialog();
mConfigurableTexts.update();
- mZenFooter.onConfigurationChanged();
}
@Override
@@ -1079,42 +982,19 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
@Override
public void onAccessibilityModeChanged(Boolean showA11yStream) {
- boolean show = showA11yStream == null ? false : showA11yStream;
- mShowA11yStream = show;
+ mShowA11yStream = showA11yStream == null ? false : showA11yStream;
VolumeRow activeRow = getActiveRow();
- if (!mShowA11yStream && AudioManager.STREAM_ACCESSIBILITY == activeRow.stream) {
+ if (!mShowA11yStream && STREAM_ACCESSIBILITY == activeRow.stream) {
dismissH(Events.DISMISS_STREAM_GONE);
} else {
updateRowsH(activeRow);
}
}
- };
-
- private final ZenModePanel.Callback mZenPanelCallback = new ZenModePanel.Callback() {
- @Override
- public void onPrioritySettings() {
- mCallback.onZenPrioritySettingsClicked();
- }
-
- @Override
- public void onInteraction() {
- mHandler.sendEmptyMessage(H.RESCHEDULE_TIMEOUT);
- }
@Override
- public void onExpanded(boolean expanded) {
- // noop.
- }
- };
-
- private final OnClickListener mClickExpand = new OnClickListener() {
- @Override
- public void onClick(View v) {
- if (mExpandButtonAnimationRunning) return;
- final boolean newExpand = !mExpanded;
- Events.writeEvent(mContext, Events.EVENT_EXPAND, newExpand);
- updateExpandedH(newExpand, false /* dismissing */);
+ public void onConnectedDeviceChanged(String deviceName) {
+ updateConnectedDeviceH(deviceName);
}
};
@@ -1126,8 +1006,6 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
private static final int SET_STREAM_IMPORTANT = 5;
private static final int RESCHEDULE_TIMEOUT = 6;
private static final int STATE_CHANGED = 7;
- private static final int UPDATE_BOTTOM_MARGIN = 8;
- private static final int UPDATE_FOOTER = 9;
public H() {
super(Looper.getMainLooper());
@@ -1143,15 +1021,13 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
case SET_STREAM_IMPORTANT: setStreamImportantH(msg.arg1, msg.arg2 != 0); break;
case RESCHEDULE_TIMEOUT: rescheduleTimeoutH(); break;
case STATE_CHANGED: onStateChangedH(mState); break;
- case UPDATE_BOTTOM_MARGIN: updateDialogBottomMarginH(); break;
- case UPDATE_FOOTER: updateFooterH(); break;
}
}
}
- private final class CustomDialog extends Dialog {
+ private final class CustomDialog extends Dialog implements DialogInterface {
public CustomDialog(Context context) {
- super(context);
+ super(context, com.android.systemui.R.style.qs_theme);
}
@Override
@@ -1161,14 +1037,14 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
}
@Override
+ protected void onStart() {
+ super.setCanceledOnTouchOutside(true);
+ super.onStart();
+ }
+
+ @Override
protected void onStop() {
super.onStop();
- final boolean animating = mMotion.isAnimating();
- if (D.BUG) Log.d(TAG, "onStop animating=" + animating);
- if (animating) {
- mPendingRecheckAll = true;
- return;
- }
mHandler.sendEmptyMessage(H.RECHECK_ALL);
}
@@ -1182,27 +1058,6 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
}
return false;
}
-
- @Override
- public boolean dispatchPopulateAccessibilityEvent(@NonNull AccessibilityEvent event) {
- event.setClassName(getClass().getSuperclass().getName());
- event.setPackageName(mContext.getPackageName());
-
- ViewGroup.LayoutParams params = getWindow().getAttributes();
- boolean isFullScreen = (params.width == ViewGroup.LayoutParams.MATCH_PARENT) &&
- (params.height == ViewGroup.LayoutParams.MATCH_PARENT);
- event.setFullScreen(isFullScreen);
-
- if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
- if (mShowing) {
- event.getText().add(mContext.getString(
- R.string.volume_dialog_accessibility_shown_message,
- getStreamLabelH(getActiveRow().ss)));
- return true;
- }
- }
- return false;
- }
}
private final class VolumeSeekBarChangeListener implements OnSeekBarChangeListener {
@@ -1323,11 +1178,14 @@ public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
private int iconRes;
private int iconMuteRes;
private boolean important;
- private int cachedIconRes;
+ private boolean defaultStream;
private ColorStateList cachedSliderTint;
private int iconState; // from Events
private ObjectAnimator anim; // slider progress animation for non-touch-related updates
private int animTargetProgress;
private int lastAudibleLevel = 1;
+ private View outputChooser;
+ private TextView connectedDevice;
+ private ImageView dndIcon;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogMotion.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogMotion.java
deleted file mode 100644
index 01d31e2a9852..000000000000
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogMotion.java
+++ /dev/null
@@ -1,317 +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.volume;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.app.Dialog;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnDismissListener;
-import android.content.DialogInterface.OnShowListener;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.util.Log;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.animation.PathInterpolator;
-
-public class VolumeDialogMotion {
- private static final String TAG = Util.logTag(VolumeDialogMotion.class);
-
- private static final float ANIMATION_SCALE = 1.0f;
- private static final int PRE_DISMISS_DELAY = 50;
-
- private final Dialog mDialog;
- private final View mDialogView;
- private final ViewGroup mContents; // volume rows + zen footer
- private final View mChevron;
- private final Handler mHandler = new Handler();
- private final Callback mCallback;
-
- private boolean mAnimating; // show or dismiss animation is running
- private boolean mShowing; // show animation is running
- private boolean mDismissing; // dismiss animation is running
- private ValueAnimator mChevronPositionAnimator;
- private ValueAnimator mContentsPositionAnimator;
-
- public VolumeDialogMotion(Dialog dialog, View dialogView, ViewGroup contents, View chevron,
- Callback callback) {
- mDialog = dialog;
- mDialogView = dialogView;
- mContents = contents;
- mChevron = chevron;
- mCallback = callback;
- mDialog.setOnDismissListener(new OnDismissListener() {
- @Override
- public void onDismiss(DialogInterface dialog) {
- if (D.BUG) Log.d(TAG, "mDialog.onDismiss");
- }
- });
- mDialog.setOnShowListener(new OnShowListener() {
- @Override
- public void onShow(DialogInterface dialog) {
- if (D.BUG) Log.d(TAG, "mDialog.onShow");
- final int h = mDialogView.getHeight();
- mDialogView.setTranslationY(-h);
- startShowAnimation();
- }
- });
- }
-
- public boolean isAnimating() {
- return mAnimating;
- }
-
- private void setShowing(boolean showing) {
- if (showing == mShowing) return;
- mShowing = showing;
- if (D.BUG) Log.d(TAG, "mShowing = " + mShowing);
- updateAnimating();
- }
-
- private void setDismissing(boolean dismissing) {
- if (dismissing == mDismissing) return;
- mDismissing = dismissing;
- if (D.BUG) Log.d(TAG, "mDismissing = " + mDismissing);
- updateAnimating();
- }
-
- private void updateAnimating() {
- final boolean animating = mShowing || mDismissing;
- if (animating == mAnimating) return;
- mAnimating = animating;
- if (D.BUG) Log.d(TAG, "mAnimating = " + mAnimating);
- if (mCallback != null) {
- mCallback.onAnimatingChanged(mAnimating);
- }
- }
-
- public void startShow() {
- if (D.BUG) Log.d(TAG, "startShow");
- if (mShowing) return;
- setShowing(true);
- if (mDismissing) {
- mDialogView.animate().cancel();
- setDismissing(false);
- startShowAnimation();
- return;
- }
- if (D.BUG) Log.d(TAG, "mDialog.show()");
- mDialog.show();
- }
-
- private int chevronDistance() {
- return mChevron.getHeight() / 6;
- }
-
- private int chevronPosY() {
- final Object tag = mChevron == null ? null : mChevron.getTag();
- return tag == null ? 0 : (Integer) tag;
- }
-
- private void startShowAnimation() {
- if (D.BUG) Log.d(TAG, "startShowAnimation");
- mDialogView.animate()
- .translationY(0)
- .setDuration(scaledDuration(300))
- .setInterpolator(new LogDecelerateInterpolator())
- .setListener(null)
- .setUpdateListener(animation -> {
- if (mChevronPositionAnimator != null) {
- final float v = (Float) mChevronPositionAnimator.getAnimatedValue();
- if (mChevronPositionAnimator == null) return;
- // reposition chevron
- final int posY = chevronPosY();
- mChevron.setTranslationY(posY + v + -mDialogView.getTranslationY());
- }
- })
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- if (mChevronPositionAnimator == null) return;
- // reposition chevron
- final int posY = chevronPosY();
- mChevron.setTranslationY(posY + -mDialogView.getTranslationY());
- }
- })
- .start();
-
- mContentsPositionAnimator = ValueAnimator.ofFloat(-chevronDistance(), 0)
- .setDuration(scaledDuration(400));
- mContentsPositionAnimator.addListener(new AnimatorListenerAdapter() {
- private boolean mCancelled;
-
- @Override
- public void onAnimationEnd(Animator animation) {
- if (mCancelled) return;
- if (D.BUG) Log.d(TAG, "show.onAnimationEnd");
- setShowing(false);
- }
- @Override
- public void onAnimationCancel(Animator animation) {
- if (D.BUG) Log.d(TAG, "show.onAnimationCancel");
- mCancelled = true;
- }
- });
- mContentsPositionAnimator.addUpdateListener(new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- float v = (Float) animation.getAnimatedValue();
- mContents.setTranslationY(v + -mDialogView.getTranslationY());
- }
- });
- mContentsPositionAnimator.setInterpolator(new LogDecelerateInterpolator());
- mContentsPositionAnimator.start();
-
- mContents.setAlpha(0);
- mContents.animate()
- .alpha(1)
- .setDuration(scaledDuration(150))
- .setInterpolator(new PathInterpolator(0f, 0f, .2f, 1f))
- .start();
-
- mChevronPositionAnimator = ValueAnimator.ofFloat(-chevronDistance(), 0)
- .setDuration(scaledDuration(250));
- mChevronPositionAnimator.setInterpolator(new PathInterpolator(.4f, 0f, .2f, 1f));
- mChevronPositionAnimator.start();
-
- mChevron.setAlpha(0);
- mChevron.animate()
- .alpha(1)
- .setStartDelay(scaledDuration(50))
- .setDuration(scaledDuration(150))
- .setInterpolator(new PathInterpolator(.4f, 0f, 1f, 1f))
- .start();
- }
-
- public void startDismiss(final Runnable onComplete) {
- if (D.BUG) Log.d(TAG, "startDismiss");
- if (mDismissing) return;
- setDismissing(true);
- if (mShowing) {
- mDialogView.animate().cancel();
- if (mContentsPositionAnimator != null) {
- mContentsPositionAnimator.cancel();
- }
- mContents.animate().cancel();
- if (mChevronPositionAnimator != null) {
- mChevronPositionAnimator.cancel();
- }
- mChevron.animate().cancel();
- setShowing(false);
- }
- mDialogView.animate()
- .translationY(-mDialogView.getHeight())
- .setDuration(scaledDuration(250))
- .setInterpolator(new LogAccelerateInterpolator())
- .setUpdateListener(new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- mContents.setTranslationY(-mDialogView.getTranslationY());
- final int posY = chevronPosY();
- mChevron.setTranslationY(posY + -mDialogView.getTranslationY());
- }
- })
- .setListener(new AnimatorListenerAdapter() {
- private boolean mCancelled;
- @Override
- public void onAnimationEnd(Animator animation) {
- if (mCancelled) return;
- if (D.BUG) Log.d(TAG, "dismiss.onAnimationEnd");
- mHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- if (D.BUG) Log.d(TAG, "mDialog.dismiss()");
- mDialog.dismiss();
- onComplete.run();
- setDismissing(false);
- }
- }, PRE_DISMISS_DELAY);
-
- }
- @Override
- public void onAnimationCancel(Animator animation) {
- if (D.BUG) Log.d(TAG, "dismiss.onAnimationCancel");
- mCancelled = true;
- }
- }).start();
- }
-
- private static int scaledDuration(int base) {
- return (int) (base * ANIMATION_SCALE);
- }
-
- public static final class LogDecelerateInterpolator implements TimeInterpolator {
- private final float mBase;
- private final float mDrift;
- private final float mTimeScale;
- private final float mOutputScale;
-
- public LogDecelerateInterpolator() {
- this(400f, 1.4f, 0);
- }
-
- private LogDecelerateInterpolator(float base, float timeScale, float drift) {
- mBase = base;
- mDrift = drift;
- mTimeScale = 1f / timeScale;
-
- mOutputScale = 1f / computeLog(1f);
- }
-
- private float computeLog(float t) {
- return 1f - (float) Math.pow(mBase, -t * mTimeScale) + (mDrift * t);
- }
-
- @Override
- public float getInterpolation(float t) {
- return computeLog(t) * mOutputScale;
- }
- }
-
- public static final class LogAccelerateInterpolator implements TimeInterpolator {
- private final int mBase;
- private final int mDrift;
- private final float mLogScale;
-
- public LogAccelerateInterpolator() {
- this(100, 0);
- }
-
- private LogAccelerateInterpolator(int base, int drift) {
- mBase = base;
- mDrift = drift;
- mLogScale = 1f / computeLog(1, mBase, mDrift);
- }
-
- private static float computeLog(float t, int base, int drift) {
- return (float) -Math.pow(base, -t) + 1 + (drift * t);
- }
-
- @Override
- public float getInterpolation(float t) {
- return 1 - computeLog(1 - t, mBase, mDrift) * mLogScale;
- }
- }
-
- public interface Callback {
- void onAnimatingChanged(boolean animating);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
index 02969e483e97..6f65b081afc4 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
@@ -40,9 +40,13 @@ public class VolumeUI extends SystemUI {
@Override
public void start() {
- mEnabled = mContext.getResources().getBoolean(R.bool.enable_volume_ui);
+ boolean enableVolumeUi = mContext.getResources().getBoolean(R.bool.enable_volume_ui);
+ boolean enableSafetyWarning =
+ mContext.getResources().getBoolean(R.bool.enable_safety_warning);
+ mEnabled = enableVolumeUi || enableSafetyWarning;
if (!mEnabled) return;
mVolumeComponent = new VolumeDialogComponent(this, mContext, null);
+ mVolumeComponent.setEnableDialogs(enableVolumeUi, enableSafetyWarning);
putComponent(VolumeComponent.class, getVolumeComponent());
setDefaultVolumeController();
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUiLayout.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUiLayout.java
new file mode 100644
index 000000000000..0a3a2ab7c61f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUiLayout.java
@@ -0,0 +1,153 @@
+/*
+ * 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.volume;
+
+import static com.android.systemui.util.leak.RotationUtils.ROTATION_NONE;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.DisplayCutout;
+import android.view.View;
+import android.view.ViewOutlineProvider;
+import android.view.ViewTreeObserver;
+import android.widget.FrameLayout;
+
+import com.android.systemui.R;
+import com.android.systemui.util.leak.RotationUtils;
+
+public class VolumeUiLayout extends FrameLayout {
+
+ private View mChild;
+ private int mRotation = ROTATION_NONE;
+ @Nullable
+ private DisplayCutout mDisplayCutout;
+
+ public VolumeUiLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mDisplayCutout = null;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ if (mChild == null) {
+ if (getChildCount() != 0) {
+ mChild = getChildAt(0);
+ updateRotation();
+ } else {
+ return;
+ }
+ }
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ updateRotation();
+ }
+
+ private void setDisplayCutout() {
+ if (mDisplayCutout == null && getRootWindowInsets() != null) {
+ DisplayCutout cutout = getRootWindowInsets().getDisplayCutout();
+ if (cutout != null) {
+ mDisplayCutout = cutout;
+ }
+ }
+ }
+
+ public void updateRotation() {
+ setDisplayCutout();
+ if (mChild == null) {
+ if (getChildCount() != 0) {
+ mChild = getChildAt(0);
+ }
+ }
+ int rotation = RotationUtils.getRotation(getContext());
+ if (rotation != mRotation) {
+ updateSafeInsets(rotation);
+ mRotation = rotation;
+ }
+ }
+
+ private void updateSafeInsets(int rotation) {
+ // Depending on our rotation, we may have to work around letterboxing from the right
+ // side from the navigation bar or a cutout.
+
+ MarginLayoutParams lp = (MarginLayoutParams) mChild.getLayoutParams();
+
+ int margin = (int) getResources().getDimension(R.dimen.volume_dialog_base_margin);
+ switch (rotation) {
+ /*
+ * Landscape: <-|. Have to deal with the nav bar
+ * Seascape: |->. Have to deal with the cutout
+ */
+ case RotationUtils.ROTATION_LANDSCAPE:
+ margin += getNavBarHeight();
+ break;
+ case RotationUtils.ROTATION_SEASCAPE:
+ margin += getDisplayCutoutHeight();
+ break;
+ default:
+ break;
+ }
+
+ lp.rightMargin = margin;
+ mChild.setLayoutParams(lp);
+ }
+
+ private int getNavBarHeight() {
+ return (int) getResources().getDimension(R.dimen.navigation_bar_size);
+ }
+
+ //TODO: Find a better way
+ private int getDisplayCutoutHeight() {
+ if (mDisplayCutout == null || mDisplayCutout.isEmpty()) {
+ return 0;
+ }
+
+ Rect r = mDisplayCutout.getBoundingRect();
+ return r.bottom - r.top;
+ }
+
+ @Override
+ public ViewOutlineProvider getOutlineProvider() {
+ return super.getOutlineProvider();
+ }
+
+ public static VolumeUiLayout get(View v) {
+ if (v instanceof VolumeUiLayout) return (VolumeUiLayout) v;
+ if (v.getParent() instanceof View) {
+ return get((View) v.getParent());
+ }
+ return null;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java b/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java
deleted file mode 100644
index 80e162983a65..000000000000
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java
+++ /dev/null
@@ -1,180 +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.volume;
-
-import android.animation.LayoutTransition;
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.provider.Settings.Global;
-import android.service.notification.ZenModeConfig;
-import android.transition.AutoTransition;
-import android.transition.TransitionManager;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.systemui.Prefs;
-import com.android.systemui.R;
-import com.android.systemui.statusbar.policy.ZenModeController;
-
-import java.util.Objects;
-
-/**
- * Zen mode information (and end button) attached to the bottom of the volume dialog.
- */
-public class ZenFooter extends LinearLayout {
- private static final String TAG = Util.logTag(ZenFooter.class);
-
- private final Context mContext;
- private final ConfigurableTexts mConfigurableTexts;
-
- private ImageView mIcon;
- private TextView mSummaryLine1;
- private TextView mSummaryLine2;
- private TextView mEndNowButton;
- private View mZenIntroduction;
- private View mZenIntroductionConfirm;
- private TextView mZenIntroductionMessage;
- private int mZen = -1;
- private ZenModeConfig mConfig;
- private ZenModeController mController;
-
- public ZenFooter(Context context, AttributeSet attrs) {
- super(context, attrs);
- mContext = context;
- mConfigurableTexts = new ConfigurableTexts(mContext);
- final LayoutTransition layoutTransition = new LayoutTransition();
- layoutTransition.setDuration(new ValueAnimator().getDuration() / 2);
- setLayoutTransition(layoutTransition);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mIcon = findViewById(R.id.volume_zen_icon);
- mSummaryLine1 = findViewById(R.id.volume_zen_summary_line_1);
- mSummaryLine2 = findViewById(R.id.volume_zen_summary_line_2);
- mEndNowButton = findViewById(R.id.volume_zen_end_now);
- mZenIntroduction = findViewById(R.id.zen_introduction);
- mZenIntroductionMessage = findViewById(R.id.zen_introduction_message);
- mConfigurableTexts.add(mZenIntroductionMessage, R.string.zen_alarms_introduction);
- mZenIntroductionConfirm = findViewById(R.id.zen_introduction_confirm);
- mZenIntroductionConfirm.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- confirmZenIntroduction();
- }
- });
- Util.setVisOrGone(mZenIntroduction, shouldShowIntroduction());
- mConfigurableTexts.add(mSummaryLine1);
- mConfigurableTexts.add(mSummaryLine2);
- mConfigurableTexts.add(mEndNowButton, R.string.volume_zen_end_now);
- }
-
- public void init(final ZenModeController controller) {
- mEndNowButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- setZen(Global.ZEN_MODE_OFF);
- controller.setZen(Global.ZEN_MODE_OFF, null, TAG);
- }
- });
- mZen = controller.getZen();
- mConfig = controller.getConfig();
- mController = controller;
- mController.addCallback(mZenCallback);
- update();
- updateIntroduction();
- }
-
- public void cleanup() {
- mController.removeCallback(mZenCallback);
- }
-
- private void setZen(int zen) {
- if (mZen == zen) return;
- mZen = zen;
- update();
- post(() -> {
- updateIntroduction();
- });
- }
-
- private void setConfig(ZenModeConfig config) {
- if (Objects.equals(mConfig, config)) return;
- mConfig = config;
- update();
- }
-
- private void confirmZenIntroduction() {
- Prefs.putBoolean(mContext, Prefs.Key.DND_CONFIRMED_ALARM_INTRODUCTION, true);
- updateIntroduction();
- }
-
- private boolean isZenPriority() {
- return mZen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
- }
-
- private boolean isZenAlarms() {
- return mZen == Global.ZEN_MODE_ALARMS;
- }
-
- private boolean isZenNone() {
- return mZen == Global.ZEN_MODE_NO_INTERRUPTIONS;
- }
-
- public void update() {
- mIcon.setImageResource(isZenNone() ? R.drawable.ic_dnd_total_silence : R.drawable.ic_dnd);
- final String line1 =
- isZenPriority() ? mContext.getString(R.string.interruption_level_priority)
- : isZenAlarms() ? mContext.getString(R.string.interruption_level_alarms)
- : isZenNone() ? mContext.getString(R.string.interruption_level_none)
- : null;
- Util.setText(mSummaryLine1, line1);
-
- final CharSequence line2 = ZenModeConfig.getConditionSummary(mContext, mConfig,
- mController.getCurrentUser(), true /*shortVersion*/);
- Util.setText(mSummaryLine2, line2);
- }
-
- public boolean shouldShowIntroduction() {
- final boolean confirmed = Prefs.getBoolean(mContext,
- Prefs.Key.DND_CONFIRMED_ALARM_INTRODUCTION, false);
- return !confirmed && isZenAlarms();
- }
-
- public void updateIntroduction() {
- Util.setVisOrGone(mZenIntroduction, shouldShowIntroduction());
- }
-
- public void onConfigurationChanged() {
- mConfigurableTexts.update();
- }
-
- private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() {
- @Override
- public void onZenChanged(int zen) {
- setZen(zen);
- }
- @Override
- public void onConfigChanged(ZenModeConfig config) {
- setConfig(config);
- }
- };
-}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenRadioLayout.java b/packages/SystemUI/src/com/android/systemui/volume/ZenRadioLayout.java
deleted file mode 100644
index 360907b0d22b..000000000000
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenRadioLayout.java
+++ /dev/null
@@ -1,93 +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.volume;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.LinearLayout;
-
-/**
- * Specialized layout for zen mode that allows the radio buttons to reside within
- * a RadioGroup, but also makes sure that all the heights off the radio buttons align
- * with the corresponding content in the second child of this view.
- */
-public class ZenRadioLayout extends LinearLayout {
-
- public ZenRadioLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- /**
- * Run 2 measurement passes, 1 that figures out the size of the content, and another
- * that sets the size of the radio buttons to the heights of the corresponding content.
- */
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
- ViewGroup radioGroup = (ViewGroup) getChildAt(0);
- ViewGroup radioContent = (ViewGroup) getChildAt(1);
- int size = radioGroup.getChildCount();
- if (size != radioContent.getChildCount()) {
- throw new IllegalStateException("Expected matching children");
- }
- boolean hasChanges = false;
- View lastView = null;
- for (int i = 0; i < size; i++) {
- View radio = radioGroup.getChildAt(i);
- View content = radioContent.getChildAt(i);
- if (lastView != null) {
- radio.setAccessibilityTraversalAfter(lastView.getId());
- }
- View contentClick = findFirstClickable(content);
- if (contentClick != null) contentClick.setAccessibilityTraversalAfter(radio.getId());
- lastView = findLastClickable(content);
- if (radio.getLayoutParams().height != content.getMeasuredHeight()) {
- hasChanges = true;
- radio.getLayoutParams().height = content.getMeasuredHeight();
- }
- }
- // Measure again if any heights changed.
- if (hasChanges) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
- }
-
- private View findFirstClickable(View content) {
- if (content.isClickable()) return content;
- if (content instanceof ViewGroup) {
- ViewGroup group = (ViewGroup) content;
- for (int i = 0; i < group.getChildCount(); i++) {
- View v = findFirstClickable(group.getChildAt(i));
- if (v != null) return v;
- }
- }
- return null;
- }
-
- private View findLastClickable(View content) {
- if (content.isClickable()) return content;
- if (content instanceof ViewGroup) {
- ViewGroup group = (ViewGroup) content;
- for (int i = group.getChildCount() - 1; i >= 0; i--) {
- View v = findLastClickable(group.getChildAt(i));
- if (v != null) return v;
- }
- }
- return null;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/car/CarVolumeDialogController.java b/packages/SystemUI/src/com/android/systemui/volume/car/CarVolumeDialogController.java
deleted file mode 100644
index d28e42e61f29..000000000000
--- a/packages/SystemUI/src/com/android/systemui/volume/car/CarVolumeDialogController.java
+++ /dev/null
@@ -1,136 +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.volume.car;
-
-import android.car.Car;
-import android.car.CarNotConnectedException;
-import android.car.media.CarAudioManager;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.ServiceConnection;
-import android.os.IBinder;
-import android.util.Log;
-
-import com.android.systemui.volume.VolumeDialogControllerImpl;
-
-/**
- * A volume dialog controller for the automotive use case.
- *
- * {@link android.car.media.CarAudioManager} is the source of truth to get the stream volumes.
- * And volume changes should be sent to the car's audio module instead of the android's audio mixer.
- */
-public class CarVolumeDialogController extends VolumeDialogControllerImpl {
- private static final String TAG = "CarVolumeDialogController";
-
- private final Car mCar;
- private CarAudioManager mCarAudioManager;
-
- private final ServiceConnection mConnection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
- try {
- mCarAudioManager = (CarAudioManager) mCar.getCarManager(Car.AUDIO_SERVICE);
- setVolumeController();
- CarVolumeDialogController.this.getState();
- } catch (CarNotConnectedException e) {
- Log.e(TAG, "Car is not connected!", e);
- }
- }
-
- @Override
- public void onServiceDisconnected(ComponentName componentName) {
- Log.e(TAG, "Car service is disconnected");
- }
- };
-
- public CarVolumeDialogController(Context context) {
- super(context);
- mCar = Car.createCar(context, mConnection);
- mCar.connect();
- }
-
- @Override
- protected void setAudioManagerStreamVolume(int stream, int level, int flag) {
- if (mCarAudioManager == null) {
- Log.d(TAG, "Car audio manager is not initialized yet");
- return;
- }
- try {
- mCarAudioManager.setStreamVolume(stream, level, flag);
- } catch (CarNotConnectedException e) {
- Log.e(TAG, "Car is not connected", e);
- }
- }
-
- @Override
- protected int getAudioManagerStreamVolume(int stream) {
- if(mCarAudioManager == null) {
- Log.d(TAG, "Car audio manager is not initialized yet");
- return 0;
- }
-
- try {
- return mCarAudioManager.getStreamVolume(stream);
- } catch (CarNotConnectedException e) {
- Log.e(TAG, "Car is not connected", e);
- return 0;
- }
- }
-
- @Override
- protected int getAudioManagerStreamMaxVolume(int stream) {
- if(mCarAudioManager == null) {
- Log.d(TAG, "Car audio manager is not initialized yet");
- return 0;
- }
-
- try {
- return mCarAudioManager.getStreamMaxVolume(stream);
- } catch (CarNotConnectedException e) {
- Log.e(TAG, "Car is not connected", e);
- return 0;
- }
- }
-
- @Override
- protected int getAudioManagerStreamMinVolume(int stream) {
- if(mCarAudioManager == null) {
- Log.d(TAG, "Car audio manager is not initialized yet");
- return 0;
- }
-
- try {
- return mCarAudioManager.getStreamMinVolume(stream);
- } catch (CarNotConnectedException e) {
- Log.e(TAG, "Car is not connected", e);
- return 0;
- }
- }
-
- @Override
- public void setVolumeController() {
- if (mCarAudioManager == null) {
- Log.d(TAG, "Car audio manager is not initialized yet");
- return;
- }
- try {
- mCarAudioManager.setVolumeController(mVolumeController);
- } catch (CarNotConnectedException e) {
- Log.e(TAG, "Car is not connected", e);
- }
- }
-}