summaryrefslogtreecommitdiffstats
path: root/packages/SystemUI/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/SystemUI/src')
-rwxr-xr-xpackages/SystemUI/src/com/android/systemui/BatteryMeterView.java286
-rw-r--r--packages/SystemUI/src/com/android/systemui/DemoMode.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/EventLogConstants.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/EventLogTags.logtags2
-rw-r--r--packages/SystemUI/src/com/android/systemui/ExpandHelper.java42
-rw-r--r--packages/SystemUI/src/com/android/systemui/ImageWallpaper.java40
-rw-r--r--packages/SystemUI/src/com/android/systemui/Prefs.java113
-rw-r--r--packages/SystemUI/src/com/android/systemui/SearchPanelCircleView.java592
-rw-r--r--packages/SystemUI/src/com/android/systemui/SearchPanelView.java345
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIApplication.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/ViewInvertHelper.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java263
-rw-r--r--packages/SystemUI/src/com/android/systemui/assist/AssistManager.java311
-rw-r--r--packages/SystemUI/src/com/android/systemui/assist/AssistOrbContainer.java155
-rw-r--r--packages/SystemUI/src/com/android/systemui/assist/AssistOrbView.java285
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeLog.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeService.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/egg/LLandActivity.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/egg/ShruggyActivity.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java259
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/DataUsageGraph.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFooter.java133
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanel.java111
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSTile.java29
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSTileView.java41
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/UsageTracker.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java54
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java69
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/DataUsageDetailView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java289
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java50
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java96
-rw-r--r--packages/SystemUI/src/com/android/systemui/recent/ColorDrawableWithDimensions.java40
-rw-r--r--packages/SystemUI/src/com/android/systemui/recent/FadedEdgeDrawHelper.java193
-rw-r--r--packages/SystemUI/src/com/android/systemui/recent/FirstFrameAnimatorHelper.java134
-rw-r--r--packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java586
-rw-r--r--packages/SystemUI/src/com/android/systemui/recent/Recents.java326
-rw-r--r--packages/SystemUI/src/com/android/systemui/recent/RecentsActivity.java248
-rw-r--r--packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java391
-rw-r--r--packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java813
-rw-r--r--packages/SystemUI/src/com/android/systemui/recent/RecentsPreloadReceiver.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java401
-rw-r--r--packages/SystemUI/src/com/android/systemui/recent/TaskDescription.java98
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/Constants.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/Recents.java (renamed from packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java)420
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java242
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHostView.java68
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java81
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java240
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsUserEventProxyReceiver.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java (renamed from packages/SystemUI/src/com/android/systemui/recent/ScreenPinningRequest.java)5
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java209
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java194
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/model/SpaceNode.java82
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/model/Task.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java86
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/FakeShadowDrawable.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/FixedSizeImageView.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java437
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewLayoutAlgorithm.java59
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java548
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewFilterAlgorithm.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java84
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java82
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java111
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java151
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/BrightnessDialog.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/ToggleSeekBar.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/ToggleSlider.java44
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java115
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedButton.java48
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedFrameLayout.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedImageView.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/AnimatedImageView.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java885
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java64
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/DelegateViewHelper.java149
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/DismissViewButton.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java486
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java141
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java135
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationBigMediaNarrowViewWrapper.java (renamed from packages/SystemUI/src/com/android/systemui/statusbar/phone/TickerView.java)32
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationBigPictureViewWrapper.java35
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java304
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationCustomViewWrapper.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java138
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowIconsView.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ServiceMonitor.java35
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java262
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StackScrollerDecorView.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java82
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarter.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java172
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java234
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java398
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java45
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPreviewContainer.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java266
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java89
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java64
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java66
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java238
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java698
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ObservableScrollView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java41
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java349
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java1816
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java150
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java48
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java85
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java169
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/SecureCameraLaunchManager.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java463
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java39
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java129
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java83
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/Ticker.java305
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/TrustDrawable.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/VelocityTrackerFactory.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java190
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityContentDescriptions.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityController.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java38
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java570
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothUtil.java234
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java180
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/EthernetIcons.java (renamed from packages/SystemUI/src/com/android/systemui/recent/Constants.java)17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/EthernetSignalController.java62
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java264
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java631
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java495
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java68
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java82
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java60
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherScrim.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java61
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileDataControllerImpl.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java542
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java69
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java1339
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/PreviewInflater.java77
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java191
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java156
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalCallbackAdapter.java63
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java314
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java88
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java48
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java200
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java88
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java68
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/HeadsUpAppearInterpolator.java51
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java406
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java619
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java249
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java305
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java302
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/StackViewState.java86
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java49
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java60
-rw-r--r--packages/SystemUI/src/com/android/systemui/tuner/AutoScrollView.java46
-rw-r--r--packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java178
-rw-r--r--packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java522
-rw-r--r--packages/SystemUI/src/com/android/systemui/tuner/StatusBarSwitch.java69
-rw-r--r--packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java (renamed from packages/SystemUI/src/com/android/systemui/recent/RecentsCallback.java)23
-rw-r--r--packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java159
-rw-r--r--packages/SystemUI/src/com/android/systemui/tuner/TunerService.java160
-rw-r--r--packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java922
-rw-r--r--packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingSecondaryUserActivity.java96
-rw-r--r--packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/D.java (renamed from packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPanel.java)10
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/Events.java217
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/MediaSessions.java378
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/SafetyWarningDialog.java109
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/SegmentedButtons.java25
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/SpTexts.java78
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/Util.java174
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java1129
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java141
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java1024
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogMotion.java313
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java1664
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumePrefs.java65
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java307
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java145
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java415
228 files changed, 21273 insertions, 15085 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
index 7bdbd0a..95b58e5 100755
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -16,19 +16,24 @@
package com.android.systemui;
+import android.animation.ArgbEvaluator;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
import android.content.res.TypedArray;
+import android.database.ContentObserver;
import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.graphics.Typeface;
+import android.net.Uri;
import android.os.BatteryManager;
import android.os.Bundle;
+import android.os.Handler;
import android.provider.Settings;
import android.util.AttributeSet;
import android.view.View;
@@ -39,10 +44,9 @@ public class BatteryMeterView extends View implements DemoMode,
BatteryController.BatteryStateChangeCallback {
public static final String TAG = BatteryMeterView.class.getSimpleName();
public static final String ACTION_LEVEL_TEST = "com.android.systemui.BATTERY_LEVEL_TEST";
+ public static final String SHOW_PERCENT_SETTING = "status_bar_show_battery_percent";
- private static final boolean ENABLE_PERCENT = true;
private static final boolean SINGLE_DIGIT_PERCENT = false;
- private static final boolean SHOW_100_PERCENT = false;
private static final int FULL = 96;
@@ -50,18 +54,19 @@ public class BatteryMeterView extends View implements DemoMode,
private final int[] mColors;
- boolean mShowPercent = true;
+ private boolean mShowPercent;
private float mButtonHeightFraction;
private float mSubpixelSmoothingLeft;
private float mSubpixelSmoothingRight;
private final Paint mFramePaint, mBatteryPaint, mWarningTextPaint, mTextPaint, mBoltPaint;
private float mTextHeight, mWarningTextHeight;
+ private int mIconTint = Color.WHITE;
private int mHeight;
private int mWidth;
private String mWarningString;
private final int mCriticalLevel;
- private final int mChargeColor;
+ private int mChargeColor;
private final float[] mBoltPoints;
private final Path mBoltPath = new Path();
@@ -76,103 +81,14 @@ public class BatteryMeterView extends View implements DemoMode,
private BatteryController mBatteryController;
private boolean mPowerSaveEnabled;
- private class BatteryTracker extends BroadcastReceiver {
- public static final int UNKNOWN_LEVEL = -1;
+ private int mDarkModeBackgroundColor;
+ private int mDarkModeFillColor;
- // current battery status
- int level = UNKNOWN_LEVEL;
- String percentStr;
- int plugType;
- boolean plugged;
- int health;
- int status;
- String technology;
- int voltage;
- int temperature;
- boolean testmode = false;
+ private int mLightModeBackgroundColor;
+ private int mLightModeFillColor;
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
- if (testmode && ! intent.getBooleanExtra("testmode", false)) return;
-
- level = (int)(100f
- * intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
- / intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100));
-
- plugType = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
- plugged = plugType != 0;
- health = intent.getIntExtra(BatteryManager.EXTRA_HEALTH,
- BatteryManager.BATTERY_HEALTH_UNKNOWN);
- status = intent.getIntExtra(BatteryManager.EXTRA_STATUS,
- BatteryManager.BATTERY_STATUS_UNKNOWN);
- technology = intent.getStringExtra(BatteryManager.EXTRA_TECHNOLOGY);
- voltage = intent.getIntExtra(BatteryManager.EXTRA_VOLTAGE, 0);
- temperature = intent.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, 0);
-
- setContentDescription(
- context.getString(R.string.accessibility_battery_level, level));
- postInvalidate();
- } else if (action.equals(ACTION_LEVEL_TEST)) {
- testmode = true;
- post(new Runnable() {
- int curLevel = 0;
- int incr = 1;
- int saveLevel = level;
- int savePlugged = plugType;
- Intent dummy = new Intent(Intent.ACTION_BATTERY_CHANGED);
- @Override
- public void run() {
- if (curLevel < 0) {
- testmode = false;
- dummy.putExtra("level", saveLevel);
- dummy.putExtra("plugged", savePlugged);
- dummy.putExtra("testmode", false);
- } else {
- dummy.putExtra("level", curLevel);
- dummy.putExtra("plugged", incr > 0 ? BatteryManager.BATTERY_PLUGGED_AC : 0);
- dummy.putExtra("testmode", true);
- }
- getContext().sendBroadcast(dummy);
-
- if (!testmode) return;
-
- curLevel += incr;
- if (curLevel == 100) {
- incr *= -1;
- }
- postDelayed(this, 200);
- }
- });
- }
- }
- }
-
- BatteryTracker mTracker = new BatteryTracker();
-
- @Override
- public void onAttachedToWindow() {
- super.onAttachedToWindow();
-
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_BATTERY_CHANGED);
- filter.addAction(ACTION_LEVEL_TEST);
- final Intent sticky = getContext().registerReceiver(mTracker, filter);
- if (sticky != null) {
- // preload the battery level
- mTracker.onReceive(getContext(), sticky);
- }
- mBatteryController.addStateChangedCallback(this);
- }
-
- @Override
- public void onDetachedFromWindow() {
- super.onDetachedFromWindow();
-
- getContext().unregisterReceiver(mTracker);
- mBatteryController.removeStateChangedCallback(this);
- }
+ private BatteryTracker mTracker = new BatteryTracker();
+ private final SettingObserver mSettingObserver = new SettingObserver();
public BatteryMeterView(Context context) {
this(context, null, 0);
@@ -189,7 +105,7 @@ public class BatteryMeterView extends View implements DemoMode,
TypedArray atts = context.obtainStyledAttributes(attrs, R.styleable.BatteryMeterView,
defStyle, 0);
final int frameColor = atts.getColor(R.styleable.BatteryMeterView_frameColor,
- res.getColor(R.color.batterymeter_frame_color));
+ context.getColor(R.color.batterymeter_frame_color));
TypedArray levels = res.obtainTypedArray(R.array.batterymeter_color_levels);
TypedArray colors = res.obtainTypedArray(R.array.batterymeter_color_values);
@@ -202,8 +118,7 @@ public class BatteryMeterView extends View implements DemoMode,
levels.recycle();
colors.recycle();
atts.recycle();
- mShowPercent = ENABLE_PERCENT && 0 != Settings.System.getInt(
- context.getContentResolver(), "status_bar_show_battery_percent", 0);
+ updateShowPercent();
mWarningString = context.getString(R.string.battery_meter_very_low_overlay_symbol);
mCriticalLevel = mContext.getResources().getInteger(
com.android.internal.R.integer.config_criticalBatteryWarningLevel);
@@ -236,11 +151,44 @@ public class BatteryMeterView extends View implements DemoMode,
mWarningTextPaint.setTypeface(font);
mWarningTextPaint.setTextAlign(Paint.Align.CENTER);
- mChargeColor = getResources().getColor(R.color.batterymeter_charge_color);
+ mChargeColor = context.getColor(R.color.batterymeter_charge_color);
mBoltPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- mBoltPaint.setColor(res.getColor(R.color.batterymeter_bolt_color));
+ mBoltPaint.setColor(context.getColor(R.color.batterymeter_bolt_color));
mBoltPoints = loadBoltPoints(res);
+
+ mDarkModeBackgroundColor =
+ context.getColor(R.color.dark_mode_icon_color_dual_tone_background);
+ mDarkModeFillColor = context.getColor(R.color.dark_mode_icon_color_dual_tone_fill);
+ mLightModeBackgroundColor =
+ context.getColor(R.color.light_mode_icon_color_dual_tone_background);
+ mLightModeFillColor = context.getColor(R.color.light_mode_icon_color_dual_tone_fill);
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_BATTERY_CHANGED);
+ filter.addAction(ACTION_LEVEL_TEST);
+ final Intent sticky = getContext().registerReceiver(mTracker, filter);
+ if (sticky != null) {
+ // preload the battery level
+ mTracker.onReceive(getContext(), sticky);
+ }
+ mBatteryController.addStateChangedCallback(this);
+ getContext().getContentResolver().registerContentObserver(
+ Settings.System.getUriFor(SHOW_PERCENT_SETTING), false, mSettingObserver);
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+
+ getContext().unregisterReceiver(mTracker);
+ mBatteryController.removeStateChangedCallback(this);
+ getContext().getContentResolver().unregisterContentObserver(mSettingObserver);
}
public void setBatteryController(BatteryController batteryController) {
@@ -282,6 +230,11 @@ public class BatteryMeterView extends View implements DemoMode,
mWarningTextHeight = -mWarningTextPaint.getFontMetrics().ascent;
}
+ private void updateShowPercent() {
+ mShowPercent = 0 != Settings.System.getInt(getContext().getContentResolver(),
+ SHOW_PERCENT_SETTING, 0);
+ }
+
private int getColorForLevel(int percent) {
// If we are in power save mode, always use the normal color.
@@ -292,11 +245,43 @@ public class BatteryMeterView extends View implements DemoMode,
for (int i=0; i<mColors.length; i+=2) {
thresh = mColors[i];
color = mColors[i+1];
- if (percent <= thresh) return color;
+ if (percent <= thresh) {
+
+ // Respect tinting for "normal" level
+ if (i == mColors.length-2) {
+ return mIconTint;
+ } else {
+ return color;
+ }
+ }
}
return color;
}
+ public void setDarkIntensity(float darkIntensity) {
+ int backgroundColor = getBackgroundColor(darkIntensity);
+ int fillColor = getFillColor(darkIntensity);
+ mIconTint = fillColor;
+ mFramePaint.setColor(backgroundColor);
+ mBoltPaint.setColor(fillColor);
+ mChargeColor = fillColor;
+ invalidate();
+ }
+
+ private int getBackgroundColor(float darkIntensity) {
+ return getColorForDarkIntensity(
+ darkIntensity, mLightModeBackgroundColor, mDarkModeBackgroundColor);
+ }
+
+ private int getFillColor(float darkIntensity) {
+ return getColorForDarkIntensity(
+ darkIntensity, mLightModeFillColor, mDarkModeFillColor);
+ }
+
+ private int getColorForDarkIntensity(float darkIntensity, int lightColor, int darkColor) {
+ return (int) ArgbEvaluator.getInstance().evaluate(darkIntensity, lightColor, darkColor);
+ }
+
@Override
public void draw(Canvas c) {
BatteryTracker tracker = mDemoMode ? mDemoTracker : mTracker;
@@ -397,8 +382,7 @@ public class BatteryMeterView extends View implements DemoMode,
boolean pctOpaque = false;
float pctX = 0, pctY = 0;
String pctText = null;
- if (!tracker.plugged && level > mCriticalLevel && mShowPercent
- && !(tracker.level == 100 && !SHOW_100_PERCENT)) {
+ if (!tracker.plugged && level > mCriticalLevel && mShowPercent) {
mTextPaint.setColor(getColorForLevel(level));
mTextPaint.setTextSize(height *
(SINGLE_DIGIT_PERCENT ? 0.75f
@@ -468,4 +452,92 @@ public class BatteryMeterView extends View implements DemoMode,
postInvalidate();
}
}
+
+ private final class BatteryTracker extends BroadcastReceiver {
+ public static final int UNKNOWN_LEVEL = -1;
+
+ // current battery status
+ int level = UNKNOWN_LEVEL;
+ String percentStr;
+ int plugType;
+ boolean plugged;
+ int health;
+ int status;
+ String technology;
+ int voltage;
+ int temperature;
+ boolean testmode = false;
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
+ if (testmode && ! intent.getBooleanExtra("testmode", false)) return;
+
+ level = (int)(100f
+ * intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
+ / intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100));
+
+ plugType = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
+ plugged = plugType != 0;
+ health = intent.getIntExtra(BatteryManager.EXTRA_HEALTH,
+ BatteryManager.BATTERY_HEALTH_UNKNOWN);
+ status = intent.getIntExtra(BatteryManager.EXTRA_STATUS,
+ BatteryManager.BATTERY_STATUS_UNKNOWN);
+ technology = intent.getStringExtra(BatteryManager.EXTRA_TECHNOLOGY);
+ voltage = intent.getIntExtra(BatteryManager.EXTRA_VOLTAGE, 0);
+ temperature = intent.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, 0);
+
+ setContentDescription(
+ context.getString(R.string.accessibility_battery_level, level));
+ postInvalidate();
+ } else if (action.equals(ACTION_LEVEL_TEST)) {
+ testmode = true;
+ post(new Runnable() {
+ int curLevel = 0;
+ int incr = 1;
+ int saveLevel = level;
+ int savePlugged = plugType;
+ Intent dummy = new Intent(Intent.ACTION_BATTERY_CHANGED);
+ @Override
+ public void run() {
+ if (curLevel < 0) {
+ testmode = false;
+ dummy.putExtra("level", saveLevel);
+ dummy.putExtra("plugged", savePlugged);
+ dummy.putExtra("testmode", false);
+ } else {
+ dummy.putExtra("level", curLevel);
+ dummy.putExtra("plugged", incr > 0 ? BatteryManager.BATTERY_PLUGGED_AC
+ : 0);
+ dummy.putExtra("testmode", true);
+ }
+ getContext().sendBroadcast(dummy);
+
+ if (!testmode) return;
+
+ curLevel += incr;
+ if (curLevel == 100) {
+ incr *= -1;
+ }
+ postDelayed(this, 200);
+ }
+ });
+ }
+ }
+ }
+
+ private final class SettingObserver extends ContentObserver {
+ public SettingObserver() {
+ super(new Handler());
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ super.onChange(selfChange, uri);
+ updateShowPercent();
+ postInvalidate();
+ }
+ }
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/DemoMode.java b/packages/SystemUI/src/com/android/systemui/DemoMode.java
index 9c206e2..d406f5b 100644
--- a/packages/SystemUI/src/com/android/systemui/DemoMode.java
+++ b/packages/SystemUI/src/com/android/systemui/DemoMode.java
@@ -24,6 +24,8 @@ public interface DemoMode {
public static final String ACTION_DEMO = "com.android.systemui.demo";
+ public static final String EXTRA_COMMAND = "command";
+
public static final String COMMAND_ENTER = "enter";
public static final String COMMAND_EXIT = "exit";
public static final String COMMAND_CLOCK = "clock";
diff --git a/packages/SystemUI/src/com/android/systemui/EventLogConstants.java b/packages/SystemUI/src/com/android/systemui/EventLogConstants.java
index c8af2d4..43a1be1 100644
--- a/packages/SystemUI/src/com/android/systemui/EventLogConstants.java
+++ b/packages/SystemUI/src/com/android/systemui/EventLogConstants.java
@@ -34,4 +34,10 @@ public class EventLogConstants {
public static final int SYSUI_LOCKSCREEN_GESTURE_TAP_LOCK = 6;
/** The user tapped a notification, needs to tap again to launch. */
public static final int SYSUI_LOCKSCREEN_GESTURE_TAP_NOTIFICATION_ACTIVATE = 7;
+ /** The user swiped down to open quick settings, from keyguard. */
+ public static final int SYSUI_LOCKSCREEN_GESTURE_SWIPE_DOWN_QS = 8;
+ /** The user swiped down to open quick settings, from shade. */
+ public static final int SYSUI_SHADE_GESTURE_SWIPE_DOWN_QS = 9;
+ /** The user tapped on the status bar to open quick settings, from shade. */
+ public static final int SYSUI_TAP_TO_OPEN_QS = 10;
}
diff --git a/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags b/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags
index d2ce94b..a584cf6 100644
--- a/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags
+++ b/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags
@@ -5,7 +5,7 @@ option java_package com.android.systemui;
# ---------------------------
# PhoneStatusBar.java
# ---------------------------
-36000 sysui_statusbar_touch (type|1),(x|1),(y|1),(enabled|1)
+36000 sysui_statusbar_touch (type|1),(x|1),(y|1),(disable1|1),(disable2|1)
36001 sysui_heads_up_status (key|3),(visible|1)
36002 sysui_fullscreen_notification (key|3)
36003 sysui_heads_up_escalation (key|3)
diff --git a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
index d42ac61..b0e2afa 100644
--- a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
@@ -22,10 +22,10 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.media.AudioAttributes;
-import android.media.AudioManager;
import android.os.Vibrator;
import android.util.Log;
import android.view.Gravity;
+import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.ScaleGestureDetector.OnScaleGestureListener;
@@ -65,15 +65,6 @@ public class ExpandHelper implements Gefingerpoken {
// 2f: maximum brightness is stretching a 1U to 3U, or a 4U to 6U
private static final float STRETCH_INTERVAL = 2f;
- // level of glow for a touch, without overstretch
- // overstretch fills the range (GLOW_BASE, 1.0]
- private static final float GLOW_BASE = 0.5f;
-
- private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
- .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
- .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
- .build();
-
@SuppressWarnings("unused")
private Context mContext;
@@ -95,13 +86,11 @@ public class ExpandHelper implements Gefingerpoken {
private float mLastSpanY;
private int mTouchSlop;
private float mLastMotionY;
- private int mPopDuration;
private float mPullGestureMinXSpan;
private Callback mCallback;
private ScaleGestureDetector mSGD;
private ViewScaler mScaler;
private ObjectAnimator mScaleAnimation;
- private Vibrator mVibrator;
private boolean mEnabled = true;
private ExpandableView mResizedView;
private float mCurrentHeight;
@@ -147,14 +136,14 @@ public class ExpandHelper implements Gefingerpoken {
}
public void setHeight(float h) {
if (DEBUG_SCALE) Log.v(TAG, "SetHeight: setting to " + h);
- mView.setActualHeight((int) h);
+ mView.setContentHeight((int) h);
mCurrentHeight = h;
}
public float getHeight() {
- return mView.getActualHeight();
+ return mView.getContentHeight();
}
public int getNaturalHeight(int maximum) {
- return Math.min(maximum, mView.getMaxHeight());
+ return Math.min(maximum, mView.getMaxContentHeight());
}
}
@@ -175,7 +164,6 @@ public class ExpandHelper implements Gefingerpoken {
mScaler = new ViewScaler();
mGravity = Gravity.TOP;
mScaleAnimation = ObjectAnimator.ofFloat(mScaler, "height", 0f);
- mPopDuration = mContext.getResources().getInteger(R.integer.blinds_pop_duration_ms);
mPullGestureMinXSpan = mContext.getResources().getDimension(R.dimen.pull_span_min);
final ViewConfiguration configuration = ViewConfiguration.get(mContext);
@@ -323,6 +311,10 @@ public class ExpandHelper implements Gefingerpoken {
isInside(mScrollAdapter.getHostView(), x, y)
&& mScrollAdapter.isScrolledToTop();
mResizedView = findView(x, y);
+ if (mResizedView != null && !mCallback.canChildBeExpanded(mResizedView)) {
+ mResizedView = null;
+ mWatchingForPull = false;
+ }
mInitialTouchY = ev.getY();
break;
@@ -387,7 +379,8 @@ public class ExpandHelper implements Gefingerpoken {
}
private boolean isFullyExpanded(ExpandableView underFocus) {
- return underFocus.getIntrinsicHeight() == underFocus.getMaxHeight();
+ return underFocus.areChildrenExpanded() || underFocus.getIntrinsicHeight()
+ - underFocus.getBottomDecorHeight() == underFocus.getMaxContentHeight();
}
@Override
@@ -448,7 +441,9 @@ public class ExpandHelper implements Gefingerpoken {
}
if (!mHasPopped) {
- vibrate(mPopDuration);
+ if (mEventSource != null) {
+ mEventSource.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
+ }
mHasPopped = true;
}
@@ -596,16 +591,5 @@ public class ExpandHelper implements Gefingerpoken {
public void onlyObserveMovements(boolean onlyMovements) {
mOnlyMovements = onlyMovements;
}
-
- /**
- * Triggers haptic feedback.
- */
- private synchronized void vibrate(long duration) {
- if (mVibrator == null) {
- mVibrator = (android.os.Vibrator)
- mContext.getSystemService(Context.VIBRATOR_SERVICE);
- }
- mVibrator.vibrate(duration, VIBRATION_ATTRIBUTES);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index 7c725b3..6acd137 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -21,13 +21,9 @@ import static javax.microedition.khronos.egl.EGL10.*;
import android.app.ActivityManager;
import android.app.WallpaperManager;
-import android.content.BroadcastReceiver;
import android.content.ComponentCallbacks2;
-import android.content.Context;
-import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Canvas;
-import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region.Op;
@@ -37,6 +33,7 @@ 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;
@@ -113,6 +110,9 @@ public class ImageWallpaper extends WallpaperService {
float mYOffset = 0.5f;
float mScale = 1f;
+ private Display mDefaultDisplay;
+ private final DisplayInfo mTmpDisplayInfo = new DisplayInfo();
+
boolean mVisible = true;
boolean mRedrawNeeded;
boolean mOffsetsChanged;
@@ -174,7 +174,9 @@ public class ImageWallpaper extends WallpaperService {
super.onCreate(surfaceHolder);
- updateSurfaceSize(surfaceHolder);
+ mDefaultDisplay = getSystemService(WindowManager.class).getDefaultDisplay();
+
+ updateSurfaceSize(surfaceHolder, getDefaultDisplayInfo());
setOffsetNotificationsEnabled(false);
}
@@ -186,9 +188,7 @@ public class ImageWallpaper extends WallpaperService {
mWallpaperManager.forgetLoadedWallpaper();
}
- void updateSurfaceSize(SurfaceHolder surfaceHolder) {
- Point p = getDefaultDisplaySize();
-
+ void updateSurfaceSize(SurfaceHolder surfaceHolder, DisplayInfo displayInfo) {
// Load background image dimensions, if we haven't saved them yet
if (mBackgroundWidth <= 0 || mBackgroundHeight <= 0) {
// Need to load the image to get dimensions
@@ -196,14 +196,14 @@ public class ImageWallpaper extends WallpaperService {
updateWallpaperLocked();
if (mBackgroundWidth <= 0 || mBackgroundHeight <= 0) {
// Default to the display size if we can't find the dimensions
- mBackgroundWidth = p.x;
- mBackgroundHeight = p.y;
+ mBackgroundWidth = displayInfo.logicalWidth;
+ mBackgroundHeight = displayInfo.logicalHeight;
}
}
// Force the wallpaper to cover the screen in both dimensions
- int surfaceWidth = Math.max(p.x, mBackgroundWidth);
- int surfaceHeight = Math.max(p.y, mBackgroundHeight);
+ int surfaceWidth = Math.max(displayInfo.logicalWidth, mBackgroundWidth);
+ int surfaceHeight = Math.max(displayInfo.logicalHeight, mBackgroundHeight);
// If the surface dimensions haven't changed, then just return
final Rect frame = surfaceHolder.getSurfaceFrame();
@@ -299,26 +299,22 @@ public class ImageWallpaper extends WallpaperService {
drawFrame();
}
- private Point getDefaultDisplaySize() {
- Point p = new Point();
- Context c = ImageWallpaper.this.getApplicationContext();
- WindowManager wm = (WindowManager)c.getSystemService(Context.WINDOW_SERVICE);
- Display d = wm.getDefaultDisplay();
- d.getRealSize(p);
- return p;
+ private DisplayInfo getDefaultDisplayInfo() {
+ mDefaultDisplay.getDisplayInfo(mTmpDisplayInfo);
+ return mTmpDisplayInfo;
}
void drawFrame() {
try {
- int newRotation = ((WindowManager) getSystemService(WINDOW_SERVICE)).
- getDefaultDisplay().getRotation();
+ DisplayInfo displayInfo = getDefaultDisplayInfo();
+ int newRotation = displayInfo.rotation;
// Sometimes a wallpaper is not large enough to cover the screen in one dimension.
// Call updateSurfaceSize -- it will only actually do the update if the dimensions
// should change
if (newRotation != mLastRotation) {
// Update surface size (if necessary)
- updateSurfaceSize(getSurfaceHolder());
+ updateSurfaceSize(getSurfaceHolder(), displayInfo);
}
SurfaceHolder sh = getSurfaceHolder();
final Rect frame = sh.getSurfaceFrame();
diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java
new file mode 100644
index 0000000..3657cf2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/Prefs.java
@@ -0,0 +1,113 @@
+/*
+ * 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;
+
+import android.annotation.StringDef;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Map;
+
+public final class Prefs {
+ private Prefs() {} // no instantation
+
+ @Retention(RetentionPolicy.SOURCE)
+ @StringDef({
+ Key.SEARCH_APP_WIDGET_ID,
+ Key.DEBUG_MODE_ENABLED,
+ Key.HOTSPOT_TILE_LAST_USED,
+ Key.COLOR_INVERSION_TILE_LAST_USED,
+ Key.DND_TILE_VISIBLE,
+ Key.DND_TILE_COMBINED_ICON,
+ Key.DND_CONFIRMED_PRIORITY_INTRODUCTION,
+ Key.DND_CONFIRMED_SILENCE_INTRODUCTION,
+ Key.DND_FAVORITE_BUCKET_INDEX,
+ Key.DND_NONE_SELECTED,
+ Key.DND_FAVORITE_ZEN,
+ })
+ public @interface Key {
+ String SEARCH_APP_WIDGET_ID = "searchAppWidgetId";
+ String SEARCH_APP_WIDGET_PACKAGE = "searchAppWidgetPackage";
+ String DEBUG_MODE_ENABLED = "debugModeEnabled";
+ String HOTSPOT_TILE_LAST_USED = "HotspotTileLastUsed";
+ String COLOR_INVERSION_TILE_LAST_USED = "ColorInversionTileLastUsed";
+ String DND_TILE_VISIBLE = "DndTileVisible";
+ String DND_TILE_COMBINED_ICON = "DndTileCombinedIcon";
+ String DND_CONFIRMED_PRIORITY_INTRODUCTION = "DndConfirmedPriorityIntroduction";
+ String DND_CONFIRMED_SILENCE_INTRODUCTION = "DndConfirmedSilenceIntroduction";
+ String DND_FAVORITE_BUCKET_INDEX = "DndCountdownMinuteIndex";
+ String DND_NONE_SELECTED = "DndNoneSelected";
+ String DND_FAVORITE_ZEN = "DndFavoriteZen";
+ }
+
+ public static boolean getBoolean(Context context, @Key String key, boolean defaultValue) {
+ return get(context).getBoolean(key, defaultValue);
+ }
+
+ public static void putBoolean(Context context, @Key String key, boolean value) {
+ get(context).edit().putBoolean(key, value).apply();
+ }
+
+ public static int getInt(Context context, @Key String key, int defaultValue) {
+ return get(context).getInt(key, defaultValue);
+ }
+
+ public static void putInt(Context context, @Key String key, int value) {
+ get(context).edit().putInt(key, value).apply();
+ }
+
+ public static long getLong(Context context, @Key String key, long defaultValue) {
+ return get(context).getLong(key, defaultValue);
+ }
+
+ public static void putLong(Context context, @Key String key, long value) {
+ get(context).edit().putLong(key, value).apply();
+ }
+
+ public static String getString(Context context, @Key String key, String defaultValue) {
+ return get(context).getString(key, defaultValue);
+ }
+
+ public static void putString(Context context, @Key String key, String value) {
+ get(context).edit().putString(key, value).apply();
+ }
+
+ public static Map<String, ?> getAll(Context context) {
+ return get(context).getAll();
+ }
+
+ public static void remove(Context context, @Key String key) {
+ get(context).edit().remove(key).apply();
+ }
+
+ public static void registerListener(Context context,
+ OnSharedPreferenceChangeListener listener) {
+ get(context).registerOnSharedPreferenceChangeListener(listener);
+ }
+
+ public static void unregisterListener(Context context,
+ OnSharedPreferenceChangeListener listener) {
+ get(context).unregisterOnSharedPreferenceChangeListener(listener);
+ }
+
+ private static SharedPreferences get(Context context) {
+ return context.getSharedPreferences(context.getPackageName(), Context.MODE_PRIVATE);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/SearchPanelCircleView.java b/packages/SystemUI/src/com/android/systemui/SearchPanelCircleView.java
deleted file mode 100644
index d8fb6da..0000000
--- a/packages/SystemUI/src/com/android/systemui/SearchPanelCircleView.java
+++ /dev/null
@@ -1,592 +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;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.PropertyValuesHolder;
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Outline;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewOutlineProvider;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
-import android.view.animation.LinearInterpolator;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
-
-import java.util.ArrayList;
-
-public class SearchPanelCircleView extends FrameLayout {
-
- private final int mCircleMinSize;
- private final int mBaseMargin;
- private final int mStaticOffset;
- private final Paint mBackgroundPaint = new Paint();
- private final Paint mRipplePaint = new Paint();
- private final Rect mCircleRect = new Rect();
- private final Rect mStaticRect = new Rect();
- private final Interpolator mFastOutSlowInInterpolator;
- private final Interpolator mAppearInterpolator;
- private final Interpolator mDisappearInterpolator;
-
- private boolean mClipToOutline;
- private final int mMaxElevation;
- private boolean mAnimatingOut;
- private float mOutlineAlpha;
- private float mOffset;
- private float mCircleSize;
- private boolean mHorizontal;
- private boolean mCircleHidden;
- private ImageView mLogo;
- private boolean mDraggedFarEnough;
- private boolean mOffsetAnimatingIn;
- private float mCircleAnimationEndValue;
- private ArrayList<Ripple> mRipples = new ArrayList<Ripple>();
-
- private ValueAnimator mOffsetAnimator;
- private ValueAnimator mCircleAnimator;
- private ValueAnimator mFadeOutAnimator;
- private ValueAnimator.AnimatorUpdateListener mCircleUpdateListener
- = new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- applyCircleSize((float) animation.getAnimatedValue());
- updateElevation();
- }
- };
- private AnimatorListenerAdapter mClearAnimatorListener = new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mCircleAnimator = null;
- }
- };
- private ValueAnimator.AnimatorUpdateListener mOffsetUpdateListener
- = new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- setOffset((float) animation.getAnimatedValue());
- }
- };
-
-
- public SearchPanelCircleView(Context context) {
- this(context, null);
- }
-
- public SearchPanelCircleView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public SearchPanelCircleView(Context context, AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
- }
-
- public SearchPanelCircleView(Context context, AttributeSet attrs, int defStyleAttr,
- int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- setOutlineProvider(new ViewOutlineProvider() {
- @Override
- public void getOutline(View view, Outline outline) {
- if (mCircleSize > 0.0f) {
- outline.setOval(mCircleRect);
- } else {
- outline.setEmpty();
- }
- outline.setAlpha(mOutlineAlpha);
- }
- });
- setWillNotDraw(false);
- mCircleMinSize = context.getResources().getDimensionPixelSize(
- R.dimen.search_panel_circle_size);
- mBaseMargin = context.getResources().getDimensionPixelSize(
- R.dimen.search_panel_circle_base_margin);
- mStaticOffset = context.getResources().getDimensionPixelSize(
- R.dimen.search_panel_circle_travel_distance);
- mMaxElevation = context.getResources().getDimensionPixelSize(
- R.dimen.search_panel_circle_elevation);
- mAppearInterpolator = AnimationUtils.loadInterpolator(mContext,
- android.R.interpolator.linear_out_slow_in);
- mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(mContext,
- android.R.interpolator.fast_out_slow_in);
- mDisappearInterpolator = AnimationUtils.loadInterpolator(mContext,
- android.R.interpolator.fast_out_linear_in);
- mBackgroundPaint.setAntiAlias(true);
- mBackgroundPaint.setColor(getResources().getColor(R.color.search_panel_circle_color));
- mRipplePaint.setColor(getResources().getColor(R.color.search_panel_ripple_color));
- mRipplePaint.setAntiAlias(true);
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- drawBackground(canvas);
- drawRipples(canvas);
- }
-
- private void drawRipples(Canvas canvas) {
- for (int i = 0; i < mRipples.size(); i++) {
- Ripple ripple = mRipples.get(i);
- ripple.draw(canvas);
- }
- }
-
- private void drawBackground(Canvas canvas) {
- canvas.drawCircle(mCircleRect.centerX(), mCircleRect.centerY(), mCircleSize / 2,
- mBackgroundPaint);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mLogo = (ImageView) findViewById(R.id.search_logo);
- }
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- mLogo.layout(0, 0, mLogo.getMeasuredWidth(), mLogo.getMeasuredHeight());
- if (changed) {
- updateCircleRect(mStaticRect, mStaticOffset, true);
- }
- }
-
- public void setCircleSize(float circleSize) {
- setCircleSize(circleSize, false, null, 0, null);
- }
-
- public void setCircleSize(float circleSize, boolean animated, final Runnable endRunnable,
- int startDelay, Interpolator interpolator) {
- boolean isAnimating = mCircleAnimator != null;
- boolean animationPending = isAnimating && !mCircleAnimator.isRunning();
- boolean animatingOut = isAnimating && mCircleAnimationEndValue == 0;
- if (animated || animationPending || animatingOut) {
- if (isAnimating) {
- if (circleSize == mCircleAnimationEndValue) {
- return;
- }
- mCircleAnimator.cancel();
- }
- mCircleAnimator = ValueAnimator.ofFloat(mCircleSize, circleSize);
- mCircleAnimator.addUpdateListener(mCircleUpdateListener);
- mCircleAnimator.addListener(mClearAnimatorListener);
- mCircleAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- if (endRunnable != null) {
- endRunnable.run();
- }
- }
- });
- Interpolator desiredInterpolator = interpolator != null ? interpolator
- : circleSize == 0 ? mDisappearInterpolator : mAppearInterpolator;
- mCircleAnimator.setInterpolator(desiredInterpolator);
- mCircleAnimator.setDuration(300);
- mCircleAnimator.setStartDelay(startDelay);
- mCircleAnimator.start();
- mCircleAnimationEndValue = circleSize;
- } else {
- if (isAnimating) {
- float diff = circleSize - mCircleAnimationEndValue;
- PropertyValuesHolder[] values = mCircleAnimator.getValues();
- values[0].setFloatValues(diff, circleSize);
- mCircleAnimator.setCurrentPlayTime(mCircleAnimator.getCurrentPlayTime());
- mCircleAnimationEndValue = circleSize;
- } else {
- applyCircleSize(circleSize);
- updateElevation();
- }
- }
- }
-
- private void applyCircleSize(float circleSize) {
- mCircleSize = circleSize;
- updateLayout();
- }
-
- private void updateElevation() {
- float t = (mStaticOffset - mOffset) / (float) mStaticOffset;
- t = 1.0f - Math.max(t, 0.0f);
- float offset = t * mMaxElevation;
- setElevation(offset);
- }
-
- /**
- * Sets the offset to the edge of the screen. By default this not not animated.
- *
- * @param offset The offset to apply.
- */
- public void setOffset(float offset) {
- setOffset(offset, false, 0, null, null);
- }
-
- /**
- * Sets the offset to the edge of the screen.
- *
- * @param offset The offset to apply.
- * @param animate Whether an animation should be performed.
- * @param startDelay The desired start delay if animated.
- * @param interpolator The desired interpolator if animated. If null,
- * a default interpolator will be taken designed for appearing or
- * disappearing.
- * @param endRunnable The end runnable which should be executed when the animation is finished.
- */
- private void setOffset(float offset, boolean animate, int startDelay,
- Interpolator interpolator, final Runnable endRunnable) {
- if (!animate) {
- mOffset = offset;
- updateLayout();
- if (endRunnable != null) {
- endRunnable.run();
- }
- } else {
- if (mOffsetAnimator != null) {
- mOffsetAnimator.removeAllListeners();
- mOffsetAnimator.cancel();
- }
- mOffsetAnimator = ValueAnimator.ofFloat(mOffset, offset);
- mOffsetAnimator.addUpdateListener(mOffsetUpdateListener);
- mOffsetAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mOffsetAnimator = null;
- if (endRunnable != null) {
- endRunnable.run();
- }
- }
- });
- Interpolator desiredInterpolator = interpolator != null ?
- interpolator : offset == 0 ? mDisappearInterpolator : mAppearInterpolator;
- mOffsetAnimator.setInterpolator(desiredInterpolator);
- mOffsetAnimator.setStartDelay(startDelay);
- mOffsetAnimator.setDuration(300);
- mOffsetAnimator.start();
- mOffsetAnimatingIn = offset != 0;
- }
- }
-
- private void updateLayout() {
- updateCircleRect();
- updateLogo();
- invalidateOutline();
- invalidate();
- updateClipping();
- }
-
- private void updateClipping() {
- boolean clip = mCircleSize < mCircleMinSize || !mRipples.isEmpty();
- if (clip != mClipToOutline) {
- setClipToOutline(clip);
- mClipToOutline = clip;
- }
- }
-
- private void updateLogo() {
- boolean exitAnimationRunning = mFadeOutAnimator != null;
- Rect rect = exitAnimationRunning ? mCircleRect : mStaticRect;
- float translationX = (rect.left + rect.right) / 2.0f - mLogo.getWidth() / 2.0f;
- float translationY = (rect.top + rect.bottom) / 2.0f - mLogo.getHeight() / 2.0f;
- float t = (mStaticOffset - mOffset) / (float) mStaticOffset;
- if (!exitAnimationRunning) {
- if (mHorizontal) {
- translationX += t * mStaticOffset * 0.3f;
- } else {
- translationY += t * mStaticOffset * 0.3f;
- }
- float alpha = 1.0f-t;
- alpha = Math.max((alpha - 0.5f) * 2.0f, 0);
- mLogo.setAlpha(alpha);
- } else {
- translationY += (mOffset - mStaticOffset) / 2;
- }
- mLogo.setTranslationX(translationX);
- mLogo.setTranslationY(translationY);
- }
-
- private void updateCircleRect() {
- updateCircleRect(mCircleRect, mOffset, false);
- }
-
- private void updateCircleRect(Rect rect, float offset, boolean useStaticSize) {
- int left, top;
- float circleSize = useStaticSize ? mCircleMinSize : mCircleSize;
- if (mHorizontal) {
- left = (int) (getWidth() - circleSize / 2 - mBaseMargin - offset);
- top = (int) ((getHeight() - circleSize) / 2);
- } else {
- left = (int) (getWidth() - circleSize) / 2;
- top = (int) (getHeight() - circleSize / 2 - mBaseMargin - offset);
- }
- rect.set(left, top, (int) (left + circleSize), (int) (top + circleSize));
- }
-
- public void setHorizontal(boolean horizontal) {
- mHorizontal = horizontal;
- updateCircleRect(mStaticRect, mStaticOffset, true);
- updateLayout();
- }
-
- public void setDragDistance(float distance) {
- if (!mAnimatingOut && (!mCircleHidden || mDraggedFarEnough)) {
- float circleSize = mCircleMinSize + rubberband(distance);
- setCircleSize(circleSize);
- }
-
- }
-
- private float rubberband(float diff) {
- return (float) Math.pow(Math.abs(diff), 0.6f);
- }
-
- public void startAbortAnimation(Runnable endRunnable) {
- if (mAnimatingOut) {
- if (endRunnable != null) {
- endRunnable.run();
- }
- return;
- }
- setCircleSize(0, true, null, 0, null);
- setOffset(0, true, 0, null, endRunnable);
- mCircleHidden = true;
- }
-
- public void startEnterAnimation() {
- if (mAnimatingOut) {
- return;
- }
- applyCircleSize(0);
- setOffset(0);
- setCircleSize(mCircleMinSize, true, null, 50, null);
- setOffset(mStaticOffset, true, 50, null, null);
- mCircleHidden = false;
- }
-
-
- public void startExitAnimation(final Runnable endRunnable) {
- if (!mHorizontal) {
- float offset = getHeight() / 2.0f;
- setOffset(offset - mBaseMargin, true, 50, mFastOutSlowInInterpolator, null);
- float xMax = getWidth() / 2;
- float yMax = getHeight() / 2;
- float maxRadius = (float) Math.ceil(Math.hypot(xMax, yMax) * 2);
- setCircleSize(maxRadius, true, null, 50, mFastOutSlowInInterpolator);
- performExitFadeOutAnimation(50, 300, endRunnable);
- } else {
-
- // when in landscape, we don't wan't the animation as it interferes with the general
- // rotation animation to the homescreen.
- endRunnable.run();
- }
- }
-
- private void performExitFadeOutAnimation(int startDelay, int duration,
- final Runnable endRunnable) {
- mFadeOutAnimator = ValueAnimator.ofFloat(mBackgroundPaint.getAlpha() / 255.0f, 0.0f);
-
- // Linear since we are animating multiple values
- mFadeOutAnimator.setInterpolator(new LinearInterpolator());
- mFadeOutAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- float animatedFraction = animation.getAnimatedFraction();
- float logoValue = animatedFraction > 0.5f ? 1.0f : animatedFraction / 0.5f;
- logoValue = PhoneStatusBar.ALPHA_OUT.getInterpolation(1.0f - logoValue);
- float backgroundValue = animatedFraction < 0.2f ? 0.0f :
- PhoneStatusBar.ALPHA_OUT.getInterpolation((animatedFraction - 0.2f) / 0.8f);
- backgroundValue = 1.0f - backgroundValue;
- mBackgroundPaint.setAlpha((int) (backgroundValue * 255));
- mOutlineAlpha = backgroundValue;
- mLogo.setAlpha(logoValue);
- invalidateOutline();
- invalidate();
- }
- });
- mFadeOutAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- if (endRunnable != null) {
- endRunnable.run();
- }
- mLogo.setAlpha(1.0f);
- mBackgroundPaint.setAlpha(255);
- mOutlineAlpha = 1.0f;
- mFadeOutAnimator = null;
- }
- });
- mFadeOutAnimator.setStartDelay(startDelay);
- mFadeOutAnimator.setDuration(duration);
- mFadeOutAnimator.start();
- }
-
- public void setDraggedFarEnough(boolean farEnough) {
- if (farEnough != mDraggedFarEnough) {
- if (farEnough) {
- if (mCircleHidden) {
- startEnterAnimation();
- }
- if (mOffsetAnimator == null) {
- addRipple();
- } else {
- postDelayed(new Runnable() {
- @Override
- public void run() {
- addRipple();
- }
- }, 100);
- }
- } else {
- startAbortAnimation(null);
- }
- mDraggedFarEnough = farEnough;
- }
-
- }
-
- private void addRipple() {
- if (mRipples.size() > 1) {
- // we only want 2 ripples at the time
- return;
- }
- float xInterpolation, yInterpolation;
- if (mHorizontal) {
- xInterpolation = 0.75f;
- yInterpolation = 0.5f;
- } else {
- xInterpolation = 0.5f;
- yInterpolation = 0.75f;
- }
- float circleCenterX = mStaticRect.left * (1.0f - xInterpolation)
- + mStaticRect.right * xInterpolation;
- float circleCenterY = mStaticRect.top * (1.0f - yInterpolation)
- + mStaticRect.bottom * yInterpolation;
- float radius = Math.max(mCircleSize, mCircleMinSize * 1.25f) * 0.75f;
- Ripple ripple = new Ripple(circleCenterX, circleCenterY, radius);
- ripple.start();
- }
-
- public void reset() {
- mDraggedFarEnough = false;
- mAnimatingOut = false;
- mCircleHidden = true;
- mClipToOutline = false;
- if (mFadeOutAnimator != null) {
- mFadeOutAnimator.cancel();
- }
- mBackgroundPaint.setAlpha(255);
- mOutlineAlpha = 1.0f;
- }
-
- /**
- * Check if an animation is currently running
- *
- * @param enterAnimation Is the animating queried the enter animation.
- */
- public boolean isAnimationRunning(boolean enterAnimation) {
- return mOffsetAnimator != null && (enterAnimation == mOffsetAnimatingIn);
- }
-
- public void performOnAnimationFinished(final Runnable runnable) {
- if (mOffsetAnimator != null) {
- mOffsetAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- if (runnable != null) {
- runnable.run();
- }
- }
- });
- } else {
- if (runnable != null) {
- runnable.run();
- }
- }
- }
-
- public void setAnimatingOut(boolean animatingOut) {
- mAnimatingOut = animatingOut;
- }
-
- /**
- * @return Whether the circle is currently launching to the search activity or aborting the
- * interaction
- */
- public boolean isAnimatingOut() {
- return mAnimatingOut;
- }
-
- @Override
- public boolean hasOverlappingRendering() {
- // not really true but it's ok during an animation, as it's never permanent
- return false;
- }
-
- private class Ripple {
- float x;
- float y;
- float radius;
- float endRadius;
- float alpha;
-
- Ripple(float x, float y, float endRadius) {
- this.x = x;
- this.y = y;
- this.endRadius = endRadius;
- }
-
- void start() {
- ValueAnimator animator = ValueAnimator.ofFloat(0.0f, 1.0f);
-
- // Linear since we are animating multiple values
- animator.setInterpolator(new LinearInterpolator());
- animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- alpha = 1.0f - animation.getAnimatedFraction();
- alpha = mDisappearInterpolator.getInterpolation(alpha);
- radius = mAppearInterpolator.getInterpolation(animation.getAnimatedFraction());
- radius *= endRadius;
- invalidate();
- }
- });
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mRipples.remove(Ripple.this);
- updateClipping();
- }
-
- public void onAnimationStart(Animator animation) {
- mRipples.add(Ripple.this);
- updateClipping();
- }
- });
- animator.setDuration(400);
- animator.start();
- }
-
- public void draw(Canvas canvas) {
- mRipplePaint.setAlpha((int) (alpha * 255));
- canvas.drawCircle(x, y, radius, mRipplePaint);
- }
- }
-
-}
diff --git a/packages/SystemUI/src/com/android/systemui/SearchPanelView.java b/packages/SystemUI/src/com/android/systemui/SearchPanelView.java
deleted file mode 100644
index 445b499..0000000
--- a/packages/SystemUI/src/com/android/systemui/SearchPanelView.java
+++ /dev/null
@@ -1,345 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui;
-
-import android.app.ActivityOptions;
-import android.app.SearchManager;
-import android.content.ActivityNotFoundException;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.media.AudioAttributes;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.os.Vibrator;
-import android.provider.Settings;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.View;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-
-import com.android.systemui.statusbar.BaseStatusBar;
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.StatusBarPanel;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
-
-public class SearchPanelView extends FrameLayout implements StatusBarPanel {
-
- private static final String TAG = "SearchPanelView";
- private static final String ASSIST_ICON_METADATA_NAME =
- "com.android.systemui.action_assist_icon";
-
- private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
- .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
- .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
- .build();
-
- private final Context mContext;
- private BaseStatusBar mBar;
-
- private SearchPanelCircleView mCircle;
- private ImageView mLogo;
- private View mScrim;
-
- private int mThreshold;
- private boolean mHorizontal;
-
- private boolean mLaunching;
- private boolean mDragging;
- private boolean mDraggedFarEnough;
- private float mStartTouch;
- private float mStartDrag;
- private boolean mLaunchPending;
-
- public SearchPanelView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public SearchPanelView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- mContext = context;
- mThreshold = context.getResources().getDimensionPixelSize(R.dimen.search_panel_threshold);
- }
-
- private void startAssistActivity() {
- if (!mBar.isDeviceProvisioned()) return;
-
- // Close Recent Apps if needed
- mBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL);
-
- final Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
- .getAssistIntent(mContext, true, UserHandle.USER_CURRENT);
- if (intent == null) return;
-
- try {
- final ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext,
- R.anim.search_launch_enter, R.anim.search_launch_exit);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- AsyncTask.execute(new Runnable() {
- @Override
- public void run() {
- mContext.startActivityAsUser(intent, opts.toBundle(),
- new UserHandle(UserHandle.USER_CURRENT));
- }
- });
- } catch (ActivityNotFoundException e) {
- Log.w(TAG, "Activity not found for " + intent.getAction());
- }
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- mCircle = (SearchPanelCircleView) findViewById(R.id.search_panel_circle);
- mLogo = (ImageView) findViewById(R.id.search_logo);
- mScrim = findViewById(R.id.search_panel_scrim);
- }
-
- private void maybeSwapSearchIcon() {
- Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
- .getAssistIntent(mContext, false, UserHandle.USER_CURRENT);
- if (intent != null) {
- ComponentName component = intent.getComponent();
- replaceDrawable(mLogo, component, ASSIST_ICON_METADATA_NAME);
- } else {
- mLogo.setImageDrawable(null);
- }
- }
-
- public void replaceDrawable(ImageView v, ComponentName component, String name) {
- if (component != null) {
- try {
- PackageManager packageManager = mContext.getPackageManager();
- // Look for the search icon specified in the activity meta-data
- Bundle metaData = packageManager.getActivityInfo(
- component, PackageManager.GET_META_DATA).metaData;
- if (metaData != null) {
- int iconResId = metaData.getInt(name);
- if (iconResId != 0) {
- Resources res = packageManager.getResourcesForActivity(component);
- v.setImageDrawable(res.getDrawable(iconResId));
- return;
- }
- }
- } catch (PackageManager.NameNotFoundException e) {
- Log.w(TAG, "Failed to swap drawable; "
- + component.flattenToShortString() + " not found", e);
- } catch (Resources.NotFoundException nfe) {
- Log.w(TAG, "Failed to swap drawable from "
- + component.flattenToShortString(), nfe);
- }
- }
- v.setImageDrawable(null);
- }
-
- @Override
- public boolean isInContentArea(int x, int y) {
- return true;
- }
-
- private void vibrate() {
- Context context = getContext();
- if (Settings.System.getIntForUser(context.getContentResolver(),
- Settings.System.HAPTIC_FEEDBACK_ENABLED, 1, UserHandle.USER_CURRENT) != 0) {
- Resources res = context.getResources();
- Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
- vibrator.vibrate(res.getInteger(R.integer.config_search_panel_view_vibration_duration),
- VIBRATION_ATTRIBUTES);
- }
- }
-
- public void show(final boolean show, boolean animate) {
- if (show) {
- maybeSwapSearchIcon();
- if (getVisibility() != View.VISIBLE) {
- setVisibility(View.VISIBLE);
- vibrate();
- if (animate) {
- startEnterAnimation();
- } else {
- mScrim.setAlpha(1f);
- }
- }
- setFocusable(true);
- setFocusableInTouchMode(true);
- requestFocus();
- } else {
- if (animate) {
- startAbortAnimation();
- } else {
- setVisibility(View.INVISIBLE);
- }
- }
- }
-
- private void startEnterAnimation() {
- mCircle.startEnterAnimation();
- mScrim.setAlpha(0f);
- mScrim.animate()
- .alpha(1f)
- .setDuration(300)
- .setStartDelay(50)
- .setInterpolator(PhoneStatusBar.ALPHA_IN)
- .start();
-
- }
-
- private void startAbortAnimation() {
- mCircle.startAbortAnimation(new Runnable() {
- @Override
- public void run() {
- mCircle.setAnimatingOut(false);
- setVisibility(View.INVISIBLE);
- }
- });
- mCircle.setAnimatingOut(true);
- mScrim.animate()
- .alpha(0f)
- .setDuration(300)
- .setStartDelay(0)
- .setInterpolator(PhoneStatusBar.ALPHA_OUT);
- }
-
- public void hide(boolean animate) {
- if (mBar != null) {
- // This will indirectly cause show(false, ...) to get called
- mBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
- } else {
- if (animate) {
- startAbortAnimation();
- } else {
- setVisibility(View.INVISIBLE);
- }
- }
- }
-
- @Override
- public boolean dispatchHoverEvent(MotionEvent event) {
- // Ignore hover events outside of this panel bounds since such events
- // generate spurious accessibility events with the panel content when
- // tapping outside of it, thus confusing the user.
- final int x = (int) event.getX();
- final int y = (int) event.getY();
- if (x >= 0 && x < getWidth() && y >= 0 && y < getHeight()) {
- return super.dispatchHoverEvent(event);
- }
- return true;
- }
-
- /**
- * Whether the panel is showing, or, if it's animating, whether it will be
- * when the animation is done.
- */
- public boolean isShowing() {
- return getVisibility() == View.VISIBLE && !mCircle.isAnimatingOut();
- }
-
- public void setBar(BaseStatusBar bar) {
- mBar = bar;
- }
-
- public boolean isAssistantAvailable() {
- return ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
- .getAssistIntent(mContext, false, UserHandle.USER_CURRENT) != null;
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- if (mLaunching || mLaunchPending) {
- return false;
- }
- int action = event.getActionMasked();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- mStartTouch = mHorizontal ? event.getX() : event.getY();
- mDragging = false;
- mDraggedFarEnough = false;
- mCircle.reset();
- break;
- case MotionEvent.ACTION_MOVE:
- float currentTouch = mHorizontal ? event.getX() : event.getY();
- if (getVisibility() == View.VISIBLE && !mDragging &&
- (!mCircle.isAnimationRunning(true /* enterAnimation */)
- || Math.abs(mStartTouch - currentTouch) > mThreshold)) {
- mStartDrag = currentTouch;
- mDragging = true;
- }
- if (mDragging) {
- float offset = Math.max(mStartDrag - currentTouch, 0.0f);
- mCircle.setDragDistance(offset);
- mDraggedFarEnough = Math.abs(mStartTouch - currentTouch) > mThreshold;
- mCircle.setDraggedFarEnough(mDraggedFarEnough);
- }
- break;
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- if (mDraggedFarEnough) {
- if (mCircle.isAnimationRunning(true /* enterAnimation */)) {
- mLaunchPending = true;
- mCircle.setAnimatingOut(true);
- mCircle.performOnAnimationFinished(new Runnable() {
- @Override
- public void run() {
- startExitAnimation();
- }
- });
- } else {
- startExitAnimation();
- }
- } else {
- startAbortAnimation();
- }
- break;
- }
- return true;
- }
-
- private void startExitAnimation() {
- mLaunchPending = false;
- if (mLaunching || getVisibility() != View.VISIBLE) {
- return;
- }
- mLaunching = true;
- startAssistActivity();
- vibrate();
- mCircle.setAnimatingOut(true);
- mCircle.startExitAnimation(new Runnable() {
- @Override
- public void run() {
- mLaunching = false;
- mCircle.setAnimatingOut(false);
- setVisibility(View.INVISIBLE);
- }
- });
- mScrim.animate()
- .alpha(0f)
- .setDuration(300)
- .setStartDelay(0)
- .setInterpolator(PhoneStatusBar.ALPHA_OUT);
- }
-
- public void setHorizontal(boolean horizontal) {
- mHorizontal = horizontal;
- mCircle.setHorizontal(horizontal);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index b3f90d7..33bd726 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -40,13 +40,14 @@ public class SystemUIApplication extends Application {
* The classes of the stuff to start.
*/
private final Class<?>[] SERVICES = new Class[] {
+ com.android.systemui.tuner.TunerService.class,
com.android.systemui.keyguard.KeyguardViewMediator.class,
- com.android.systemui.recent.Recents.class,
+ com.android.systemui.recents.Recents.class,
com.android.systemui.volume.VolumeUI.class,
com.android.systemui.statusbar.SystemBars.class,
com.android.systemui.usb.StorageNotification.class,
com.android.systemui.power.PowerUI.class,
- com.android.systemui.media.RingtonePlayer.class
+ com.android.systemui.media.RingtonePlayer.class,
};
/**
diff --git a/packages/SystemUI/src/com/android/systemui/ViewInvertHelper.java b/packages/SystemUI/src/com/android/systemui/ViewInvertHelper.java
index 2ff8f8a..eddf2b1 100644
--- a/packages/SystemUI/src/com/android/systemui/ViewInvertHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/ViewInvertHelper.java
@@ -26,8 +26,6 @@ import android.view.View;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
-import com.android.systemui.statusbar.phone.NotificationPanelView;
-
/**
* Helper to invert the colors of views and fade between the states.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java b/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java
new file mode 100644
index 0000000..c3a8f2e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java
@@ -0,0 +1,263 @@
+/*
+ * 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.assist;
+
+import com.android.systemui.R;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.os.Handler;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.animation.AnimationUtils;
+
+/**
+ * Visually discloses that contextual data was provided to an assistant.
+ */
+public class AssistDisclosure {
+ private final Context mContext;
+ private final WindowManager mWm;
+ private final Handler mHandler;
+
+ private AssistDisclosureView mView;
+ private boolean mViewAdded;
+
+ public AssistDisclosure(Context context, Handler handler) {
+ mContext = context;
+ mHandler = handler;
+ mWm = mContext.getSystemService(WindowManager.class);
+ }
+
+ public void postShow() {
+ mHandler.removeCallbacks(mShowRunnable);
+ mHandler.post(mShowRunnable);
+ }
+
+ private void show() {
+ if (mView == null) {
+ mView = new AssistDisclosureView(mContext);
+ }
+ if (!mViewAdded) {
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
+ | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_FULLSCREEN
+ | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED,
+ PixelFormat.TRANSLUCENT);
+ lp.setTitle("AssistDisclosure");
+
+ mWm.addView(mView, lp);
+ mViewAdded = true;
+ }
+ }
+
+ private void hide() {
+ if (mViewAdded) {
+ mWm.removeView(mView);
+ mViewAdded = false;
+ }
+ }
+
+ private Runnable mShowRunnable = new Runnable() {
+ @Override
+ public void run() {
+ show();
+ }
+ };
+
+ private class AssistDisclosureView extends View
+ implements ValueAnimator.AnimatorUpdateListener {
+
+ public static final int TRACING_ANIMATION_DURATION = 600;
+ public static final int ALPHA_IN_ANIMATION_DURATION = 450;
+ public static final int ALPHA_OUT_ANIMATION_DURATION = 400;
+
+ private float mThickness;
+ private float mShadowThickness;
+ private final Paint mPaint = new Paint();
+ private final Paint mShadowPaint = new Paint();
+
+ private final ValueAnimator mTracingAnimator;
+ private final ValueAnimator mAlphaOutAnimator;
+ private final ValueAnimator mAlphaInAnimator;
+ private final AnimatorSet mAnimator;
+
+ private float mTracingProgress = 0;
+ private int mAlpha = 0;
+
+ public AssistDisclosureView(Context context) {
+ super(context);
+
+ mTracingAnimator = ValueAnimator.ofFloat(0, 1).setDuration(TRACING_ANIMATION_DURATION);
+ mTracingAnimator.addUpdateListener(this);
+ mTracingAnimator.setInterpolator(AnimationUtils.loadInterpolator(mContext,
+ R.interpolator.assist_disclosure_trace));
+ mAlphaInAnimator = ValueAnimator.ofInt(0, 255).setDuration(ALPHA_IN_ANIMATION_DURATION);
+ mAlphaInAnimator.addUpdateListener(this);
+ mAlphaInAnimator.setInterpolator(AnimationUtils.loadInterpolator(mContext,
+ android.R.interpolator.fast_out_slow_in));
+ mAlphaOutAnimator = ValueAnimator.ofInt(255, 0).setDuration(
+ ALPHA_OUT_ANIMATION_DURATION);
+ mAlphaOutAnimator.addUpdateListener(this);
+ mAlphaOutAnimator.setInterpolator(AnimationUtils.loadInterpolator(mContext,
+ android.R.interpolator.fast_out_linear_in));
+ mAnimator = new AnimatorSet();
+ mAnimator.play(mAlphaInAnimator).with(mTracingAnimator);
+ mAnimator.play(mAlphaInAnimator).before(mAlphaOutAnimator);
+ mAnimator.addListener(new AnimatorListenerAdapter() {
+ boolean mCancelled;
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mCancelled = false;
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCancelled = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (!mCancelled) {
+ hide();
+ }
+ }
+ });
+
+ PorterDuffXfermode srcMode = new PorterDuffXfermode(PorterDuff.Mode.SRC);
+ mPaint.setColor(Color.WHITE);
+ mPaint.setXfermode(srcMode);
+ mShadowPaint.setColor(Color.DKGRAY);
+ mShadowPaint.setXfermode(srcMode);
+
+ mThickness = getResources().getDimension(R.dimen.assist_disclosure_thickness);
+ mShadowThickness = getResources().getDimension(
+ R.dimen.assist_disclosure_shadow_thickness);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ startAnimation();
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_ASSIST_READING_CONTEXT);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+
+ mAnimator.cancel();
+
+ mTracingProgress = 0;
+ mAlpha = 0;
+ }
+
+ private void startAnimation() {
+ mAnimator.cancel();
+ mAnimator.start();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ mPaint.setAlpha(mAlpha);
+ mShadowPaint.setAlpha(mAlpha / 4);
+
+ drawGeometry(canvas, mShadowPaint, mShadowThickness);
+ drawGeometry(canvas, mPaint, 0);
+ }
+
+ private void drawGeometry(Canvas canvas, Paint paint, float padding) {
+ final int width = getWidth();
+ final int height = getHeight();
+ float thickness = mThickness;
+ final float pixelProgress = mTracingProgress * (width + height - 2 * thickness);
+
+ float bottomProgress = Math.min(pixelProgress, width / 2f);
+ if (bottomProgress > 0) {
+ drawBeam(canvas,
+ width / 2f - bottomProgress,
+ height - thickness,
+ width / 2f + bottomProgress,
+ height, paint, padding);
+ }
+
+ float sideProgress = Math.min(pixelProgress - bottomProgress, height - thickness);
+ if (sideProgress > 0) {
+ drawBeam(canvas,
+ 0,
+ (height - thickness) - sideProgress,
+ thickness,
+ height - thickness, paint, padding);
+ drawBeam(canvas,
+ width - thickness,
+ (height - thickness) - sideProgress,
+ width,
+ height - thickness, paint, padding);
+ }
+
+ float topProgress = Math.min(pixelProgress - bottomProgress - sideProgress,
+ width / 2 - thickness);
+ if (sideProgress > 0 && topProgress > 0) {
+ drawBeam(canvas,
+ thickness,
+ 0,
+ thickness + topProgress,
+ thickness, paint, padding);
+ drawBeam(canvas,
+ (width - thickness) - topProgress,
+ 0,
+ width - thickness,
+ thickness, paint, padding);
+ }
+ }
+
+ private void drawBeam(Canvas canvas, float left, float top, float right, float bottom,
+ Paint paint, float padding) {
+ canvas.drawRect(left - padding,
+ top - padding,
+ right + padding,
+ bottom + padding,
+ paint);
+ }
+
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ if (animation == mAlphaOutAnimator) {
+ mAlpha = (int) mAlphaOutAnimator.getAnimatedValue();
+ } else if (animation == mAlphaInAnimator) {
+ mAlpha = (int) mAlphaInAnimator.getAnimatedValue();
+ } else if (animation == mTracingAnimator) {
+ mTracingProgress = (float) mTracingAnimator.getAnimatedValue();
+ }
+ invalidate();
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
new file mode 100644
index 0000000..51d0bf1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
@@ -0,0 +1,311 @@
+package com.android.systemui.assist;
+
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.SearchManager;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.graphics.PixelFormat;
+import android.media.AudioAttributes;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.HapticFeedbackConstants;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.ImageView;
+
+import com.android.internal.app.AssistUtils;
+import com.android.internal.app.IVoiceInteractionSessionShowCallback;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.phone.PhoneStatusBar;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Class to manage everything related to assist in SystemUI.
+ */
+public class AssistManager {
+
+ private static final String TAG = "AssistManager";
+ private static final String ASSIST_ICON_METADATA_NAME =
+ "com.android.systemui.action_assist_icon";
+
+ private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+ .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
+ .build();
+
+ private static final long TIMEOUT_SERVICE = 2500;
+ private static final long TIMEOUT_ACTIVITY = 1000;
+
+ private final Context mContext;
+ private final WindowManager mWindowManager;
+ private final AssistDisclosure mAssistDisclosure;
+
+ private AssistOrbContainer mView;
+ private final PhoneStatusBar mBar;
+ private final AssistUtils mAssistUtils;
+
+ private ComponentName mAssistComponent;
+
+ private IVoiceInteractionSessionShowCallback mShowCallback =
+ new IVoiceInteractionSessionShowCallback.Stub() {
+
+ @Override
+ public void onFailed() throws RemoteException {
+ mView.post(mHideRunnable);
+ }
+
+ @Override
+ public void onShown() throws RemoteException {
+ mView.post(mHideRunnable);
+ }
+ };
+
+ private Runnable mHideRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mView.removeCallbacks(this);
+ mView.show(false /* show */, true /* animate */);
+ }
+ };
+
+ private final ContentObserver mAssistSettingsObserver = new ContentObserver(new Handler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ updateAssistInfo();
+ }
+ };
+
+ public AssistManager(PhoneStatusBar bar, Context context) {
+ mContext = context;
+ mBar = bar;
+ mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+ mAssistUtils = new AssistUtils(context);
+
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.ASSISTANT), false,
+ mAssistSettingsObserver);
+ mAssistSettingsObserver.onChange(false);
+ mAssistDisclosure = new AssistDisclosure(context, new Handler());
+ }
+
+ public void onConfigurationChanged() {
+ boolean visible = false;
+ if (mView != null) {
+ visible = mView.isShowing();
+ mWindowManager.removeView(mView);
+ }
+
+ mView = (AssistOrbContainer) LayoutInflater.from(mContext).inflate(
+ R.layout.assist_orb, null);
+ mView.setVisibility(View.GONE);
+ mView.setSystemUiVisibility(
+ View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
+ WindowManager.LayoutParams lp = getLayoutParams();
+ mWindowManager.addView(mView, lp);
+ if (visible) {
+ mView.show(true /* show */, false /* animate */);
+ }
+ }
+
+ public void onGestureInvoked() {
+ if (mAssistComponent == null) {
+ return;
+ }
+
+ final boolean isService = isAssistantService();
+ if (!isService || !isVoiceSessionRunning()) {
+ showOrb();
+ mView.postDelayed(mHideRunnable, isService
+ ? TIMEOUT_SERVICE
+ : TIMEOUT_ACTIVITY);
+ }
+ startAssist();
+ }
+
+ public void hideAssist() {
+ mAssistUtils.hideCurrentSession();
+ }
+
+ private WindowManager.LayoutParams getLayoutParams() {
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ mContext.getResources().getDimensionPixelSize(R.dimen.assist_orb_scrim_height),
+ WindowManager.LayoutParams.TYPE_VOICE_INTERACTION_STARTING,
+ WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.TRANSLUCENT);
+ if (ActivityManager.isHighEndGfx()) {
+ lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
+ }
+ lp.gravity = Gravity.BOTTOM | Gravity.START;
+ lp.setTitle("AssistPreviewPanel");
+ lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED
+ | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
+ return lp;
+ }
+
+ private void showOrb() {
+ maybeSwapSearchIcon();
+ mView.show(true /* show */, true /* animate */);
+ }
+
+ private void startAssist() {
+ if (mAssistComponent != null) {
+ if (isAssistantService()) {
+ startVoiceInteractor();
+ } else {
+ startAssistActivity();
+ }
+ }
+ }
+
+ private void startAssistActivity() {
+ if (!mBar.isDeviceProvisioned()) {
+ return;
+ }
+
+ // Close Recent Apps if needed
+ mBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL |
+ CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL);
+
+ boolean structureEnabled = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.ASSIST_STRUCTURE_ENABLED, 1, UserHandle.USER_CURRENT) != 0;
+
+ final Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
+ .getAssistIntent(mContext, structureEnabled, UserHandle.USER_CURRENT);
+ if (intent == null) {
+ return;
+ }
+ if (mAssistComponent != null) {
+ intent.setComponent(mAssistComponent);
+ }
+
+ if (structureEnabled) {
+ showDisclosure();
+ }
+
+ try {
+ final ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext,
+ R.anim.search_launch_enter, R.anim.search_launch_exit);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ AsyncTask.execute(new Runnable() {
+ @Override
+ public void run() {
+ mContext.startActivityAsUser(intent, opts.toBundle(),
+ new UserHandle(UserHandle.USER_CURRENT));
+ }
+ });
+ } catch (ActivityNotFoundException e) {
+ Log.w(TAG, "Activity not found for " + intent.getAction());
+ }
+ }
+
+ private void startVoiceInteractor() {
+ mAssistUtils.showSessionForActiveService(mShowCallback);
+ }
+
+ public void launchVoiceAssistFromKeyguard() {
+ mAssistUtils.launchVoiceAssistFromKeyguard();
+ }
+
+ public boolean canVoiceAssistBeLaunchedFromKeyguard() {
+ return mAssistUtils.activeServiceSupportsLaunchFromKeyguard();
+ }
+
+ public ComponentName getVoiceInteractorComponentName() {
+ return mAssistUtils.getActiveServiceComponentName();
+ }
+
+ private boolean isVoiceSessionRunning() {
+ return mAssistUtils.isSessionRunning();
+ }
+
+ public void destroy() {
+ mWindowManager.removeViewImmediate(mView);
+ }
+
+ private void maybeSwapSearchIcon() {
+ if (mAssistComponent != null) {
+ replaceDrawable(mView.getOrb().getLogo(), mAssistComponent, ASSIST_ICON_METADATA_NAME,
+ isAssistantService());
+ } else {
+ mView.getOrb().getLogo().setImageDrawable(null);
+ }
+ }
+
+ public void replaceDrawable(ImageView v, ComponentName component, String name,
+ boolean isService) {
+ if (component != null) {
+ try {
+ PackageManager packageManager = mContext.getPackageManager();
+ // Look for the search icon specified in the activity meta-data
+ Bundle metaData = isService
+ ? packageManager.getServiceInfo(
+ component, PackageManager.GET_META_DATA).metaData
+ : packageManager.getActivityInfo(
+ component, PackageManager.GET_META_DATA).metaData;
+ if (metaData != null) {
+ int iconResId = metaData.getInt(name);
+ if (iconResId != 0) {
+ Resources res = packageManager.getResourcesForApplication(
+ component.getPackageName());
+ v.setImageDrawable(res.getDrawable(iconResId));
+ return;
+ }
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "Failed to swap drawable; "
+ + component.flattenToShortString() + " not found", e);
+ } catch (Resources.NotFoundException nfe) {
+ Log.w(TAG, "Failed to swap drawable from "
+ + component.flattenToShortString(), nfe);
+ }
+ }
+ v.setImageDrawable(null);
+ }
+
+ private boolean isAssistantService() {
+ return mAssistComponent == null ?
+ false : mAssistComponent.equals(getVoiceInteractorComponentName());
+ }
+
+ private void updateAssistInfo() {
+ mAssistComponent = mAssistUtils.getAssistComponentForUser(UserHandle.USER_CURRENT);
+ }
+
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("AssistManager state:");
+ pw.print(" mAssistComponent="); pw.println(mAssistComponent);
+ }
+
+ public void showDisclosure() {
+ mAssistDisclosure.postShow();
+ }
+
+ public void onUserSwitched(int newUserId) {
+ updateAssistInfo();
+ }
+
+ public void prepareBeforeInvocation() {
+ updateAssistInfo();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistOrbContainer.java b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbContainer.java
new file mode 100644
index 0000000..67017db
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbContainer.java
@@ -0,0 +1,155 @@
+/*
+ * 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.assist;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.widget.FrameLayout;
+
+import com.android.systemui.R;
+
+public class AssistOrbContainer extends FrameLayout {
+
+ private static final long EXIT_START_DELAY = 150;
+
+ private final Interpolator mLinearOutSlowInInterpolator;
+ private final Interpolator mFastOutLinearInInterpolator;
+
+ private View mScrim;
+ private View mNavbarScrim;
+ private AssistOrbView mOrb;
+
+ private boolean mAnimatingOut;
+
+ public AssistOrbContainer(Context context) {
+ this(context, null);
+ }
+
+ public AssistOrbContainer(Context context, @Nullable AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public AssistOrbContainer(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
+ android.R.interpolator.linear_out_slow_in);
+ mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,
+ android.R.interpolator.fast_out_slow_in);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mScrim = findViewById(R.id.assist_orb_scrim);
+ mNavbarScrim = findViewById(R.id.assist_orb_navbar_scrim);
+ mOrb = (AssistOrbView) findViewById(R.id.assist_orb);
+ }
+
+ public void show(final boolean show, boolean animate) {
+ if (show) {
+ if (getVisibility() != View.VISIBLE) {
+ setVisibility(View.VISIBLE);
+ if (animate) {
+ startEnterAnimation();
+ } else {
+ reset();
+ }
+ }
+ } else {
+ if (animate) {
+ startExitAnimation(new Runnable() {
+ @Override
+ public void run() {
+ mAnimatingOut = false;
+ setVisibility(View.GONE);
+ }
+ });
+ } else {
+ setVisibility(View.GONE);
+ }
+ }
+ }
+
+ private void reset() {
+ mAnimatingOut = false;
+ mOrb.reset();
+ mScrim.setAlpha(1f);
+ mNavbarScrim.setAlpha(1f);
+ }
+
+ private void startEnterAnimation() {
+ if (mAnimatingOut) {
+ return;
+ }
+ mOrb.startEnterAnimation();
+ mScrim.setAlpha(0f);
+ mNavbarScrim.setAlpha(0f);
+ post(new Runnable() {
+ @Override
+ public void run() {
+ mScrim.animate()
+ .alpha(1f)
+ .setDuration(300)
+ .setStartDelay(0)
+ .setInterpolator(mLinearOutSlowInInterpolator);
+ mNavbarScrim.animate()
+ .alpha(1f)
+ .setDuration(300)
+ .setStartDelay(0)
+ .setInterpolator(mLinearOutSlowInInterpolator);
+ }
+ });
+ }
+
+ private void startExitAnimation(final Runnable endRunnable) {
+ if (mAnimatingOut) {
+ if (endRunnable != null) {
+ endRunnable.run();
+ }
+ return;
+ }
+ mAnimatingOut = true;
+ mOrb.startExitAnimation(EXIT_START_DELAY);
+ mScrim.animate()
+ .alpha(0f)
+ .setDuration(250)
+ .setStartDelay(EXIT_START_DELAY)
+ .setInterpolator(mFastOutLinearInInterpolator);
+ mNavbarScrim.animate()
+ .alpha(0f)
+ .setDuration(250)
+ .setStartDelay(EXIT_START_DELAY)
+ .setInterpolator(mFastOutLinearInInterpolator)
+ .withEndAction(endRunnable);
+ }
+
+ /**
+ * Whether the panel is showing, or, if it's animating, whether it will be
+ * when the animation is done.
+ */
+ public boolean isShowing() {
+ return getVisibility() == View.VISIBLE && !mAnimatingOut;
+ }
+
+ public AssistOrbView getOrb() {
+ return mOrb;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistOrbView.java b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbView.java
new file mode 100644
index 0000000..a3372a8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbView.java
@@ -0,0 +1,285 @@
+/*
+ * 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.assist;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Outline;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewOutlineProvider;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.view.animation.OvershootInterpolator;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import com.android.systemui.R;
+
+public class AssistOrbView extends FrameLayout {
+
+ private final int mCircleMinSize;
+ private final int mBaseMargin;
+ private final int mStaticOffset;
+ private final Paint mBackgroundPaint = new Paint();
+ private final Rect mCircleRect = new Rect();
+ private final Rect mStaticRect = new Rect();
+ private final Interpolator mAppearInterpolator;
+ private final Interpolator mDisappearInterpolator;
+ private final Interpolator mOvershootInterpolator = new OvershootInterpolator();
+
+ private boolean mClipToOutline;
+ private final int mMaxElevation;
+ private float mOutlineAlpha;
+ private float mOffset;
+ private float mCircleSize;
+ private ImageView mLogo;
+ private float mCircleAnimationEndValue;
+
+ private ValueAnimator mOffsetAnimator;
+ private ValueAnimator mCircleAnimator;
+
+ private ValueAnimator.AnimatorUpdateListener mCircleUpdateListener
+ = new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ applyCircleSize((float) animation.getAnimatedValue());
+ updateElevation();
+ }
+ };
+ private AnimatorListenerAdapter mClearAnimatorListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mCircleAnimator = null;
+ }
+ };
+ private ValueAnimator.AnimatorUpdateListener mOffsetUpdateListener
+ = new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ mOffset = (float) animation.getAnimatedValue();
+ updateLayout();
+ }
+ };
+
+
+ public AssistOrbView(Context context) {
+ this(context, null);
+ }
+
+ public AssistOrbView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public AssistOrbView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public AssistOrbView(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ setOutlineProvider(new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ if (mCircleSize > 0.0f) {
+ outline.setOval(mCircleRect);
+ } else {
+ outline.setEmpty();
+ }
+ outline.setAlpha(mOutlineAlpha);
+ }
+ });
+ setWillNotDraw(false);
+ mCircleMinSize = context.getResources().getDimensionPixelSize(
+ R.dimen.assist_orb_size);
+ mBaseMargin = context.getResources().getDimensionPixelSize(
+ R.dimen.assist_orb_base_margin);
+ mStaticOffset = context.getResources().getDimensionPixelSize(
+ R.dimen.assist_orb_travel_distance);
+ mMaxElevation = context.getResources().getDimensionPixelSize(
+ R.dimen.assist_orb_elevation);
+ mAppearInterpolator = AnimationUtils.loadInterpolator(mContext,
+ android.R.interpolator.linear_out_slow_in);
+ mDisappearInterpolator = AnimationUtils.loadInterpolator(mContext,
+ android.R.interpolator.fast_out_linear_in);
+ mBackgroundPaint.setAntiAlias(true);
+ mBackgroundPaint.setColor(getResources().getColor(R.color.assist_orb_color));
+ }
+
+ public ImageView getLogo() {
+ return mLogo;
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ drawBackground(canvas);
+ }
+
+ private void drawBackground(Canvas canvas) {
+ canvas.drawCircle(mCircleRect.centerX(), mCircleRect.centerY(), mCircleSize / 2,
+ mBackgroundPaint);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mLogo = (ImageView) findViewById(R.id.search_logo);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ mLogo.layout(0, 0, mLogo.getMeasuredWidth(), mLogo.getMeasuredHeight());
+ if (changed) {
+ updateCircleRect(mStaticRect, mStaticOffset, true);
+ }
+ }
+
+ public void animateCircleSize(float circleSize, long duration,
+ long startDelay, Interpolator interpolator) {
+ if (circleSize == mCircleAnimationEndValue) {
+ return;
+ }
+ if (mCircleAnimator != null) {
+ mCircleAnimator.cancel();
+ }
+ mCircleAnimator = ValueAnimator.ofFloat(mCircleSize, circleSize);
+ mCircleAnimator.addUpdateListener(mCircleUpdateListener);
+ mCircleAnimator.addListener(mClearAnimatorListener);
+ mCircleAnimator.setInterpolator(interpolator);
+ mCircleAnimator.setDuration(duration);
+ mCircleAnimator.setStartDelay(startDelay);
+ mCircleAnimator.start();
+ mCircleAnimationEndValue = circleSize;
+ }
+
+ private void applyCircleSize(float circleSize) {
+ mCircleSize = circleSize;
+ updateLayout();
+ }
+
+ private void updateElevation() {
+ float t = (mStaticOffset - mOffset) / (float) mStaticOffset;
+ t = 1.0f - Math.max(t, 0.0f);
+ float offset = t * mMaxElevation;
+ setElevation(offset);
+ }
+
+ /**
+ * Animates the offset to the edge of the screen.
+ *
+ * @param offset The offset to apply.
+ * @param startDelay The desired start delay if animated.
+ *
+ * @param interpolator The desired interpolator if animated. If null,
+ * a default interpolator will be taken designed for appearing or
+ * disappearing.
+ */
+ private void animateOffset(float offset, long duration, long startDelay,
+ Interpolator interpolator) {
+ if (mOffsetAnimator != null) {
+ mOffsetAnimator.removeAllListeners();
+ mOffsetAnimator.cancel();
+ }
+ mOffsetAnimator = ValueAnimator.ofFloat(mOffset, offset);
+ mOffsetAnimator.addUpdateListener(mOffsetUpdateListener);
+ mOffsetAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mOffsetAnimator = null;
+ }
+ });
+ mOffsetAnimator.setInterpolator(interpolator);
+ mOffsetAnimator.setStartDelay(startDelay);
+ mOffsetAnimator.setDuration(duration);
+ mOffsetAnimator.start();
+ }
+
+ private void updateLayout() {
+ updateCircleRect();
+ updateLogo();
+ invalidateOutline();
+ invalidate();
+ updateClipping();
+ }
+
+ private void updateClipping() {
+ boolean clip = mCircleSize < mCircleMinSize;
+ if (clip != mClipToOutline) {
+ setClipToOutline(clip);
+ mClipToOutline = clip;
+ }
+ }
+
+ private void updateLogo() {
+ float translationX = (mCircleRect.left + mCircleRect.right) / 2.0f - mLogo.getWidth() / 2.0f;
+ float translationY = (mCircleRect.top + mCircleRect.bottom) / 2.0f
+ - mLogo.getHeight() / 2.0f - mCircleMinSize / 7f;
+ float t = (mStaticOffset - mOffset) / (float) mStaticOffset;
+ translationY += t * mStaticOffset * 0.1f;
+ float alpha = 1.0f-t;
+ alpha = Math.max((alpha - 0.5f) * 2.0f, 0);
+ mLogo.setImageAlpha((int) (alpha * 255));
+ mLogo.setTranslationX(translationX);
+ mLogo.setTranslationY(translationY);
+ }
+
+ private void updateCircleRect() {
+ updateCircleRect(mCircleRect, mOffset, false);
+ }
+
+ private void updateCircleRect(Rect rect, float offset, boolean useStaticSize) {
+ int left, top;
+ float circleSize = useStaticSize ? mCircleMinSize : mCircleSize;
+ left = (int) (getWidth() - circleSize) / 2;
+ top = (int) (getHeight() - circleSize / 2 - mBaseMargin - offset);
+ rect.set(left, top, (int) (left + circleSize), (int) (top + circleSize));
+ }
+
+ public void startExitAnimation(long delay) {
+ animateCircleSize(0, 200, delay, mDisappearInterpolator);
+ animateOffset(0, 200, delay, mDisappearInterpolator);
+ }
+
+ public void startEnterAnimation() {
+ applyCircleSize(0);
+ post(new Runnable() {
+ @Override
+ public void run() {
+ animateCircleSize(mCircleMinSize, 300, 0 /* delay */, mOvershootInterpolator);
+ animateOffset(mStaticOffset, 400, 0 /* delay */, mAppearInterpolator);
+ }
+ });
+ }
+
+ public void reset() {
+ mClipToOutline = false;
+ mBackgroundPaint.setAlpha(255);
+ mOutlineAlpha = 1.0f;
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ // not really true but it's ok during an animation, as it's never permanent
+ return false;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index 1f3a830..3f72125 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -82,11 +82,9 @@ public class DozeLog {
sNotificationPulseStats.append();
}
- public static void traceDozing(Context context, boolean dozing) {
- if (!ENABLED) return;
- sPulsing = false;
+ private static void init(Context context) {
synchronized (DozeLog.class) {
- if (dozing && sMessages == null) {
+ if (sMessages == null) {
sTimes = new long[SIZE];
sMessages = new String[SIZE];
sSince = System.currentTimeMillis();
@@ -105,6 +103,12 @@ public class DozeLog {
KeyguardUpdateMonitor.getInstance(context).registerCallback(sKeyguardCallback);
}
}
+ }
+
+ public static void traceDozing(Context context, boolean dozing) {
+ if (!ENABLED) return;
+ sPulsing = false;
+ init(context);
log("dozing " + dozing);
}
@@ -146,10 +150,12 @@ public class DozeLog {
}
}
- public static void traceProximityResult(boolean near, long millis, int pulseReason) {
+ public static void traceProximityResult(Context context, boolean near, long millis,
+ int pulseReason) {
if (!ENABLED) return;
log("proximityResult reason=" + pulseReasonToString(pulseReason) + " near=" + near
+ " millis=" + millis);
+ init(context);
sProxStats[pulseReason][near ? 0 : 1].append();
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
index 8d27450..5d46712 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
@@ -235,7 +235,7 @@ public class DozeService extends DreamService {
public void onProximityResult(int result) {
final boolean isNear = result == RESULT_NEAR;
final long end = SystemClock.uptimeMillis();
- DozeLog.traceProximityResult(isNear, end - start, reason);
+ DozeLog.traceProximityResult(mContext, isNear, end - start, reason);
if (nonBlocking) {
// we already continued
return;
diff --git a/packages/SystemUI/src/com/android/systemui/egg/LLandActivity.java b/packages/SystemUI/src/com/android/systemui/egg/LLandActivity.java
index b9f8106..50221d3 100644
--- a/packages/SystemUI/src/com/android/systemui/egg/LLandActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/egg/LLandActivity.java
@@ -18,7 +18,6 @@ package com.android.systemui.egg;
import android.app.Activity;
import android.os.Bundle;
-import android.util.Log;
import android.widget.TextView;
import com.android.systemui.R;
diff --git a/packages/SystemUI/src/com/android/systemui/egg/ShruggyActivity.java b/packages/SystemUI/src/com/android/systemui/egg/ShruggyActivity.java
new file mode 100644
index 0000000..7459957
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/egg/ShruggyActivity.java
@@ -0,0 +1,34 @@
+/*
+ * 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.egg;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.Toast;
+import com.android.systemui.R;
+
+public class ShruggyActivity extends Activity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Toast.makeText(this, getString(R.string.regrettable_lack_of_easter_egg),
+ Toast.LENGTH_SHORT).show();
+ Log.v("SystemUI", "Hey, it's just a preview; what did you expect?");
+ finish();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 73fa2ed..98558b4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -108,15 +108,21 @@ public class KeyguardService extends Service {
}
@Override // Binder interface
- public void onScreenTurnedOff(int reason) {
+ public void onStartedGoingToSleep(int reason) {
checkPermission();
- mKeyguardViewMediator.onScreenTurnedOff(reason);
+ mKeyguardViewMediator.onStartedGoingToSleep(reason);
}
@Override // Binder interface
- public void onScreenTurnedOn(IKeyguardShowCallback callback) {
+ public void onFinishedGoingToSleep(int reason) {
checkPermission();
- mKeyguardViewMediator.onScreenTurnedOn(callback);
+ mKeyguardViewMediator.onFinishedGoingToSleep(reason);
+ }
+
+ @Override // Binder interface
+ public void onStartedWakingUp(IKeyguardShowCallback callback) {
+ checkPermission();
+ mKeyguardViewMediator.onStartedWakingUp(callback);
}
@Override // Binder interface
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index fae0643..7f61fc1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -33,6 +33,7 @@ import android.content.pm.UserInfo;
import android.media.AudioManager;
import android.media.SoundPool;
import android.os.Bundle;
+import android.os.DeadObjectException;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -61,9 +62,9 @@ import com.android.internal.telephony.IccCardConstants;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardConstants;
import com.android.keyguard.KeyguardDisplayManager;
+import com.android.keyguard.KeyguardSecurityView;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.keyguard.MultiUserAvatarCache;
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.SystemUI;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
@@ -172,8 +173,13 @@ public class KeyguardViewMediator extends SystemUI {
*/
private static final String KEYGUARD_ANALYTICS_SETTING = "keyguard_analytics";
+ /**
+ * How much faster we collapse the lockscreen when authenticating with fingerprint.
+ */
+ private static final float FINGERPRINT_COLLAPSE_SPEEDUP_FACTOR = 1.3f;
+
/** The stream type that the lock sounds are tied to. */
- private int mMasterStreamType;
+ private int mUiSoundsStreamType;
private AlarmManager mAlarmManager;
private AudioManager mAudioManager;
@@ -184,11 +190,6 @@ public class KeyguardViewMediator extends SystemUI {
private boolean mBootCompleted;
private boolean mBootSendUserPresent;
- // Whether the next call to playSounds() should be skipped. Defaults to
- // true because the first lock (on boot) should be silent.
- private boolean mSuppressNextLockSound = true;
-
-
/** High level access to the power manager for WakeLocks */
private PowerManager mPM;
@@ -251,7 +252,7 @@ public class KeyguardViewMediator extends SystemUI {
private KeyguardUpdateMonitor mUpdateMonitor;
- private boolean mScreenOn;
+ private boolean mDeviceInteractive;
// last known state of the cellular connection
private String mPhoneState = TelephonyManager.EXTRA_STATE_IDLE;
@@ -302,6 +303,18 @@ public class KeyguardViewMediator extends SystemUI {
private final ArrayList<IKeyguardStateCallback> mKeyguardStateCallbacks = new ArrayList<>();
+ /**
+ * When starting going to sleep, we figured out that we need to reset Keyguard state and this
+ * should be committed when finished going to sleep.
+ */
+ private boolean mPendingReset;
+
+ /**
+ * When starting goign to sleep, we figured out that we need to lock Keyguard and this should be
+ * committed when finished going to sleep.
+ */
+ private boolean mPendingLock;
+
KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() {
@Override
@@ -314,9 +327,6 @@ public class KeyguardViewMediator extends SystemUI {
resetKeyguardDonePendingLocked();
resetStateLocked();
adjustStatusBarLocked();
- // When we switch users we want to bring the new user to the biometric unlock even
- // if the current user has gone to the backup.
- KeyguardUpdateMonitor.getInstance(mContext).setAlternateUnlockEnabled(true);
}
}
@@ -333,21 +343,14 @@ public class KeyguardViewMediator extends SystemUI {
}
@Override
- public void onUserRemoved(int userId) {
- mLockPatternUtils.removeUser(userId);
- MultiUserAvatarCache.getInstance().clear(userId);
- }
-
- @Override
public void onUserInfoChanged(int userId) {
- MultiUserAvatarCache.getInstance().clear(userId);
}
@Override
public void onPhoneStateChanged(int phoneState) {
synchronized (KeyguardViewMediator.this) {
if (TelephonyManager.CALL_STATE_IDLE == phoneState // call ending
- && !mScreenOn // screen off
+ && !mDeviceInteractive // screen off
&& mExternallyEnabled) { // not disabled by any app
// note: this is a way to gracefully reenable the keyguard when the call
@@ -369,7 +372,6 @@ public class KeyguardViewMediator extends SystemUI {
@Override
public void onDeviceProvisioned() {
sendUserPresentBroadcast();
- updateInputRestricted();
}
@Override
@@ -380,14 +382,17 @@ public class KeyguardViewMediator extends SystemUI {
+ ",state=" + simState + ")");
}
- try {
- int size = mKeyguardStateCallbacks.size();
- boolean simPinSecure = mUpdateMonitor.isSimPinSecure();
- for (int i = 0; i < size; i++) {
+ int size = mKeyguardStateCallbacks.size();
+ boolean simPinSecure = mUpdateMonitor.isSimPinSecure();
+ for (int i = size - 1; i >= 0; i--) {
+ try {
mKeyguardStateCallbacks.get(i).onSimSecureStateChanged(simPinSecure);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to call onSimSecureStateChanged", e);
+ if (e instanceof DeadObjectException) {
+ mKeyguardStateCallbacks.remove(i);
+ }
}
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to call onSimSecureStateChanged", e);
}
switch (simState) {
@@ -447,9 +452,13 @@ public class KeyguardViewMediator extends SystemUI {
}
}
- public void onFingerprintRecognized(int userId) {
+ @Override
+ public void onFingerprintAuthenticated(int userId) {
if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
- mViewMediatorCallback.keyguardDone(true);
+ mStatusBarKeyguardViewManager.notifyKeyguardAuthenticated();
+ } else {
+ mStatusBarKeyguardViewManager.animateCollapsePanels(
+ FINGERPRINT_COLLAPSE_SPEEDUP_FACTOR);
}
};
@@ -477,11 +486,6 @@ public class KeyguardViewMediator extends SystemUI {
}
@Override
- public void onUserActivityTimeoutChanged() {
- mStatusBarKeyguardViewManager.updateUserActivityTimeout();
- }
-
- @Override
public void keyguardDonePending() {
mKeyguardDonePending = true;
mHideAnimationRun = true;
@@ -505,6 +509,11 @@ public class KeyguardViewMediator extends SystemUI {
}
@Override
+ public void resetKeyguard() {
+ resetStateLocked();
+ }
+
+ @Override
public void playTrustedSound() {
KeyguardViewMediator.this.playTrustedSound();
}
@@ -513,6 +522,22 @@ public class KeyguardViewMediator extends SystemUI {
public boolean isInputRestricted() {
return KeyguardViewMediator.this.isInputRestricted();
}
+
+ @Override
+ public boolean isScreenOn() {
+ return mDeviceInteractive;
+ }
+
+ @Override
+ public int getBouncerPromptReason() {
+ int currentUser = ActivityManager.getCurrentUser();
+ if ((mUpdateMonitor.getUserTrustIsManaged(currentUser)
+ || mUpdateMonitor.isUnlockWithFingerPrintPossible(currentUser))
+ && !mTrustManager.hasUserAuthenticatedSinceBoot(currentUser)) {
+ return KeyguardSecurityView.PROMPT_REASON_RESTART;
+ }
+ return KeyguardSecurityView.PROMPT_REASON_NONE;
+ }
};
public void userActivity() {
@@ -536,17 +561,19 @@ public class KeyguardViewMediator extends SystemUI {
mUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
mLockPatternUtils = new LockPatternUtils(mContext);
- mLockPatternUtils.setCurrentUser(ActivityManager.getCurrentUser());
+ KeyguardUpdateMonitor.setCurrentUser(ActivityManager.getCurrentUser());
// Assume keyguard is showing (unless it's disabled) until we know for sure...
- setShowingLocked(!shouldWaitForProvisioning() && !mLockPatternUtils.isLockScreenDisabled());
+ setShowingLocked(!shouldWaitForProvisioning() && !mLockPatternUtils.isLockScreenDisabled(
+ KeyguardUpdateMonitor.getCurrentUser()));
+ updateInputRestrictedLocked();
mTrustManager.reportKeyguardShowingChanged();
mStatusBarKeyguardViewManager = new StatusBarKeyguardViewManager(mContext,
mViewMediatorCallback, mLockPatternUtils);
final ContentResolver cr = mContext.getContentResolver();
- mScreenOn = mPM.isScreenOn();
+ mDeviceInteractive = mPM.isInteractive();
mLockSounds = new SoundPool(1, AudioManager.STREAM_SYSTEM, 0);
String soundPath = Settings.Global.getString(cr, Settings.Global.LOCK_SOUND);
@@ -595,26 +622,8 @@ public class KeyguardViewMediator extends SystemUI {
synchronized (this) {
if (DEBUG) Log.d(TAG, "onSystemReady");
mSystemReady = true;
- mUpdateMonitor.registerCallback(mUpdateCallback);
-
- // Suppress biometric unlock right after boot until things have settled if it is the
- // selected security method, otherwise unsuppress it. It must be unsuppressed if it is
- // not the selected security method for the following reason: if the user starts
- // without a screen lock selected, the biometric unlock would be suppressed the first
- // time they try to use it.
- //
- // Note that the biometric unlock will still not show if it is not the selected method.
- // Calling setAlternateUnlockEnabled(true) simply says don't suppress it if it is the
- // selected method.
- if (mLockPatternUtils.usingBiometricWeak()
- && mLockPatternUtils.isBiometricWeakInstalled()) {
- if (DEBUG) Log.d(TAG, "suppressing biometric unlock during boot");
- mUpdateMonitor.setAlternateUnlockEnabled(false);
- } else {
- mUpdateMonitor.setAlternateUnlockEnabled(true);
- }
-
doKeyguardLocked(null);
+ mUpdateMonitor.registerCallback(mUpdateCallback);
}
// Most services aren't available until the system reaches the ready state, so we
// send it here when the device first boots.
@@ -626,21 +635,18 @@ public class KeyguardViewMediator extends SystemUI {
* @param why either {@link android.view.WindowManagerPolicy#OFF_BECAUSE_OF_USER} or
* {@link android.view.WindowManagerPolicy#OFF_BECAUSE_OF_TIMEOUT}.
*/
- public void onScreenTurnedOff(int why) {
+ public void onStartedGoingToSleep(int why) {
+ if (DEBUG) Log.d(TAG, "onStartedGoingToSleep(" + why + ")");
synchronized (this) {
- mScreenOn = false;
- if (DEBUG) Log.d(TAG, "onScreenTurnedOff(" + why + ")");
-
- resetKeyguardDonePendingLocked();
- mHideAnimationRun = false;
+ mDeviceInteractive = false;
// Lock immediately based on setting if secure (user has a pin/pattern/password).
// This also "locks" the device when not secure to provide easy access to the
// camera while preventing unwanted input.
+ int currentUser = KeyguardUpdateMonitor.getCurrentUser();
final boolean lockImmediately =
- mLockPatternUtils.getPowerButtonInstantlyLocks() || !mLockPatternUtils.isSecure();
-
- notifyScreenOffLocked();
+ mLockPatternUtils.getPowerButtonInstantlyLocks(currentUser)
+ || !mLockPatternUtils.isSecure(currentUser);
if (mExitSecureCallback != null) {
if (DEBUG) Log.d(TAG, "pending exit secure callback cancelled");
@@ -654,15 +660,40 @@ public class KeyguardViewMediator extends SystemUI {
hideLocked();
}
} else if (mShowing) {
- resetStateLocked();
+ mPendingReset = true;
} else if (why == WindowManagerPolicy.OFF_BECAUSE_OF_TIMEOUT
- || (why == WindowManagerPolicy.OFF_BECAUSE_OF_USER && !lockImmediately)) {
+ || (why == WindowManagerPolicy.OFF_BECAUSE_OF_USER && !lockImmediately)) {
doKeyguardLaterLocked();
- } else {
+ } else if (!mLockPatternUtils.isLockScreenDisabled(currentUser)) {
+ mPendingLock = true;
+ }
+
+ if (mPendingLock) {
+ playSounds(true);
+ }
+ }
+ }
+
+ public void onFinishedGoingToSleep(int why) {
+ if (DEBUG) Log.d(TAG, "onFinishedGoingToSleep(" + why + ")");
+ synchronized (this) {
+ mDeviceInteractive = false;
+
+ resetKeyguardDonePendingLocked();
+ mHideAnimationRun = false;
+
+ notifyScreenOffLocked();
+
+ if (mPendingReset) {
+ resetStateLocked();
+ mPendingReset = false;
+ }
+ if (mPendingLock) {
doKeyguardLocked(null);
+ mPendingLock = false;
}
}
- KeyguardUpdateMonitor.getInstance(mContext).dispatchScreenTurndOff(why);
+ KeyguardUpdateMonitor.getInstance(mContext).dispatchScreenTurnedOff(why);
}
private void doKeyguardLaterLocked() {
@@ -684,7 +715,7 @@ public class KeyguardViewMediator extends SystemUI {
// From DevicePolicyAdmin
final long policyTimeout = mLockPatternUtils.getDevicePolicyManager()
- .getMaximumTimeToLock(null, mLockPatternUtils.getCurrentUser());
+ .getMaximumTimeToLock(null, KeyguardUpdateMonitor.getCurrentUser());
long timeout;
if (policyTimeout > 0) {
@@ -697,7 +728,6 @@ public class KeyguardViewMediator extends SystemUI {
if (timeout <= 0) {
// Lock now
- mSuppressNextLockSound = true;
doKeyguardLocked(null);
} else {
// Lock in the future
@@ -717,13 +747,15 @@ public class KeyguardViewMediator extends SystemUI {
}
/**
- * Let's us know the screen was turned on.
+ * Let's us know when the device is waking up.
*/
- public void onScreenTurnedOn(IKeyguardShowCallback callback) {
+ public void onStartedWakingUp(IKeyguardShowCallback callback) {
+
+ // TODO: Rename all screen off/on references to interactive/sleeping
synchronized (this) {
- mScreenOn = true;
+ mDeviceInteractive = true;
cancelDoKeyguardLaterLocked();
- if (DEBUG) Log.d(TAG, "onScreenTurnedOn, seq = " + mDelayedShowingSequence);
+ if (DEBUG) Log.d(TAG, "onStartedWakingUp, seq = " + mDelayedShowingSequence);
if (callback != null) {
notifyScreenOnLocked(callback);
}
@@ -733,7 +765,8 @@ public class KeyguardViewMediator extends SystemUI {
}
private void maybeSendUserPresentBroadcast() {
- if (mSystemReady && mLockPatternUtils.isLockScreenDisabled()) {
+ if (mSystemReady && mLockPatternUtils.isLockScreenDisabled(
+ KeyguardUpdateMonitor.getCurrentUser())) {
// Lock screen is disabled because the user has set the preference to "None".
// In this case, send out ACTION_USER_PRESENT here instead of in
// handleKeyguardDone()
@@ -747,7 +780,8 @@ public class KeyguardViewMediator extends SystemUI {
*/
public void onDreamingStarted() {
synchronized (this) {
- if (mScreenOn && mLockPatternUtils.isSecure()) {
+ if (mDeviceInteractive
+ && mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())) {
doKeyguardLaterLocked();
}
}
@@ -758,7 +792,7 @@ public class KeyguardViewMediator extends SystemUI {
*/
public void onDreamingStopped() {
synchronized (this) {
- if (mScreenOn) {
+ if (mDeviceInteractive) {
cancelDoKeyguardLaterLocked();
}
}
@@ -892,6 +926,12 @@ public class KeyguardViewMediator extends SystemUI {
*/
private void handleSetOccluded(boolean isOccluded) {
synchronized (KeyguardViewMediator.this) {
+ if (mHiding && isOccluded) {
+ // We're in the process of going away but WindowManager wants to show a
+ // SHOW_WHEN_LOCKED activity instead.
+ startKeyguardExitAnimation(0, 0);
+ }
+
if (mOccluded != isOccluded) {
mOccluded = isOccluded;
mStatusBarKeyguardViewManager.setOccluded(isOccluded);
@@ -917,7 +957,7 @@ public class KeyguardViewMediator extends SystemUI {
* was suppressed by an app that disabled the keyguard or we haven't been provisioned yet.
*/
public boolean isInputRestricted() {
- return mShowing || mNeedToReshowWhenReenabled || shouldWaitForProvisioning();
+ return mShowing || mNeedToReshowWhenReenabled;
}
private void updateInputRestricted() {
@@ -929,13 +969,16 @@ public class KeyguardViewMediator extends SystemUI {
boolean inputRestricted = isInputRestricted();
if (mInputRestricted != inputRestricted) {
mInputRestricted = inputRestricted;
- try {
- int size = mKeyguardStateCallbacks.size();
- for (int i = 0; i < size; i++) {
+ int size = mKeyguardStateCallbacks.size();
+ for (int i = size - 1; i >= 0; i--) {
+ try {
mKeyguardStateCallbacks.get(i).onInputRestrictedStateChanged(inputRestricted);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to call onDeviceProvisioned", e);
+ if (e instanceof DeadObjectException) {
+ mKeyguardStateCallbacks.remove(i);
+ }
}
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to call onDeviceProvisioned", e);
}
}
}
@@ -982,12 +1025,13 @@ public class KeyguardViewMediator extends SystemUI {
return;
}
- if (mLockPatternUtils.isLockScreenDisabled() && !lockedOrMissing) {
+ if (mLockPatternUtils.isLockScreenDisabled(KeyguardUpdateMonitor.getCurrentUser())
+ && !lockedOrMissing) {
if (DEBUG) Log.d(TAG, "doKeyguard: not showing because lockscreen is off");
return;
}
- if (mLockPatternUtils.checkVoldPassword()) {
+ if (mLockPatternUtils.checkVoldPassword(KeyguardUpdateMonitor.getCurrentUser())) {
if (DEBUG) Log.d(TAG, "Not showing lock screen since just decrypted");
// Without this, settings is not enabled until the lock screen first appears
setShowingLocked(false);
@@ -1080,7 +1124,7 @@ public class KeyguardViewMediator extends SystemUI {
}
public boolean isSecure() {
- return mLockPatternUtils.isSecure()
+ return mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())
|| KeyguardUpdateMonitor.getInstance(mContext).isSimPinSecure();
}
@@ -1091,7 +1135,7 @@ public class KeyguardViewMediator extends SystemUI {
* @param newUserId The id of the incoming user.
*/
public void setCurrentUser(int newUserId) {
- mLockPatternUtils.setCurrentUser(newUserId);
+ KeyguardUpdateMonitor.setCurrentUser(newUserId);
}
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@@ -1103,8 +1147,6 @@ public class KeyguardViewMediator extends SystemUI {
+ sequence + ", mDelayedShowingSequence = " + mDelayedShowingSequence);
synchronized (KeyguardViewMediator.this) {
if (mDelayedShowingSequence == sequence) {
- // Don't play lockscreen SFX if the screen went off due to timeout.
- mSuppressNextLockSound = true;
doKeyguardLocked(null);
}
}
@@ -1221,7 +1263,7 @@ public class KeyguardViewMediator extends SystemUI {
private void sendUserPresentBroadcast() {
synchronized (this) {
if (mBootCompleted) {
- final UserHandle currentUser = new UserHandle(mLockPatternUtils.getCurrentUser());
+ final UserHandle currentUser = new UserHandle(KeyguardUpdateMonitor.getCurrentUser());
final UserManager um = (UserManager) mContext.getSystemService(
Context.USER_SERVICE);
List <UserInfo> userHandles = um.getProfiles(currentUser.getIdentifier());
@@ -1255,13 +1297,6 @@ public class KeyguardViewMediator extends SystemUI {
}
private void playSounds(boolean locked) {
- // User feedback for keyguard.
-
- if (mSuppressNextLockSound) {
- mSuppressNextLockSound = false;
- return;
- }
-
playSound(locked ? mLockSoundId : mUnlockSoundId);
}
@@ -1275,10 +1310,10 @@ public class KeyguardViewMediator extends SystemUI {
if (mAudioManager == null) {
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
if (mAudioManager == null) return;
- mMasterStreamType = mAudioManager.getMasterStreamType();
+ mUiSoundsStreamType = mAudioManager.getUiSoundsStreamType();
}
// If the stream is muted, don't play the sound
- if (mAudioManager.isStreamMute(mMasterStreamType)) return;
+ if (mAudioManager.isStreamMute(mUiSoundsStreamType)) return;
mLockSoundStreamId = mLockSounds.play(soundId,
mLockSoundVolume, mLockSoundVolume, 1/*priortiy*/, 0/*loop*/, 1.0f/*rate*/);
@@ -1286,9 +1321,6 @@ public class KeyguardViewMediator extends SystemUI {
}
private void playTrustedSound() {
- if (mSuppressNextLockSound) {
- return;
- }
playSound(mTrustedSoundId);
}
@@ -1321,9 +1353,6 @@ public class KeyguardViewMediator extends SystemUI {
adjustStatusBarLocked();
userActivity();
- // Do this at the end to not slow down display of the keyguard.
- playSounds(true);
-
mShowKeyguardWakeLock.release();
}
mKeyguardDisplayManager.show();
@@ -1333,10 +1362,12 @@ public class KeyguardViewMediator extends SystemUI {
@Override
public void run() {
try {
+ mStatusBarKeyguardViewManager.keyguardGoingAway();
+
// Don't actually hide the Keyguard at the moment, wait for window
// manager until it tells us it's safe to do so with
// startKeyguardExitAnimation.
- mWM.keyguardGoingAway(
+ ActivityManagerNative.getDefault().keyguardGoingAway(
mStatusBarKeyguardViewManager.shouldDisableWindowAnimationsForUnlock(),
mStatusBarKeyguardViewManager.isGoingToNotificationShade());
} catch (RemoteException e) {
@@ -1531,13 +1562,16 @@ public class KeyguardViewMediator extends SystemUI {
private void setShowingLocked(boolean showing) {
if (showing != mShowing) {
mShowing = showing;
- try {
- int size = mKeyguardStateCallbacks.size();
- for (int i = 0; i < size; i++) {
+ int size = mKeyguardStateCallbacks.size();
+ for (int i = size - 1; i >= 0; i--) {
+ try {
mKeyguardStateCallbacks.get(i).onShowingStateChanged(showing);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to call onShowingStateChanged", e);
+ if (e instanceof DeadObjectException) {
+ mKeyguardStateCallbacks.remove(i);
+ }
}
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to call onShowingStateChanged", e);
}
updateInputRestrictedLocked();
mTrustManager.reportKeyguardShowingChanged();
@@ -1550,8 +1584,9 @@ public class KeyguardViewMediator extends SystemUI {
try {
callback.onSimSecureStateChanged(mUpdateMonitor.isSimPinSecure());
callback.onShowingStateChanged(mShowing);
+ callback.onInputRestrictedStateChanged(mInputRestricted);
} catch (RemoteException e) {
- Slog.w(TAG, "Failed to call onShowingStateChanged or onSimSecureStateChanged", e);
+ Slog.w(TAG, "Failed to call onShowingStateChanged or onSimSecureStateChanged or onInputRestrictedStateChanged", e);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
index c23f45d..2a84362 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
@@ -18,8 +18,6 @@ package com.android.systemui.media;
import android.app.Activity;
import android.app.AlertDialog;
-import android.app.PendingIntent;
-import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -32,16 +30,10 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
-import android.view.LayoutInflater;
import android.view.WindowManager;
import android.widget.CheckBox;
import android.widget.CompoundButton;
-import android.widget.TextView;
-
-import com.android.internal.app.AlertActivity;
-import com.android.internal.app.AlertController;
import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.SystemUIDialog;
public class MediaProjectionPermissionActivity extends Activity
implements DialogInterface.OnClickListener, CheckBox.OnCheckedChangeListener,
@@ -104,6 +96,7 @@ public class MediaProjectionPermissionActivity extends Activity
.create();
mDialog.create();
+ mDialog.getButton(DialogInterface.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true);
((CheckBox) mDialog.findViewById(R.id.remember)).setOnCheckedChangeListener(this);
mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index 4391bfc..a12a3f1 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -50,7 +50,6 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
private static final boolean DEBUG = PowerUI.DEBUG;
private static final String TAG_NOTIFICATION = "low_battery";
- private static final int ID_NOTIFICATION = 100;
private static final int SHOWING_NOTHING = 0;
private static final int SHOWING_WARNING = 1;
@@ -145,7 +144,7 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
showSaverNotification();
mShowing = SHOWING_SAVER;
} else {
- mNoMan.cancelAsUser(TAG_NOTIFICATION, ID_NOTIFICATION, UserHandle.ALL);
+ mNoMan.cancelAsUser(TAG_NOTIFICATION, R.id.notification_power, UserHandle.ALL);
mShowing = SHOWING_NOTHING;
}
}
@@ -160,13 +159,13 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
.setContentText(mContext.getString(R.string.invalid_charger_text))
.setPriority(Notification.PRIORITY_MAX)
.setVisibility(Notification.VISIBILITY_PUBLIC)
- .setColor(mContext.getResources().getColor(
+ .setColor(mContext.getColor(
com.android.internal.R.color.system_notification_accent_color));
final Notification n = nb.build();
if (n.headsUpContentView != null) {
n.headsUpContentView.setViewVisibility(com.android.internal.R.id.right_icon, View.GONE);
}
- mNoMan.notifyAsUser(TAG_NOTIFICATION, ID_NOTIFICATION, n, UserHandle.ALL);
+ mNoMan.notifyAsUser(TAG_NOTIFICATION, R.id.notification_power, n, UserHandle.ALL);
}
private void showWarningNotification() {
@@ -184,7 +183,7 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
.setDeleteIntent(pendingBroadcast(ACTION_DISMISSED_WARNING))
.setPriority(Notification.PRIORITY_MAX)
.setVisibility(Notification.VISIBILITY_PUBLIC)
- .setColor(mContext.getResources().getColor(
+ .setColor(mContext.getColor(
com.android.internal.R.color.battery_saver_mode_color));
if (hasBatterySettings()) {
nb.setContentIntent(pendingBroadcast(ACTION_SHOW_BATTERY_SETTINGS));
@@ -204,7 +203,7 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
if (n.headsUpContentView != null) {
n.headsUpContentView.setViewVisibility(com.android.internal.R.id.right_icon, View.GONE);
}
- mNoMan.notifyAsUser(TAG_NOTIFICATION, ID_NOTIFICATION, n, UserHandle.ALL);
+ mNoMan.notifyAsUser(TAG_NOTIFICATION, R.id.notification_power, n, UserHandle.ALL);
}
private void showSaverNotification() {
@@ -215,13 +214,13 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
.setOngoing(true)
.setShowWhen(false)
.setVisibility(Notification.VISIBILITY_PUBLIC)
- .setColor(mContext.getResources().getColor(
+ .setColor(mContext.getColor(
com.android.internal.R.color.battery_saver_mode_color));
addStopSaverAction(nb);
if (hasSaverSettings()) {
nb.setContentIntent(pendingActivity(mOpenSaverSettings));
}
- mNoMan.notifyAsUser(TAG_NOTIFICATION, ID_NOTIFICATION, nb.build(), UserHandle.ALL);
+ mNoMan.notifyAsUser(TAG_NOTIFICATION, R.id.notification_power, nb.build(), UserHandle.ALL);
}
private void addStopSaverAction(Notification.Builder nb) {
@@ -377,7 +376,8 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
filter.addAction(ACTION_START_SAVER);
filter.addAction(ACTION_STOP_SAVER);
filter.addAction(ACTION_DISMISSED_WARNING);
- mContext.registerReceiverAsUser(this, UserHandle.ALL, filter, null, mHandler);
+ mContext.registerReceiverAsUser(this, UserHandle.ALL, filter,
+ android.Manifest.permission.STATUS_BAR_SERVICE, mHandler);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/DataUsageGraph.java b/packages/SystemUI/src/com/android/systemui/qs/DataUsageGraph.java
index d55ceaa..aff5d2b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/DataUsageGraph.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/DataUsageGraph.java
@@ -44,10 +44,10 @@ public class DataUsageGraph extends View {
public DataUsageGraph(Context context, AttributeSet attrs) {
super(context, attrs);
final Resources res = context.getResources();
- mTrackColor = res.getColor(R.color.data_usage_graph_track);
- mUsageColor = res.getColor(R.color.system_accent_color);
- mOverlimitColor = res.getColor(R.color.system_warning_color);
- mWarningColor = res.getColor(R.color.data_usage_graph_warning);
+ mTrackColor = context.getColor(R.color.data_usage_graph_track);
+ mUsageColor = context.getColor(R.color.system_accent_color);
+ mOverlimitColor = context.getColor(R.color.system_warning_color);
+ mWarningColor = context.getColor(R.color.data_usage_graph_warning);
mMarkerWidth = res.getDimensionPixelSize(R.dimen.data_usage_graph_marker_width);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java
index 111484b..a318efc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java
@@ -42,14 +42,21 @@ public class QSDetailClipper {
}
final int w = mDetail.getWidth() - x;
final int h = mDetail.getHeight() - y;
+ int innerR = 0;
+ if (x < 0 || w < 0 || y < 0 || h < 0) {
+ innerR = Math.abs(x);
+ innerR = Math.min(innerR, Math.abs(y));
+ innerR = Math.min(innerR, Math.abs(w));
+ innerR = Math.min(innerR, Math.abs(h));
+ }
int r = (int) Math.ceil(Math.sqrt(x * x + y * y));
r = (int) Math.max(r, Math.ceil(Math.sqrt(w * w + y * y)));
r = (int) Math.max(r, Math.ceil(Math.sqrt(w * w + h * h)));
r = (int) Math.max(r, Math.ceil(Math.sqrt(x * x + h * h)));
if (in) {
- mAnimator = ViewAnimationUtils.createCircularReveal(mDetail, x, y, 0, r);
+ mAnimator = ViewAnimationUtils.createCircularReveal(mDetail, x, y, innerR, r);
} else {
- mAnimator = ViewAnimationUtils.createCircularReveal(mDetail, x, y, r, 0);
+ mAnimator = ViewAnimationUtils.createCircularReveal(mDetail, x, y, r, innerR);
}
mAnimator.setDuration((long)(mAnimator.getDuration() * 1.5));
if (listener != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
index 95ac558..25b9105 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
@@ -193,8 +193,6 @@ public class QSDetailItems extends FrameLayout {
title.setMaxLines(twoLines ? 1 : 2);
summary.setVisibility(twoLines ? VISIBLE : GONE);
summary.setText(twoLines ? item.line2 : null);
- view.setMinimumHeight(mContext.getResources() .getDimensionPixelSize(
- twoLines ? R.dimen.qs_detail_item_height_twoline : R.dimen.qs_detail_item_height));
view.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
@@ -239,8 +237,8 @@ public class QSDetailItems extends FrameLayout {
public static class Item {
public int icon;
public Drawable overlay;
- public String line1;
- public String line2;
+ public CharSequence line1;
+ public CharSequence line2;
public Object tag;
public boolean canDisconnect;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
index a0b6e82..ca38528 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
@@ -18,12 +18,12 @@ package com.android.systemui.qs;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
-import android.content.res.Configuration;
+import android.content.Intent;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.os.UserHandle;
import android.util.Log;
-import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
@@ -40,6 +40,8 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene
protected static final String TAG = "QSFooter";
protected static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final String ACTION_VPN_SETTINGS = "android.net.vpn.SETTINGS";
+
private final View mRootView;
private final TextView mFooterText;
private final ImageView mFooterIcon;
@@ -108,21 +110,13 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene
}
private void handleRefreshState() {
+ mIsIconVisible = mSecurityController.isVpnEnabled();
if (mSecurityController.hasDeviceOwner()) {
mFooterTextId = R.string.device_owned_footer;
mIsVisible = true;
- mIsIconVisible = false;
- } else if (mSecurityController.hasProfileOwner()) {
- mFooterTextId = R.string.profile_owned_footer;
- mIsVisible = true;
- mIsIconVisible = false;
- } else if (mSecurityController.isVpnEnabled()) {
- mFooterTextId = R.string.vpn_footer;
- mIsVisible = true;
- mIsIconVisible = true;
} else {
- mIsVisible = false;
- mIsIconVisible = false;
+ mFooterTextId = R.string.vpn_footer;
+ mIsVisible = mIsIconVisible;
}
mMainHandler.post(mUpdateDisplayState);
}
@@ -130,14 +124,21 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene
@Override
public void onClick(DialogInterface dialog, int which) {
if (which == DialogInterface.BUTTON_NEGATIVE) {
- mSecurityController.disconnectFromVpn();
+ final Intent settingsIntent = new Intent(ACTION_VPN_SETTINGS);
+ mContext.startActivityAsUser(settingsIntent, UserHandle.CURRENT);
}
}
private void createDialog() {
+ String deviceOwner = mSecurityController.getDeviceOwnerName();
+ String profileOwner = mSecurityController.getProfileOwnerName();
+ String primaryVpn = mSecurityController.getPrimaryVpnName();
+ String profileVpn = mSecurityController.getProfileVpnName();
+ boolean managed = mSecurityController.hasProfileOwner();
+
mDialog = new SystemUIDialog(mContext);
- mDialog.setTitle(getTitle());
- mDialog.setMessage(getMessage());
+ mDialog.setTitle(getTitle(deviceOwner));
+ mDialog.setMessage(getMessage(deviceOwner, profileOwner, primaryVpn, profileVpn, managed));
mDialog.setButton(DialogInterface.BUTTON_POSITIVE, getPositiveButton(), this);
if (mSecurityController.isVpnEnabled()) {
mDialog.setButton(DialogInterface.BUTTON_NEGATIVE, getNegativeButton(), this);
@@ -146,95 +147,49 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene
}
private String getNegativeButton() {
- if (mSecurityController.isLegacyVpn()) {
- return mContext.getString(R.string.disconnect_vpn);
- } else {
- return mContext.getString(R.string.disable_vpn);
- }
+ return mContext.getString(R.string.status_bar_settings_settings_button);
}
private String getPositiveButton() {
return mContext.getString(R.string.quick_settings_done);
}
- private String getMessage() {
- if (mSecurityController.hasDeviceOwner()) {
- if (mSecurityController.hasProfileOwner()) {
- if (mSecurityController.isVpnEnabled()) {
- if (mSecurityController.isLegacyVpn()) {
- return mContext.getString(
- R.string.monitoring_description_legacy_vpn_device_and_profile_owned,
- mSecurityController.getDeviceOwnerName(),
- mSecurityController.getProfileOwnerName(),
- mSecurityController.getLegacyVpnName());
- } else {
- return mContext.getString(
- R.string.monitoring_description_vpn_device_and_profile_owned,
- mSecurityController.getDeviceOwnerName(),
- mSecurityController.getProfileOwnerName(),
- mSecurityController.getVpnApp());
- }
- } else {
- return mContext.getString(
- R.string.monitoring_description_device_and_profile_owned,
- mSecurityController.getDeviceOwnerName(),
- mSecurityController.getProfileOwnerName());
- }
+ private String getMessage(String deviceOwner, String profileOwner, String primaryVpn,
+ String profileVpn, boolean primaryUserIsManaged) {
+ if (deviceOwner != null) {
+ if (primaryVpn != null) {
+ return mContext.getString(R.string.monitoring_description_vpn_app_device_owned,
+ deviceOwner, primaryVpn);
} else {
- if (mSecurityController.isVpnEnabled()) {
- if (mSecurityController.isLegacyVpn()) {
- return mContext.getString(
- R.string.monitoring_description_legacy_vpn_device_owned,
- mSecurityController.getDeviceOwnerName(),
- mSecurityController.getLegacyVpnName());
- } else {
- return mContext.getString(R.string.monitoring_description_vpn_device_owned,
- mSecurityController.getDeviceOwnerName(),
- mSecurityController.getVpnApp());
- }
- } else {
- return mContext.getString(R.string.monitoring_description_device_owned,
- mSecurityController.getDeviceOwnerName());
- }
+ return mContext.getString(R.string.monitoring_description_device_owned,
+ deviceOwner);
}
- } else if (mSecurityController.hasProfileOwner()) {
- if (mSecurityController.isVpnEnabled()) {
- if (mSecurityController.isLegacyVpn()) {
- return mContext.getString(
- R.string.monitoring_description_legacy_vpn_profile_owned,
- mSecurityController.getProfileOwnerName(),
- mSecurityController.getLegacyVpnName());
- } else {
- return mContext.getString(
- R.string.monitoring_description_vpn_profile_owned,
- mSecurityController.getProfileOwnerName(),
- mSecurityController.getVpnApp());
- }
+ } else if (primaryVpn != null) {
+ if (profileVpn != null) {
+ return mContext.getString(R.string.monitoring_description_app_personal_work,
+ profileOwner, profileVpn, primaryVpn);
} else {
- return mContext.getString(
- R.string.monitoring_description_profile_owned,
- mSecurityController.getProfileOwnerName());
+ return mContext.getString(R.string.monitoring_description_app_personal,
+ primaryVpn);
}
+ } else if (profileVpn != null) {
+ return mContext.getString(R.string.monitoring_description_app_work,
+ profileOwner, profileVpn);
+ } else if (profileOwner != null && primaryUserIsManaged) {
+ return mContext.getString(R.string.monitoring_description_device_owned,
+ profileOwner);
} else {
- if (mSecurityController.isLegacyVpn()) {
- return mContext.getString(R.string.monitoring_description_legacy_vpn,
- mSecurityController.getLegacyVpnName());
-
- } else {
- return mContext.getString(R.string.monitoring_description_vpn,
- mSecurityController.getVpnApp());
- }
+ // No device owner, no personal VPN, no work VPN, no user owner. Why are we here?
+ return null;
}
}
- private int getTitle() {
- if (mSecurityController.hasDeviceOwner()) {
+ private int getTitle(String deviceOwner) {
+ if (deviceOwner != null) {
return R.string.monitoring_title_device_owned;
+ } else {
+ return R.string.monitoring_title;
}
- if (mSecurityController.hasProfileOwner()) {
- return R.string.monitoring_title_profile_owned;
- }
- return R.string.monitoring_title;
}
private final Runnable mUpdateDisplayState = new Runnable() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 4dacacf..2ded919 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -26,7 +26,6 @@ import android.content.res.Resources;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
-import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -34,6 +33,7 @@ import android.view.accessibility.AccessibilityEvent;
import android.widget.ImageView;
import android.widget.TextView;
+import com.android.internal.logging.MetricsLogger;
import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile.DetailAdapter;
@@ -50,12 +50,12 @@ public class QSPanel extends ViewGroup {
private static final float TILE_ASPECT = 1.2f;
private final Context mContext;
- private final ArrayList<TileRecord> mRecords = new ArrayList<TileRecord>();
+ protected final ArrayList<TileRecord> mRecords = new ArrayList<TileRecord>();
private final View mDetail;
private final ViewGroup mDetailContent;
private final TextView mDetailSettingsButton;
private final TextView mDetailDoneButton;
- private final View mBrightnessView;
+ protected final View mBrightnessView;
private final QSDetailClipper mClipper;
private final H mHandler = new H();
@@ -111,6 +111,8 @@ public class QSPanel extends ViewGroup {
mDetailDoneButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
+ announceForAccessibility(
+ mContext.getString(R.string.accessibility_desc_quick_settings));
closeDetail();
}
});
@@ -183,8 +185,11 @@ public class QSPanel extends ViewGroup {
public void setExpanded(boolean expanded) {
if (mExpanded == expanded) return;
mExpanded = expanded;
+ MetricsLogger.visibility(mContext, MetricsLogger.QS_PANEL, mExpanded);
if (!mExpanded) {
closeDetail();
+ } else {
+ logTiles();
}
}
@@ -212,9 +217,19 @@ public class QSPanel extends ViewGroup {
mFooter.refreshState();
}
- public void showDetailAdapter(boolean show, DetailAdapter adapter) {
+ public void showDetailAdapter(boolean show, DetailAdapter adapter, int[] locationInWindow) {
+ int xInWindow = locationInWindow[0];
+ int yInWindow = locationInWindow[1];
+ mDetail.getLocationInWindow(locationInWindow);
+
Record r = new Record();
r.detailAdapter = adapter;
+ r.x = xInWindow - locationInWindow[0];
+ r.y = yInWindow - locationInWindow[1];
+
+ locationInWindow[0] = xInWindow;
+ locationInWindow[1] = yInWindow;
+
showDetail(show, r);
}
@@ -227,6 +242,9 @@ public class QSPanel extends ViewGroup {
}
private void handleSetTileVisibility(View v, int visibility) {
+ if (visibility == VISIBLE && !mGridContentVisible) {
+ visibility = INVISIBLE;
+ }
if (visibility == v.getVisibility()) return;
v.setVisibility(visibility);
}
@@ -244,6 +262,12 @@ public class QSPanel extends ViewGroup {
}
}
+ private void drawTile(TileRecord r, QSTile.State state) {
+ final int visibility = state.visible ? VISIBLE : GONE;
+ setTileVisibility(r.tileView, visibility);
+ r.tileView.onStateChanged(state);
+ }
+
private void addTile(final QSTile<?> tile) {
final TileRecord r = new TileRecord();
r.tile = tile;
@@ -252,15 +276,9 @@ public class QSPanel extends ViewGroup {
final QSTile.Callback callback = new QSTile.Callback() {
@Override
public void onStateChanged(QSTile.State state) {
- int visibility = state.visible ? VISIBLE : GONE;
- if (state.visible && !mGridContentVisible) {
-
- // We don't want to show it if the content is hidden,
- // then we just set it to invisible, to ensure that it gets visible again
- visibility = INVISIBLE;
+ if (!r.openingDetail) {
+ drawTile(r, state);
}
- setTileVisibility(r.tileView, visibility);
- r.tileView.onStateChanged(state);
}
@Override
public void onShowDetail(boolean show) {
@@ -334,24 +352,32 @@ public class QSPanel extends ViewGroup {
if (r instanceof TileRecord) {
handleShowDetailTile((TileRecord) r, show);
} else {
- handleShowDetailImpl(r, show, getWidth() /* x */, 0/* y */);
+ int x = 0;
+ int y = 0;
+ if (r != null) {
+ x = r.x;
+ y = r.y;
+ }
+ handleShowDetailImpl(r, show, x, y);
}
}
private void handleShowDetailTile(TileRecord r, boolean show) {
- if ((mDetailRecord != null) == show) return;
+ if ((mDetailRecord != null) == show && mDetailRecord == r) return;
if (show) {
r.detailAdapter = r.tile.getDetailAdapter();
if (r.detailAdapter == null) return;
}
+ r.tile.setDetailListening(show);
int x = r.tileView.getLeft() + r.tileView.getWidth() / 2;
int y = r.tileView.getTop() + r.tileView.getHeight() / 2;
handleShowDetailImpl(r, show, x, y);
}
private void handleShowDetailImpl(Record r, boolean show, int x, int y) {
- if ((mDetailRecord != null) == show) return; // already in right state
+ boolean visibleDiff = (mDetailRecord != null) != show;
+ if (!visibleDiff && mDetailRecord == r) return; // already in right state
DetailAdapter detailAdapter = null;
AnimatorListener listener = null;
if (show) {
@@ -364,16 +390,26 @@ public class QSPanel extends ViewGroup {
mDetailSettingsButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
- mHost.startSettingsActivity(settingsIntent);
+ mHost.startActivityDismissingKeyguard(settingsIntent);
}
});
mDetailContent.removeAllViews();
mDetail.bringToFront();
mDetailContent.addView(r.detailView);
+ MetricsLogger.visible(mContext, detailAdapter.getMetricsCategory());
+ announceForAccessibility(mContext.getString(
+ R.string.accessibility_quick_settings_detail,
+ mContext.getString(detailAdapter.getTitle())));
setDetailRecord(r);
listener = mHideGridContentWhenDone;
+ if (r instanceof TileRecord && visibleDiff) {
+ ((TileRecord) r).openingDetail = true;
+ }
} else {
+ if (mDetailRecord != null) {
+ MetricsLogger.hidden(mContext, mDetailRecord.detailAdapter.getMetricsCategory());
+ }
mClosingDetail = true;
setGridContentVisibility(true);
listener = mTeardownDetailWhenDone;
@@ -381,7 +417,9 @@ public class QSPanel extends ViewGroup {
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
fireShowingDetail(show ? detailAdapter : null);
- mClipper.animateCircularClip(x, y, show, listener);
+ if (visibleDiff) {
+ mClipper.animateCircularClip(x, y, show, listener);
+ }
}
private void setGridContentVisibility(boolean visible) {
@@ -393,9 +431,21 @@ public class QSPanel extends ViewGroup {
}
}
mBrightnessView.setVisibility(newVis);
+ if (mGridContentVisible != visible) {
+ MetricsLogger.visibility(mContext, MetricsLogger.QS_PANEL, newVis);
+ }
mGridContentVisible = visible;
}
+ private void logTiles() {
+ for (int i = 0; i < mRecords.size(); i++) {
+ TileRecord tileRecord = mRecords.get(i);
+ if (tileRecord.tile.getState().visible) {
+ MetricsLogger.visible(mContext, tileRecord.tile.getMetricsCategory());
+ }
+ }
+ }
+
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int width = MeasureSpec.getSize(widthMeasureSpec);
@@ -422,6 +472,7 @@ public class QSPanel extends ViewGroup {
rows = r + 1;
}
+ View previousView = mBrightnessView;
for (TileRecord record : mRecords) {
if (record.tileView.setDual(record.tile.supportsDualTargets())) {
record.tileView.handleStateChanged(record.tile.getState());
@@ -430,6 +481,7 @@ public class QSPanel extends ViewGroup {
final int cw = record.row == 0 ? mLargeCellWidth : mCellWidth;
final int ch = record.row == 0 ? mLargeCellHeight : mCellHeight;
record.tileView.measure(exactly(cw), exactly(ch));
+ previousView = record.tileView.updateAccessibilityOrder(previousView);
}
int h = rows == 0 ? brightnessHeight : (getRowTop(rows) + mPanelPaddingBottom);
if (mFooter.hasFooter()) {
@@ -537,14 +589,17 @@ public class QSPanel extends ViewGroup {
private static class Record {
View detailView;
DetailAdapter detailAdapter;
+ int x;
+ int y;
}
- private static final class TileRecord extends Record {
- QSTile<?> tile;
- QSTileView tileView;
- int row;
- int col;
- boolean scanState;
+ protected static final class TileRecord extends Record {
+ public QSTile<?> tile;
+ public QSTileView tileView;
+ public int row;
+ public int col;
+ public boolean scanState;
+ public boolean openingDetail;
}
private final AnimatorListenerAdapter mTeardownDetailWhenDone = new AnimatorListenerAdapter() {
@@ -560,6 +615,7 @@ public class QSPanel extends ViewGroup {
// If we have been cancelled, remove the listener so that onAnimationEnd doesn't get
// called, this will avoid accidentally turning off the grid when we don't want to.
animation.removeListener(this);
+ redrawTile();
};
@Override
@@ -567,6 +623,15 @@ public class QSPanel extends ViewGroup {
// Only hide content if still in detail state.
if (mDetailRecord != null) {
setGridContentVisibility(false);
+ redrawTile();
+ }
+ }
+
+ private void redrawTile() {
+ if (mDetailRecord instanceof TileRecord) {
+ final TileRecord tileRecord = (TileRecord) mDetailRecord;
+ tileRecord.openingDetail = false;
+ drawTile(tileRecord, tileRecord.tile.getState());
}
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
index 1790a4e..38fade2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
@@ -18,6 +18,7 @@ package com.android.systemui.qs;
import android.content.Context;
import android.content.Intent;
+import android.graphics.drawable.Animatable;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
@@ -32,12 +33,12 @@ import com.android.systemui.qs.QSTile.State;
import com.android.systemui.statusbar.policy.BluetoothController;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.FlashlightController;
+import com.android.systemui.statusbar.policy.HotspotController;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
import com.android.systemui.statusbar.policy.Listenable;
import com.android.systemui.statusbar.policy.LocationController;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.RotationLockController;
-import com.android.systemui.statusbar.policy.HotspotController;
import com.android.systemui.statusbar.policy.ZenModeController;
import java.util.Collection;
@@ -68,6 +69,15 @@ public abstract class QSTile<TState extends State> implements Listenable {
abstract protected void handleClick();
abstract protected void handleUpdateState(TState state, Object arg);
+ /**
+ * Declare the category of this tile.
+ *
+ * Categories are defined in {@link com.android.internal.logging.MetricsLogger}
+ * or if there is no relevant existing category you may define one in
+ * {@link com.android.systemui.qs.QSTile}.
+ */
+ abstract public int getMetricsCategory();
+
protected QSTile(Host host) {
mHost = host;
mContext = host.getContext();
@@ -96,6 +106,7 @@ public abstract class QSTile<TState extends State> implements Listenable {
View createDetailView(Context context, View convertView, ViewGroup parent);
Intent getSettingsIntent();
void setToggleState(boolean state);
+ int getMetricsCategory();
}
// safe to call from any thread
@@ -148,6 +159,10 @@ public abstract class QSTile<TState extends State> implements Listenable {
return mState;
}
+ public void setDetailListening(boolean listening) {
+ // optional
+ }
+
// call only on tile worker looper
private void handleSetCallback(Callback callback) {
@@ -291,7 +306,7 @@ public abstract class QSTile<TState extends State> implements Listenable {
}
public interface Host {
- void startSettingsActivity(Intent intent);
+ void startActivityDismissingKeyguard(Intent intent);
void warn(String message, Throwable t);
void collapsePanels();
Looper getLooper();
@@ -325,7 +340,7 @@ public abstract class QSTile<TState extends State> implements Listenable {
public static class ResourceIcon extends Icon {
private static final SparseArray<Icon> ICONS = new SparseArray<Icon>();
- private final int mResId;
+ protected final int mResId;
private ResourceIcon(int resId) {
mResId = resId;
@@ -342,7 +357,11 @@ public abstract class QSTile<TState extends State> implements Listenable {
@Override
public Drawable getDrawable(Context context) {
- return context.getDrawable(mResId);
+ Drawable d = context.getDrawable(mResId);
+ if (d instanceof Animatable) {
+ ((Animatable) d).start();
+ }
+ return d;
}
@Override
@@ -370,7 +389,7 @@ public abstract class QSTile<TState extends State> implements Listenable {
@Override
public Drawable getDrawable(Context context) {
// workaround: get a clean state for every new AVD
- final AnimatedVectorDrawable d = (AnimatedVectorDrawable) super.getDrawable(context)
+ final AnimatedVectorDrawable d = (AnimatedVectorDrawable) context.getDrawable(mResId)
.getConstantState().newDrawable();
d.start();
if (mAllowAnimation) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
index 16ae6b4..6d26a3b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
@@ -38,6 +38,7 @@ import android.widget.TextView;
import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
+import com.android.systemui.qs.QSTile.AnimationIcon;
import com.android.systemui.qs.QSTile.State;
import java.util.Objects;
@@ -82,20 +83,21 @@ public class QSTileView extends ViewGroup {
setClipChildren(false);
mTopBackgroundView = new View(context);
+ mTopBackgroundView.setId(View.generateViewId());
addView(mTopBackgroundView);
mIcon = createIcon();
addView(mIcon);
mDivider = new View(mContext);
- mDivider.setBackgroundColor(res.getColor(R.color.qs_tile_divider));
+ mDivider.setBackgroundColor(context.getColor(R.color.qs_tile_divider));
final int dh = res.getDimensionPixelSize(R.dimen.qs_tile_divider_height);
mDivider.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, dh));
addView(mDivider);
setClickable(true);
-
updateTopPadding();
+ setId(View.generateViewId());
}
private void updateTopPadding() {
@@ -136,10 +138,10 @@ public class QSTileView extends ViewGroup {
final Resources res = mContext.getResources();
if (mDual) {
mDualLabel = new QSDualTileLabel(mContext);
- mDualLabel.setId(android.R.id.title);
+ mDualLabel.setId(View.generateViewId());
mDualLabel.setBackgroundResource(R.drawable.btn_borderless_rect);
- mDualLabel.setFirstLineCaret(res.getDrawable(R.drawable.qs_dual_tile_caret));
- mDualLabel.setTextColor(res.getColor(R.color.qs_tile_text));
+ mDualLabel.setFirstLineCaret(mContext.getDrawable(R.drawable.qs_dual_tile_caret));
+ mDualLabel.setTextColor(mContext.getColor(R.color.qs_tile_text));
mDualLabel.setPadding(0, mDualTileVerticalPaddingPx, 0, mDualTileVerticalPaddingPx);
mDualLabel.setTypeface(CONDENSED);
mDualLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX,
@@ -154,10 +156,10 @@ public class QSTileView extends ViewGroup {
mDualLabel.setContentDescription(labelDescription);
}
addView(mDualLabel);
+ mDualLabel.setAccessibilityTraversalAfter(mTopBackgroundView.getId());
} else {
mLabel = new TextView(mContext);
- mLabel.setId(android.R.id.title);
- mLabel.setTextColor(res.getColor(R.color.qs_tile_text));
+ mLabel.setTextColor(mContext.getColor(R.color.qs_tile_text));
mLabel.setGravity(Gravity.CENTER_HORIZONTAL);
mLabel.setMinLines(2);
mLabel.setPadding(0, 0, 0, 0);
@@ -315,8 +317,9 @@ public class QSTileView extends ViewGroup {
iv.setImageDrawable(d);
iv.setTag(R.id.qs_icon_tag, state.icon);
if (d instanceof Animatable) {
- if (!iv.isShown()) {
- ((Animatable) d).stop(); // skip directly to end state
+ Animatable a = (Animatable) d;
+ if (state.icon instanceof AnimationIcon && !iv.isShown()) {
+ a.stop(); // skip directly to end state
}
}
}
@@ -326,6 +329,26 @@ public class QSTileView extends ViewGroup {
mHandler.obtainMessage(H.STATE_CHANGED, state).sendToTarget();
}
+ /**
+ * Update the accessibility order for this view.
+ *
+ * @param previousView the view which should be before this one
+ * @return the last view in this view which is accessible
+ */
+ public View updateAccessibilityOrder(View previousView) {
+ View firstView;
+ View lastView;
+ if (mDual) {
+ lastView = mDualLabel;
+ firstView = mTopBackgroundView;
+ } else {
+ firstView = this;
+ lastView = this;
+ }
+ firstView.setAccessibilityTraversalAfter(previousView.getId());
+ return lastView;
+ }
+
private class H extends Handler {
private static final int STATE_CHANGED = 1;
public H() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/UsageTracker.java b/packages/SystemUI/src/com/android/systemui/qs/UsageTracker.java
index e60aa53..f36019b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/UsageTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/UsageTracker.java
@@ -23,6 +23,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
+import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.Listenable;
@@ -32,14 +33,15 @@ public class UsageTracker implements Listenable {
private final Context mContext;
private final long mTimeToShowTile;
- private final String mPrefKey;
+ @Prefs.Key private final String mPrefKey;
private final String mResetAction;
private boolean mRegistered;
- public UsageTracker(Context context, Class<?> tile, int timeoutResource) {
+ public UsageTracker(Context context, @Prefs.Key String prefKey, Class<?> tile,
+ int timeoutResource) {
mContext = context;
- mPrefKey = tile.getSimpleName() + "LastUsed";
+ mPrefKey = prefKey;
mTimeToShowTile = MILLIS_PER_DAY * mContext.getResources().getInteger(timeoutResource);
mResetAction = "com.android.systemui.qs." + tile.getSimpleName() + ".usage_reset";
}
@@ -56,16 +58,16 @@ public class UsageTracker implements Listenable {
}
public boolean isRecentlyUsed() {
- long lastUsed = getSharedPrefs().getLong(mPrefKey, 0);
+ long lastUsed = Prefs.getLong(mContext, mPrefKey, 0L /* defaultValue */);
return (System.currentTimeMillis() - lastUsed) < mTimeToShowTile;
}
public void trackUsage() {
- getSharedPrefs().edit().putLong(mPrefKey, System.currentTimeMillis()).commit();
+ Prefs.putLong(mContext, mPrefKey, System.currentTimeMillis());
}
public void reset() {
- getSharedPrefs().edit().remove(mPrefKey).commit();
+ Prefs.remove(mContext, mPrefKey);
}
public void showResetConfirmation(String title, final Runnable onConfirmed) {
@@ -87,10 +89,6 @@ public class UsageTracker implements Listenable {
d.show();
}
- private SharedPreferences getSharedPrefs() {
- return mContext.getSharedPreferences(mContext.getPackageName(), 0);
- }
-
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
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 2dd02a5..49f8d1c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
@@ -23,6 +23,7 @@ import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.provider.Settings.Global;
+import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
import com.android.systemui.qs.GlobalSetting;
import com.android.systemui.qs.QSTile;
@@ -55,6 +56,7 @@ public class AirplaneModeTile extends QSTile<QSTile.BooleanState> {
@Override
public void handleClick() {
+ MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
setEnabled(!mState.value);
mEnable.setAllowAnimation(true);
mDisable.setAllowAnimation(true);
@@ -72,7 +74,7 @@ public class AirplaneModeTile extends QSTile<QSTile.BooleanState> {
final boolean airplaneMode = value != 0;
state.value = airplaneMode;
state.visible = true;
- state.label = mContext.getString(R.string.quick_settings_airplane_mode_label);
+ state.label = mContext.getString(R.string.airplane_mode);
if (airplaneMode) {
state.icon = mEnable;
state.contentDescription = mContext.getString(
@@ -85,6 +87,11 @@ public class AirplaneModeTile extends QSTile<QSTile.BooleanState> {
}
@Override
+ public int getMetricsCategory() {
+ return MetricsLogger.QS_AIRPLANEMODE;
+ }
+
+ @Override
protected String composeChangeAnnouncement() {
if (mState.value) {
return mContext.getString(R.string.accessibility_quick_settings_airplane_changed_on);
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 c15566f..abce31f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -16,6 +16,8 @@
package com.android.systemui.qs.tiles;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.Intent;
import android.provider.Settings;
@@ -23,13 +25,15 @@ import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
+import com.android.internal.logging.MetricsLogger;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.systemui.R;
import com.android.systemui.qs.QSDetailItems;
import com.android.systemui.qs.QSDetailItems.Item;
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.policy.BluetoothController;
-import com.android.systemui.statusbar.policy.BluetoothController.PairedDevice;
+import java.util.Collection;
import java.util.Set;
/** Quick settings tile: Bluetooth **/
@@ -72,6 +76,7 @@ public class BluetoothTile extends QSTile<QSTile.BooleanState> {
@Override
protected void handleClick() {
final boolean isEnabled = (Boolean)mState.value;
+ MetricsLogger.action(mContext, getMetricsCategory(), !isEnabled);
mController.setBluetoothEnabled(!isEnabled);
}
@@ -129,6 +134,11 @@ public class BluetoothTile extends QSTile<QSTile.BooleanState> {
}
@Override
+ public int getMetricsCategory() {
+ return MetricsLogger.QS_BLUETOOTH;
+ }
+
+ @Override
protected String composeChangeAnnouncement() {
if (mState.value) {
return mContext.getString(R.string.accessibility_quick_settings_bluetooth_changed_on);
@@ -139,11 +149,12 @@ public class BluetoothTile extends QSTile<QSTile.BooleanState> {
private final BluetoothController.Callback mCallback = new BluetoothController.Callback() {
@Override
- public void onBluetoothStateChange(boolean enabled, boolean connecting) {
+ public void onBluetoothStateChange(boolean enabled) {
refreshState();
}
+
@Override
- public void onBluetoothPairedDevicesChanged() {
+ public void onBluetoothDevicesChanged() {
mUiHandler.post(new Runnable() {
@Override
public void run() {
@@ -174,11 +185,17 @@ public class BluetoothTile extends QSTile<QSTile.BooleanState> {
@Override
public void setToggleState(boolean state) {
+ MetricsLogger.action(mContext, MetricsLogger.QS_BLUETOOTH_TOGGLE, state);
mController.setBluetoothEnabled(state);
showDetail(false);
}
@Override
+ public int getMetricsCategory() {
+ return MetricsLogger.QS_BLUETOOTH_DETAILS;
+ }
+
+ @Override
public View createDetailView(Context context, View convertView, ViewGroup parent) {
mItems = QSDetailItems.convertOrInflate(context, convertView, parent);
mItems.setTagSuffix("Bluetooth");
@@ -199,19 +216,21 @@ public class BluetoothTile extends QSTile<QSTile.BooleanState> {
private void updateItems() {
if (mItems == null) return;
Item[] items = null;
- final Set<PairedDevice> devices = mController.getPairedDevices();
+ final Collection<CachedBluetoothDevice> devices = mController.getDevices();
if (devices != null) {
- items = new Item[devices.size()];
+ items = new Item[getBondedCount(devices)];
int i = 0;
- for (PairedDevice device : devices) {
+ for (CachedBluetoothDevice device : devices) {
+ if (device.getBondState() == BluetoothDevice.BOND_NONE) continue;
final Item item = new Item();
item.icon = R.drawable.ic_qs_bluetooth_on;
- item.line1 = device.name;
- if (device.state == PairedDevice.STATE_CONNECTED) {
+ item.line1 = device.getName();
+ int state = device.getMaxConnectionState();
+ if (state == BluetoothProfile.STATE_CONNECTED) {
item.icon = R.drawable.ic_qs_bluetooth_connected;
item.line2 = mContext.getString(R.string.quick_settings_connected);
item.canDisconnect = true;
- } else if (device.state == PairedDevice.STATE_CONNECTING) {
+ } else if (state == BluetoothProfile.STATE_CONNECTING) {
item.icon = R.drawable.ic_qs_bluetooth_connecting;
item.line2 = mContext.getString(R.string.quick_settings_connecting);
}
@@ -222,11 +241,22 @@ public class BluetoothTile extends QSTile<QSTile.BooleanState> {
mItems.setItems(items);
}
+ private int getBondedCount(Collection<CachedBluetoothDevice> devices) {
+ int ct = 0;
+ for (CachedBluetoothDevice device : devices) {
+ if (device.getBondState() != BluetoothDevice.BOND_NONE) {
+ ct++;
+ }
+ }
+ return ct;
+ }
+
@Override
public void onDetailItemClick(Item item) {
if (item == null || item.tag == null) return;
- final PairedDevice device = (PairedDevice) item.tag;
- if (device != null && device.state == PairedDevice.STATE_DISCONNECTED) {
+ final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag;
+ if (device != null && device.getMaxConnectionState()
+ == BluetoothProfile.STATE_DISCONNECTED) {
mController.connect(device);
}
}
@@ -234,7 +264,7 @@ public class BluetoothTile extends QSTile<QSTile.BooleanState> {
@Override
public void onDetailItemDisconnect(Item item) {
if (item == null || item.tag == null) return;
- final PairedDevice device = (PairedDevice) item.tag;
+ final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag;
if (device != null) {
mController.disconnect(device);
}
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 5bf6fb5..937615a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -24,6 +24,7 @@ import android.view.View;
import android.view.View.OnAttachStateChangeListener;
import android.view.ViewGroup;
+import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
import com.android.systemui.qs.QSDetailItems;
import com.android.systemui.qs.QSDetailItems.Item;
@@ -85,12 +86,13 @@ public class CastTile extends QSTile<QSTile.BooleanState> {
@Override
protected void handleClick() {
+ MetricsLogger.action(mContext, getMetricsCategory());
showDetail(true);
}
@Override
protected void handleUpdateState(BooleanState state, Object arg) {
- state.visible = !(mKeyguard.isSecure() && mKeyguard.isShowing());
+ state.visible = !(mKeyguard.isSecure() && mKeyguard.isShowing() && !mKeyguard.isTrusted());
state.label = mContext.getString(R.string.quick_settings_cast_title);
state.value = false;
state.autoMirrorDrawable = false;
@@ -113,6 +115,11 @@ public class CastTile extends QSTile<QSTile.BooleanState> {
}
@Override
+ public int getMetricsCategory() {
+ return MetricsLogger.QS_CAST;
+ }
+
+ @Override
protected String composeChangeAnnouncement() {
if (!mState.value) {
// We only announce when it's turned off to avoid vocal overflow.
@@ -164,6 +171,11 @@ public class CastTile extends QSTile<QSTile.BooleanState> {
}
@Override
+ public int getMetricsCategory() {
+ return MetricsLogger.QS_CAST_DETAILS;
+ }
+
+ @Override
public View createDetailView(Context context, View convertView, ViewGroup parent) {
mItems = QSDetailItems.convertOrInflate(context, convertView, parent);
mItems.setTagSuffix("Cast");
@@ -234,6 +246,7 @@ public class CastTile extends QSTile<QSTile.BooleanState> {
@Override
public void onDetailItemClick(Item item) {
if (item == null || item.tag == null) return;
+ MetricsLogger.action(mContext, MetricsLogger.QS_CAST_SELECT);
final CastDevice device = (CastDevice) item.tag;
mController.startCasting(device);
}
@@ -241,6 +254,7 @@ public class CastTile extends QSTile<QSTile.BooleanState> {
@Override
public void onDetailItemDisconnect(Item item) {
if (item == null || item.tag == null) return;
+ MetricsLogger.action(mContext, MetricsLogger.QS_CAST_DISCONNECT);
final CastDevice device = (CastDevice) item.tag;
mController.stopCasting(device);
}
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 30f92b9..07406b9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -24,14 +24,17 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
import com.android.systemui.qs.QSTileView;
import com.android.systemui.qs.SignalTileView;
import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.statusbar.policy.NetworkController.IconState;
import com.android.systemui.statusbar.policy.NetworkController.MobileDataController;
import com.android.systemui.statusbar.policy.NetworkController.MobileDataController.DataUsageInfo;
-import com.android.systemui.statusbar.policy.NetworkController.NetworkSignalChangedCallback;
+import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+import com.android.systemui.statusbar.policy.SignalCallbackAdapter;
/** Quick settings tile: Cellular **/
public class CellularTile extends QSTile<QSTile.SignalState> {
@@ -62,9 +65,9 @@ public class CellularTile extends QSTile<QSTile.SignalState> {
@Override
public void setListening(boolean listening) {
if (listening) {
- mController.addNetworkSignalChangedCallback(mCallback);
+ mController.addSignalCallback(mSignalCallback);
} else {
- mController.removeNetworkSignalChangedCallback(mCallback);
+ mController.removeSignalCallback(mSignalCallback);
}
}
@@ -75,10 +78,11 @@ public class CellularTile extends QSTile<QSTile.SignalState> {
@Override
protected void handleClick() {
+ MetricsLogger.action(mContext, getMetricsCategory());
if (mDataController.isMobileDataSupported()) {
showDetail(true);
} else {
- mHost.startSettingsActivity(CELLULAR_SETTINGS);
+ mHost.startActivityDismissingKeyguard(CELLULAR_SETTINGS);
}
}
@@ -118,6 +122,11 @@ public class CellularTile extends QSTile<QSTile.SignalState> {
state.label);
}
+ @Override
+ public int getMetricsCategory() {
+ return MetricsLogger.QS_CELLULAR;
+ }
+
// Remove the period from the network name
public static String removeTrailingPeriod(String string) {
if (string == null) return null;
@@ -131,7 +140,6 @@ public class CellularTile extends QSTile<QSTile.SignalState> {
private static final class CallbackInfo {
boolean enabled;
boolean wifiEnabled;
- boolean wifiConnected;
boolean airplaneModeEnabled;
int mobileSignalIconId;
String signalContentDescription;
@@ -144,40 +152,38 @@ public class CellularTile extends QSTile<QSTile.SignalState> {
boolean isDataTypeIconWide;
}
- private final NetworkSignalChangedCallback mCallback = new NetworkSignalChangedCallback() {
+ private final SignalCallback mSignalCallback = new SignalCallbackAdapter() {
private final CallbackInfo mInfo = new CallbackInfo();
-
@Override
- public void onWifiSignalChanged(boolean enabled, boolean connected, int wifiSignalIconId,
- boolean activityIn, boolean activityOut,
- String wifiSignalContentDescriptionId, String description) {
+ public void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon,
+ boolean activityIn, boolean activityOut, String description) {
mInfo.wifiEnabled = enabled;
- mInfo.wifiConnected = connected;
refreshState(mInfo);
}
@Override
- public void onMobileDataSignalChanged(boolean enabled,
- int mobileSignalIconId,
- String mobileSignalContentDescriptionId, int dataTypeIconId,
- boolean activityIn, boolean activityOut,
- String dataTypeContentDescriptionId, String description,
- boolean isDataTypeIconWide) {
- mInfo.enabled = enabled;
- mInfo.mobileSignalIconId = mobileSignalIconId;
- mInfo.signalContentDescription = mobileSignalContentDescriptionId;
- mInfo.dataTypeIconId = dataTypeIconId;
- mInfo.dataContentDescription = dataTypeContentDescriptionId;
+ public void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType,
+ int qsType, boolean activityIn, boolean activityOut, String typeContentDescription,
+ String description, boolean isWide, int subId) {
+ if (qsIcon == null) {
+ // Not data sim, don't display.
+ return;
+ }
+ mInfo.enabled = qsIcon.visible;
+ mInfo.mobileSignalIconId = qsIcon.icon;
+ mInfo.signalContentDescription = qsIcon.contentDescription;
+ mInfo.dataTypeIconId = qsType;
+ mInfo.dataContentDescription = typeContentDescription;
mInfo.activityIn = activityIn;
mInfo.activityOut = activityOut;
mInfo.enabledDesc = description;
- mInfo.isDataTypeIconWide = isDataTypeIconWide;
+ mInfo.isDataTypeIconWide = qsType != 0 && isWide;
refreshState(mInfo);
}
@Override
- public void onNoSimVisibleChanged(boolean visible) {
- mInfo.noSim = visible;
+ public void setNoSims(boolean show) {
+ mInfo.noSim = show;
if (mInfo.noSim) {
// Make sure signal gets cleared out when no sims.
mInfo.mobileSignalIconId = 0;
@@ -192,12 +198,13 @@ public class CellularTile extends QSTile<QSTile.SignalState> {
}
@Override
- public void onAirplaneModeChanged(boolean enabled) {
- mInfo.airplaneModeEnabled = enabled;
+ public void setIsAirplaneMode(IconState icon) {
+ mInfo.airplaneModeEnabled = icon.visible;
refreshState(mInfo);
}
- public void onMobileDataEnabled(boolean enabled) {
+ @Override
+ public void setMobileDataEnabled(boolean enabled) {
mDetailAdapter.setMobileDataEnabled(enabled);
}
};
@@ -223,10 +230,16 @@ public class CellularTile extends QSTile<QSTile.SignalState> {
@Override
public void setToggleState(boolean state) {
+ MetricsLogger.action(mContext, MetricsLogger.QS_CELLULAR_TOGGLE, state);
mDataController.setMobileDataEnabled(state);
}
@Override
+ public int getMetricsCategory() {
+ return MetricsLogger.QS_DATAUSAGEDETAIL;
+ }
+
+ @Override
public View createDetailView(Context context, View convertView, ViewGroup parent) {
final DataUsageDetailView v = (DataUsageDetailView) (convertView != null
? convertView
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
index 5963a45..c6fc6ff 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
@@ -18,6 +18,8 @@ package com.android.systemui.qs.tiles;
import android.provider.Settings.Secure;
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
import com.android.systemui.qs.SecureSetting;
@@ -50,7 +52,8 @@ public class ColorInversionTile extends QSTile<QSTile.BooleanState> {
}
}
};
- mUsageTracker = new UsageTracker(host.getContext(), ColorInversionTile.class,
+ mUsageTracker = new UsageTracker(host.getContext(),
+ Prefs.Key.COLOR_INVERSION_TILE_LAST_USED, ColorInversionTile.class,
R.integer.days_to_show_color_inversion_tile);
if (mSetting.getValue() != 0 && !mUsageTracker.isRecentlyUsed()) {
mUsageTracker.trackUsage();
@@ -84,6 +87,7 @@ public class ColorInversionTile extends QSTile<QSTile.BooleanState> {
@Override
protected void handleClick() {
+ MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
mSetting.setValue(mState.value ? 0 : 1);
mEnable.setAllowAnimation(true);
mDisable.setAllowAnimation(true);
@@ -113,6 +117,11 @@ public class ColorInversionTile extends QSTile<QSTile.BooleanState> {
}
@Override
+ public int getMetricsCategory() {
+ return MetricsLogger.QS_COLORINVERSION;
+ }
+
+ @Override
protected String composeChangeAnnouncement() {
if (mState.value) {
return mContext.getString(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataUsageDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataUsageDetailView.java
index eb816b7..d0ae383 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataUsageDetailView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataUsageDetailView.java
@@ -96,7 +96,7 @@ public class DataUsageDetailView extends LinearLayout {
title.setText(titleId);
final TextView usage = (TextView) findViewById(R.id.usage_text);
usage.setText(formatBytes(bytes));
- usage.setTextColor(res.getColor(usageColor));
+ usage.setTextColor(mContext.getColor(usageColor));
final DataUsageGraph graph = (DataUsageGraph) findViewById(R.id.usage_graph);
graph.setLevels(info.limitLevel, info.warningLevel, info.usageLevel);
final TextView carrier = (TextView) findViewById(R.id.usage_carrier_text);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
new file mode 100644
index 0000000..359ed5f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -0,0 +1,289 @@
+/*
+ * 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.qs.tiles;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.provider.Settings;
+import android.provider.Settings.Global;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnAttachStateChangeListener;
+import android.view.ViewGroup;
+import android.widget.Toast;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.Prefs;
+import com.android.systemui.R;
+import com.android.systemui.qs.QSTile;
+import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.volume.ZenModePanel;
+
+/** Quick settings tile: Do not disturb **/
+public class DndTile extends QSTile<QSTile.BooleanState> {
+
+ private static final Intent ZEN_SETTINGS =
+ new Intent(Settings.ACTION_ZEN_MODE_SETTINGS);
+
+ private static final Intent ZEN_PRIORITY_SETTINGS =
+ new Intent(Settings.ACTION_ZEN_MODE_PRIORITY_SETTINGS);
+
+ 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 AnimationIcon mDisable =
+ new AnimationIcon(R.drawable.ic_dnd_disable_animation);
+ private final AnimationIcon mDisableTotalSilence =
+ new AnimationIcon(R.drawable.ic_dnd_total_silence_disable_animation);
+
+ private final ZenModeController mController;
+ private final DndDetailAdapter mDetailAdapter;
+
+ private boolean mListening;
+ private boolean mShowingDetail;
+
+ public DndTile(Host host) {
+ super(host);
+ mController = host.getZenModeController();
+ mDetailAdapter = new DndDetailAdapter();
+ mContext.registerReceiver(mReceiver, new IntentFilter(ACTION_SET_VISIBLE));
+ }
+
+ public static void setVisible(Context context, boolean visible) {
+ Prefs.putBoolean(context, Prefs.Key.DND_TILE_VISIBLE, visible);
+ }
+
+ public static boolean isVisible(Context context) {
+ return Prefs.getBoolean(context, Prefs.Key.DND_TILE_VISIBLE, false /* defaultValue */);
+ }
+
+ public static void setCombinedIcon(Context context, boolean combined) {
+ Prefs.putBoolean(context, Prefs.Key.DND_TILE_COMBINED_ICON, combined);
+ }
+
+ public static boolean isCombinedIcon(Context context) {
+ return Prefs.getBoolean(context, Prefs.Key.DND_TILE_COMBINED_ICON,
+ false /* defaultValue */);
+ }
+
+ @Override
+ public DetailAdapter getDetailAdapter() {
+ return mDetailAdapter;
+ }
+
+ @Override
+ protected BooleanState newTileState() {
+ return new BooleanState();
+ }
+
+ @Override
+ public void handleClick() {
+ if (mController.isVolumeRestricted()) {
+ // Collapse the panels, so the user can see the toast.
+ mHost.collapsePanels();
+ Toast.makeText(mContext, mContext.getString(
+ com.android.internal.R.string.error_message_change_not_allowed),
+ Toast.LENGTH_LONG).show();
+ return;
+ }
+ mDisable.setAllowAnimation(true);
+ mDisableTotalSilence.setAllowAnimation(true);
+ MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
+ if (mState.value) {
+ mController.setZen(Global.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
+ protected void handleUpdateState(BooleanState state, Object arg) {
+ final int zen = arg instanceof Integer ? (Integer) arg : mController.getZen();
+ final boolean newValue = zen != Global.ZEN_MODE_OFF;
+ final boolean valueChanged = state.value != newValue;
+ state.value = newValue;
+ state.visible = isVisible(mContext);
+ 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 Global.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 = TOTAL_SILENCE.equals(state.icon) ? mDisableTotalSilence : mDisable;
+ state.label = mContext.getString(R.string.quick_settings_dnd_label);
+ state.contentDescription = mContext.getString(
+ R.string.accessibility_quick_settings_dnd_off);
+ break;
+ }
+ if (mShowingDetail && !state.value) {
+ showDetail(false);
+ }
+ if (valueChanged) {
+ fireToggleStateChanged(state.value);
+ }
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsLogger.QS_DND;
+ }
+
+ @Override
+ protected String composeChangeAnnouncement() {
+ if (mState.value) {
+ return mContext.getString(R.string.accessibility_quick_settings_dnd_changed_on);
+ } else {
+ return mContext.getString(R.string.accessibility_quick_settings_dnd_changed_off);
+ }
+ }
+
+ @Override
+ public void setListening(boolean listening) {
+ if (mListening == listening) return;
+ mListening = listening;
+ if (mListening) {
+ mController.addCallback(mZenCallback);
+ Prefs.registerListener(mContext, mPrefListener);
+ } else {
+ mController.removeCallback(mZenCallback);
+ Prefs.unregisterListener(mContext, mPrefListener);
+ }
+ }
+
+ private final OnSharedPreferenceChangeListener mPrefListener
+ = new OnSharedPreferenceChangeListener() {
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
+ @Prefs.Key String key) {
+ if (Prefs.Key.DND_TILE_COMBINED_ICON.equals(key) ||
+ Prefs.Key.DND_TILE_VISIBLE.equals(key)) {
+ refreshState();
+ }
+ }
+ };
+
+ private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() {
+ public void onZenChanged(int zen) {
+ refreshState(zen);
+ }
+ };
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final boolean visible = intent.getBooleanExtra(EXTRA_VISIBLE, false);
+ setVisible(mContext, visible);
+ refreshState();
+ }
+ };
+
+ private final class DndDetailAdapter implements DetailAdapter, OnAttachStateChangeListener {
+
+ @Override
+ public int getTitle() {
+ return R.string.quick_settings_dnd_label;
+ }
+
+ @Override
+ public Boolean getToggleState() {
+ return mState.value;
+ }
+
+ @Override
+ public Intent getSettingsIntent() {
+ return ZEN_SETTINGS;
+ }
+
+ @Override
+ public void setToggleState(boolean state) {
+ MetricsLogger.action(mContext, MetricsLogger.QS_DND_TOGGLE, state);
+ if (!state) {
+ mController.setZen(Global.ZEN_MODE_OFF, null, TAG);
+ showDetail(false);
+ }
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsLogger.QS_DND_DETAILS;
+ }
+
+ @Override
+ public View createDetailView(Context context, View convertView, ViewGroup parent) {
+ final ZenModePanel zmp = convertView != null ? (ZenModePanel) convertView
+ : (ZenModePanel) LayoutInflater.from(context).inflate(
+ R.layout.zen_mode_panel, parent, false);
+ if (convertView == null) {
+ zmp.init(mController);
+ zmp.addOnAttachStateChangeListener(this);
+ zmp.setCallback(mZenModePanelCallback);
+ }
+ return zmp;
+ }
+
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ mShowingDetail = true;
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ mShowingDetail = false;
+ }
+ }
+
+ private final ZenModePanel.Callback mZenModePanelCallback = new ZenModePanel.Callback() {
+ @Override
+ public void onPrioritySettings() {
+ mHost.startActivityDismissingKeyguard(ZEN_PRIORITY_SETTINGS);
+ }
+
+ @Override
+ public void onInteraction() {
+ // noop
+ }
+
+ @Override
+ public void onExpanded(boolean expanded) {
+ // noop
+ }
+ };
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
index 5c1a317..5d74604 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
@@ -17,8 +17,8 @@
package com.android.systemui.qs.tiles;
import android.app.ActivityManager;
-import android.os.SystemClock;
+import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.policy.FlashlightController;
@@ -27,16 +27,11 @@ import com.android.systemui.statusbar.policy.FlashlightController;
public class FlashlightTile extends QSTile<QSTile.BooleanState> implements
FlashlightController.FlashlightListener {
- /** Grace period for which we consider the flashlight
- * still available because it was recently on. */
- private static final long RECENTLY_ON_DURATION_MILLIS = 500;
-
private final AnimationIcon mEnable
= new AnimationIcon(R.drawable.ic_signal_flashlight_enable_animation);
private final AnimationIcon mDisable
= new AnimationIcon(R.drawable.ic_signal_flashlight_disable_animation);
private final FlashlightController mFlashlightController;
- private long mWasLastOn;
public FlashlightTile(Host host) {
super(host);
@@ -68,34 +63,23 @@ public class FlashlightTile extends QSTile<QSTile.BooleanState> implements
if (ActivityManager.isUserAMonkey()) {
return;
}
+ MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
boolean newState = !mState.value;
- mFlashlightController.setFlashlight(newState);
refreshState(newState ? UserBoolean.USER_TRUE : UserBoolean.USER_FALSE);
+ mFlashlightController.setFlashlight(newState);
}
@Override
protected void handleUpdateState(BooleanState state, Object arg) {
- if (state.value) {
- mWasLastOn = SystemClock.uptimeMillis();
- }
-
+ state.visible = mFlashlightController.isAvailable();
+ state.label = mHost.getContext().getString(R.string.quick_settings_flashlight_label);
if (arg instanceof UserBoolean) {
- state.value = ((UserBoolean) arg).value;
- }
-
- if (!state.value && mWasLastOn != 0) {
- if (SystemClock.uptimeMillis() > mWasLastOn + RECENTLY_ON_DURATION_MILLIS) {
- mWasLastOn = 0;
- } else {
- mHandler.removeCallbacks(mRecentlyOnTimeout);
- mHandler.postAtTime(mRecentlyOnTimeout, mWasLastOn + RECENTLY_ON_DURATION_MILLIS);
+ boolean value = ((UserBoolean) arg).value;
+ if (value == state.value) {
+ return;
}
+ state.value = value;
}
-
- // Always show the tile when the flashlight is or was recently on. This is needed because
- // the camera is not available while it is being used for the flashlight.
- state.visible = mWasLastOn != 0 || mFlashlightController.isAvailable();
- state.label = mHost.getContext().getString(R.string.quick_settings_flashlight_label);
final AnimationIcon icon = state.value ? mEnable : mDisable;
icon.setAllowAnimation(arg instanceof UserBoolean && ((UserBoolean) arg).userInitiated);
state.icon = icon;
@@ -106,6 +90,11 @@ public class FlashlightTile extends QSTile<QSTile.BooleanState> implements
}
@Override
+ public int getMetricsCategory() {
+ return MetricsLogger.QS_FLASHLIGHT;
+ }
+
+ @Override
protected String composeChangeAnnouncement() {
if (mState.value) {
return mContext.getString(R.string.accessibility_quick_settings_flashlight_changed_on);
@@ -115,8 +104,8 @@ public class FlashlightTile extends QSTile<QSTile.BooleanState> implements
}
@Override
- public void onFlashlightOff() {
- refreshState(UserBoolean.BACKGROUND_FALSE);
+ public void onFlashlightChanged(boolean enabled) {
+ refreshState(enabled ? UserBoolean.BACKGROUND_TRUE : UserBoolean.BACKGROUND_FALSE);
}
@Override
@@ -128,11 +117,4 @@ public class FlashlightTile extends QSTile<QSTile.BooleanState> implements
public void onFlashlightAvailabilityChanged(boolean available) {
refreshState();
}
-
- private Runnable mRecentlyOnTimeout = new Runnable() {
- @Override
- public void run() {
- refreshState();
- }
- };
}
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 fcc517f..f28a24b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -20,6 +20,8 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.qs.UsageTracker;
import com.android.systemui.qs.QSTile;
@@ -68,6 +70,7 @@ public class HotspotTile extends QSTile<QSTile.BooleanState> {
@Override
protected void handleClick() {
final boolean isEnabled = (Boolean) mState.value;
+ MetricsLogger.action(mContext, getMetricsCategory(), !isEnabled);
mController.setHotspotEnabled(!isEnabled);
mEnable.setAllowAnimation(true);
mDisable.setAllowAnimation(true);
@@ -96,6 +99,11 @@ public class HotspotTile extends QSTile<QSTile.BooleanState> {
}
@Override
+ public int getMetricsCategory() {
+ return MetricsLogger.QS_HOTSPOT;
+ }
+
+ @Override
protected String composeChangeAnnouncement() {
if (mState.value) {
return mContext.getString(R.string.accessibility_quick_settings_hotspot_changed_on);
@@ -105,7 +113,8 @@ public class HotspotTile extends QSTile<QSTile.BooleanState> {
}
private static UsageTracker newUsageTracker(Context context) {
- return new UsageTracker(context, HotspotTile.class, R.integer.days_to_show_hotspot_tile);
+ return new UsageTracker(context, Prefs.Key.HOTSPOT_TILE_LAST_USED, HotspotTile.class,
+ R.integer.days_to_show_hotspot_tile);
}
private final class Callback implements HotspotController.Callback {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
index 2736530..f7f7acb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
@@ -29,6 +29,7 @@ import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
+import com.android.internal.logging.MetricsLogger;
import com.android.systemui.qs.QSTile;
import java.util.Arrays;
@@ -42,6 +43,7 @@ public class IntentTile extends QSTile<QSTile.State> {
private PendingIntent mOnLongClick;
private String mOnLongClickUri;
private int mCurrentUserId;
+ private String mIntentPackage;
private IntentTile(Host host, String action) {
super(host);
@@ -82,6 +84,7 @@ public class IntentTile extends QSTile<QSTile.State> {
@Override
protected void handleClick() {
+ MetricsLogger.action(mContext, getMetricsCategory(), mIntentPackage);
sendIntent("click", mOnClick, mOnClickUri);
}
@@ -93,7 +96,11 @@ public class IntentTile extends QSTile<QSTile.State> {
private void sendIntent(String type, PendingIntent pi, String uri) {
try {
if (pi != null) {
- pi.send();
+ if (pi.isActivity()) {
+ getHost().startActivityDismissingKeyguard(pi.getIntent());
+ } else {
+ pi.send();
+ }
} else if (uri != null) {
final Intent intent = Intent.parseUri(uri, Intent.URI_INTENT_SCHEME);
mContext.sendBroadcastAsUser(intent, new UserHandle(mCurrentUserId));
@@ -133,6 +140,13 @@ public class IntentTile extends QSTile<QSTile.State> {
mOnClickUri = intent.getStringExtra("onClickUri");
mOnLongClick = intent.getParcelableExtra("onLongClick");
mOnLongClickUri = intent.getStringExtra("onLongClickUri");
+ mIntentPackage = intent.getStringExtra("package");
+ mIntentPackage = mIntentPackage == null ? "" : mIntentPackage;
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsLogger.QS_INTENT;
}
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
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 11ec722..e6fade4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
@@ -16,6 +16,7 @@
package com.android.systemui.qs.tiles;
+import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
@@ -59,6 +60,7 @@ public class LocationTile extends QSTile<QSTile.BooleanState> {
@Override
protected void handleClick() {
final boolean wasEnabled = (Boolean) mState.value;
+ MetricsLogger.action(mContext, getMetricsCategory(), !wasEnabled);
mController.setLocationEnabled(!wasEnabled);
mEnable.setAllowAnimation(true);
mDisable.setAllowAnimation(true);
@@ -87,6 +89,11 @@ public class LocationTile extends QSTile<QSTile.BooleanState> {
}
@Override
+ public int getMetricsCategory() {
+ return MetricsLogger.QS_LOCATION;
+ }
+
+ @Override
protected String composeChangeAnnouncement() {
if (mState.value) {
return mContext.getString(R.string.accessibility_quick_settings_location_changed_on);
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 f46b9a6..6d2c8c0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
@@ -18,6 +18,7 @@ package com.android.systemui.qs.tiles;
import android.content.res.Configuration;
+import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.policy.RotationLockController;
@@ -59,6 +60,7 @@ public class RotationLockTile extends QSTile<QSTile.BooleanState> {
@Override
protected void handleClick() {
if (mController == null) return;
+ MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
final boolean newState = !mState.value;
mController.setRotationLocked(newState);
refreshState(newState ? UserBoolean.USER_TRUE : UserBoolean.USER_FALSE);
@@ -71,6 +73,10 @@ public class RotationLockTile extends QSTile<QSTile.BooleanState> {
: mController.isRotationLocked();
final boolean userInitiated = arg != null ? ((UserBoolean) arg).userInitiated : false;
state.visible = mController.isRotationLockAffordanceVisible();
+ if (state.value == rotationLocked && state.contentDescription != null) {
+ // No change and initialized, no need to update all the values.
+ return;
+ }
state.value = rotationLocked;
final boolean portrait = mContext.getResources().getConfiguration().orientation
!= Configuration.ORIENTATION_LANDSCAPE;
@@ -92,6 +98,11 @@ public class RotationLockTile extends QSTile<QSTile.BooleanState> {
R.string.accessibility_rotation_lock_off);
}
+ @Override
+ public int getMetricsCategory() {
+ return MetricsLogger.QS_ROTATIONLOCK;
+ }
+
/**
* Get the correct accessibility string based on the state
*
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
index c55cbcc..21c5c96 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
@@ -28,7 +28,6 @@ import android.graphics.Bitmap;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
-import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
index 6bad652..d4f54b6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
@@ -16,6 +16,7 @@
package com.android.systemui.qs.tiles;
+import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
import com.android.systemui.qs.PseudoGridView;
import com.android.systemui.statusbar.policy.UserSwitcherController;
@@ -84,6 +85,7 @@ public class UserDetailView extends PseudoGridView {
public void onClick(View view) {
UserSwitcherController.UserRecord tag =
(UserSwitcherController.UserRecord) view.getTag();
+ MetricsLogger.action(mContext, MetricsLogger.QS_SWITCH_USER);
switchTo(tag);
}
}
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 e09024b..c33ef7c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -24,17 +24,21 @@ import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
+import com.android.internal.logging.MetricsLogger;
+import com.android.settingslib.wifi.AccessPoint;
import com.android.systemui.R;
import com.android.systemui.qs.QSDetailItems;
import com.android.systemui.qs.QSDetailItems.Item;
import com.android.systemui.qs.QSTile;
import com.android.systemui.qs.QSTileView;
import com.android.systemui.qs.SignalTileView;
-import com.android.systemui.statusbar.phone.QSTileHost;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.NetworkController.AccessPointController;
-import com.android.systemui.statusbar.policy.NetworkController.AccessPointController.AccessPoint;
-import com.android.systemui.statusbar.policy.NetworkController.NetworkSignalChangedCallback;
+import com.android.systemui.statusbar.policy.NetworkController.IconState;
+import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+import com.android.systemui.statusbar.policy.SignalCallbackAdapter;
+
+import java.util.List;
/** Quick settings tile: Wifi **/
public class WifiTile extends QSTile<QSTile.SignalState> {
@@ -65,10 +69,17 @@ public class WifiTile extends QSTile<QSTile.SignalState> {
@Override
public void setListening(boolean listening) {
if (listening) {
- mController.addNetworkSignalChangedCallback(mCallback);
+ mController.addSignalCallback(mSignalCallback);
+ } else {
+ mController.removeSignalCallback(mSignalCallback);
+ }
+ }
+
+ @Override
+ public void setDetailListening(boolean listening) {
+ if (listening) {
mWifiController.addAccessPointCallback(mDetailAdapter);
} else {
- mController.removeNetworkSignalChangedCallback(mCallback);
mWifiController.removeAccessPointCallback(mDetailAdapter);
}
}
@@ -86,13 +97,14 @@ public class WifiTile extends QSTile<QSTile.SignalState> {
@Override
protected void handleClick() {
mState.copyTo(mStateBeforeClick);
+ MetricsLogger.action(mContext, getMetricsCategory(), !mState.enabled);
mController.setWifiEnabled(!mState.enabled);
}
@Override
protected void handleSecondaryClick() {
if (!mWifiController.canConfigWifi()) {
- mHost.startSettingsActivity(new Intent(Settings.ACTION_WIFI_SETTINGS));
+ mHost.startActivityDismissingKeyguard(new Intent(Settings.ACTION_WIFI_SETTINGS));
return;
}
if (!mState.enabled) {
@@ -151,6 +163,11 @@ public class WifiTile extends QSTile<QSTile.SignalState> {
}
@Override
+ public int getMetricsCategory() {
+ return MetricsLogger.QS_WIFI;
+ }
+
+ @Override
protected boolean shouldAnnouncementBeDelayed() {
return mStateBeforeClick.enabled == mState.enabled;
}
@@ -196,46 +213,21 @@ public class WifiTile extends QSTile<QSTile.SignalState> {
}
}
- private final NetworkSignalChangedCallback mCallback = new NetworkSignalChangedCallback() {
+ private final SignalCallback mSignalCallback = new SignalCallbackAdapter() {
@Override
- public void onWifiSignalChanged(boolean enabled, boolean connected, int wifiSignalIconId,
- boolean activityIn, boolean activityOut,
- String wifiSignalContentDescriptionId, String description) {
+ public void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon,
+ boolean activityIn, boolean activityOut, String description) {
if (DEBUG) Log.d(TAG, "onWifiSignalChanged enabled=" + enabled);
final CallbackInfo info = new CallbackInfo();
info.enabled = enabled;
- info.connected = connected;
- info.wifiSignalIconId = wifiSignalIconId;
+ info.connected = qsIcon.visible;
+ info.wifiSignalIconId = qsIcon.icon;
info.enabledDesc = description;
info.activityIn = activityIn;
info.activityOut = activityOut;
- info.wifiSignalContentDescription = wifiSignalContentDescriptionId;
+ info.wifiSignalContentDescription = qsIcon.contentDescription;
refreshState(info);
}
-
- @Override
- public void onMobileDataSignalChanged(boolean enabled,
- int mobileSignalIconId,
- String mobileSignalContentDescriptionId, int dataTypeIconId,
- boolean activityIn, boolean activityOut,
- String dataTypeContentDescriptionId, String description,
- boolean isDataTypeIconWide) {
- // noop
- }
-
- public void onNoSimVisibleChanged(boolean noSims) {
- // noop
- }
-
- @Override
- public void onAirplaneModeChanged(boolean enabled) {
- // noop
- }
-
- @Override
- public void onMobileDataEnabled(boolean enabled) {
- // noop
- }
};
private final class WifiDetailAdapter implements DetailAdapter,
@@ -261,11 +253,17 @@ public class WifiTile extends QSTile<QSTile.SignalState> {
@Override
public void setToggleState(boolean state) {
if (DEBUG) Log.d(TAG, "setToggleState " + state);
+ MetricsLogger.action(mContext, MetricsLogger.QS_WIFI_TOGGLE, state);
mController.setWifiEnabled(state);
showDetail(false);
}
@Override
+ public int getMetricsCategory() {
+ return MetricsLogger.QS_WIFI_DETAILS;
+ }
+
+ @Override
public View createDetailView(Context context, View convertView, ViewGroup parent) {
if (DEBUG) Log.d(TAG, "createDetailView convertView=" + (convertView != null));
mAccessPoints = null;
@@ -282,24 +280,24 @@ public class WifiTile extends QSTile<QSTile.SignalState> {
}
@Override
- public void onAccessPointsChanged(final AccessPoint[] accessPoints) {
- mAccessPoints = accessPoints;
+ public void onAccessPointsChanged(final List<AccessPoint> accessPoints) {
+ mAccessPoints = accessPoints.toArray(new AccessPoint[accessPoints.size()]);
updateItems();
- if (accessPoints != null && accessPoints.length > 0) {
+ if (accessPoints != null && accessPoints.size() > 0) {
fireScanStateChanged(false);
}
}
@Override
public void onSettingsActivityTriggered(Intent settingsIntent) {
- mHost.startSettingsActivity(settingsIntent);
+ mHost.startActivityDismissingKeyguard(settingsIntent);
}
@Override
public void onDetailItemClick(Item item) {
if (item == null || item.tag == null) return;
final AccessPoint ap = (AccessPoint) item.tag;
- if (!ap.isConnected) {
+ if (!ap.isActive()) {
if (mWifiController.connect(ap)) {
mHost.collapsePanels();
}
@@ -326,16 +324,10 @@ public class WifiTile extends QSTile<QSTile.SignalState> {
final AccessPoint ap = mAccessPoints[i];
final Item item = new Item();
item.tag = ap;
- item.icon = ap.iconId;
- item.line1 = ap.ssid;
- if (ap.isConnected) {
- item.line2 = mContext.getString(ap.isConfigured ?
- R.string.quick_settings_connected :
- R.string.quick_settings_connected_via_wfa);
- } else if (ap.networkId >= 0) {
- // TODO: Set line 2 to wifi saved string here.
- }
- item.overlay = ap.hasSecurity
+ item.icon = mWifiController.getIcon(ap);
+ item.line1 = ap.getSsid();
+ item.line2 = ap.isActive() ? ap.getSummary() : null;
+ item.overlay = ap.getSecurity() != AccessPoint.SECURITY_NONE
? mContext.getDrawable(R.drawable.qs_ic_wifi_lock)
: null;
items[i] = item;
diff --git a/packages/SystemUI/src/com/android/systemui/recent/ColorDrawableWithDimensions.java b/packages/SystemUI/src/com/android/systemui/recent/ColorDrawableWithDimensions.java
deleted file mode 100644
index b4d3edd..0000000
--- a/packages/SystemUI/src/com/android/systemui/recent/ColorDrawableWithDimensions.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.recent;
-
-import android.graphics.drawable.ColorDrawable;
-
-public class ColorDrawableWithDimensions extends ColorDrawable {
- private int mWidth;
- private int mHeight;
-
- public ColorDrawableWithDimensions(int color, int width, int height) {
- super(color);
- mWidth = width;
- mHeight = height;
- }
-
- @Override
- public int getIntrinsicWidth() {
- return mWidth;
- }
-
- @Override
- public int getIntrinsicHeight() {
- return mHeight;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recent/FadedEdgeDrawHelper.java b/packages/SystemUI/src/com/android/systemui/recent/FadedEdgeDrawHelper.java
deleted file mode 100644
index 59f7a80..0000000
--- a/packages/SystemUI/src/com/android/systemui/recent/FadedEdgeDrawHelper.java
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.recent;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.LinearGradient;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.Shader;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.widget.LinearLayout;
-
-import com.android.systemui.R;
-
-public class FadedEdgeDrawHelper {
- public static final boolean OPTIMIZE_SW_RENDERED_RECENTS = true;
- public static final boolean USE_DARK_FADE_IN_HW_ACCELERATED_MODE = true;
- private View mScrollView;
-
- private int mFadingEdgeLength;
- private boolean mIsVertical;
- private boolean mSoftwareRendered = false;
- private Paint mBlackPaint;
- private Paint mFadePaint;
- private Matrix mFadeMatrix;
- private LinearGradient mFade;
-
- public static FadedEdgeDrawHelper create(Context context,
- AttributeSet attrs, View scrollView, boolean isVertical) {
- boolean isTablet = context.getResources().
- getBoolean(R.bool.config_recents_interface_for_tablets);
- if (!isTablet && (OPTIMIZE_SW_RENDERED_RECENTS || USE_DARK_FADE_IN_HW_ACCELERATED_MODE)) {
- return new FadedEdgeDrawHelper(context, attrs, scrollView, isVertical);
- } else {
- return null;
- }
- }
-
- public FadedEdgeDrawHelper(Context context,
- AttributeSet attrs, View scrollView, boolean isVertical) {
- mScrollView = scrollView;
- TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.View);
- mFadingEdgeLength = a.getDimensionPixelSize(android.R.styleable.View_fadingEdgeLength,
- ViewConfiguration.get(context).getScaledFadingEdgeLength());
- mIsVertical = isVertical;
- }
-
- public void onAttachedToWindowCallback(
- LinearLayout layout, boolean hardwareAccelerated) {
- mSoftwareRendered = !hardwareAccelerated;
- if ((mSoftwareRendered && OPTIMIZE_SW_RENDERED_RECENTS)
- || USE_DARK_FADE_IN_HW_ACCELERATED_MODE) {
- mScrollView.setVerticalFadingEdgeEnabled(false);
- mScrollView.setHorizontalFadingEdgeEnabled(false);
- }
- }
-
- public void addViewCallback(View newLinearLayoutChild) {
- if (mSoftwareRendered && OPTIMIZE_SW_RENDERED_RECENTS) {
- final RecentsPanelView.ViewHolder holder =
- (RecentsPanelView.ViewHolder) newLinearLayoutChild.getTag();
- holder.labelView.setDrawingCacheEnabled(true);
- holder.labelView.buildDrawingCache();
- }
- }
-
- public void drawCallback(Canvas canvas,
- int left, int right, int top, int bottom, int scrollX, int scrollY,
- float topFadingEdgeStrength, float bottomFadingEdgeStrength,
- float leftFadingEdgeStrength, float rightFadingEdgeStrength, int mPaddingTop) {
-
- if ((mSoftwareRendered && OPTIMIZE_SW_RENDERED_RECENTS)
- || USE_DARK_FADE_IN_HW_ACCELERATED_MODE) {
- if (mFadePaint == null) {
- mFadePaint = new Paint();
- mFadeMatrix = new Matrix();
- // use use a height of 1, and then wack the matrix each time we
- // actually use it.
- mFade = new LinearGradient(0, 0, 0, 1, 0xCC000000, 0, Shader.TileMode.CLAMP);
- // PULL OUT THIS CONSTANT
- mFadePaint.setShader(mFade);
- }
-
- // draw the fade effect
- boolean drawTop = false;
- boolean drawBottom = false;
- boolean drawLeft = false;
- boolean drawRight = false;
-
- float topFadeStrength = 0.0f;
- float bottomFadeStrength = 0.0f;
- float leftFadeStrength = 0.0f;
- float rightFadeStrength = 0.0f;
-
- final float fadeHeight = mFadingEdgeLength;
- int length = (int) fadeHeight;
-
- // clip the fade length if top and bottom fades overlap
- // overlapping fades produce odd-looking artifacts
- if (mIsVertical && (top + length > bottom - length)) {
- length = (bottom - top) / 2;
- }
-
- // also clip horizontal fades if necessary
- if (!mIsVertical && (left + length > right - length)) {
- length = (right - left) / 2;
- }
-
- if (mIsVertical) {
- topFadeStrength = Math.max(0.0f, Math.min(1.0f, topFadingEdgeStrength));
- drawTop = topFadeStrength * fadeHeight > 1.0f;
- bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, bottomFadingEdgeStrength));
- drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
- }
-
- if (!mIsVertical) {
- leftFadeStrength = Math.max(0.0f, Math.min(1.0f, leftFadingEdgeStrength));
- drawLeft = leftFadeStrength * fadeHeight > 1.0f;
- rightFadeStrength = Math.max(0.0f, Math.min(1.0f, rightFadingEdgeStrength));
- drawRight = rightFadeStrength * fadeHeight > 1.0f;
- }
-
- if (drawTop) {
- mFadeMatrix.setScale(1, fadeHeight * topFadeStrength);
- mFadeMatrix.postTranslate(left, top);
- mFade.setLocalMatrix(mFadeMatrix);
- mFadePaint.setShader(mFade);
- canvas.drawRect(left, top, right, top + length, mFadePaint);
-
- if (mBlackPaint == null) {
- // Draw under the status bar at the top
- mBlackPaint = new Paint();
- mBlackPaint.setColor(0xFF000000);
- }
- canvas.drawRect(left, top - mPaddingTop, right, top, mBlackPaint);
- }
-
- if (drawBottom) {
- mFadeMatrix.setScale(1, fadeHeight * bottomFadeStrength);
- mFadeMatrix.postRotate(180);
- mFadeMatrix.postTranslate(left, bottom);
- mFade.setLocalMatrix(mFadeMatrix);
- mFadePaint.setShader(mFade);
- canvas.drawRect(left, bottom - length, right, bottom, mFadePaint);
- }
-
- if (drawLeft) {
- mFadeMatrix.setScale(1, fadeHeight * leftFadeStrength);
- mFadeMatrix.postRotate(-90);
- mFadeMatrix.postTranslate(left, top);
- mFade.setLocalMatrix(mFadeMatrix);
- mFadePaint.setShader(mFade);
- canvas.drawRect(left, top, left + length, bottom, mFadePaint);
- }
-
- if (drawRight) {
- mFadeMatrix.setScale(1, fadeHeight * rightFadeStrength);
- mFadeMatrix.postRotate(90);
- mFadeMatrix.postTranslate(right, top);
- mFade.setLocalMatrix(mFadeMatrix);
- mFadePaint.setShader(mFade);
- canvas.drawRect(right - length, top, right, bottom, mFadePaint);
- }
- }
- }
-
- public int getVerticalFadingEdgeLength() {
- return mFadingEdgeLength;
- }
-
- public int getHorizontalFadingEdgeLength() {
- return mFadingEdgeLength;
- }
-
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recent/FirstFrameAnimatorHelper.java b/packages/SystemUI/src/com/android/systemui/recent/FirstFrameAnimatorHelper.java
deleted file mode 100644
index 84d13cf..0000000
--- a/packages/SystemUI/src/com/android/systemui/recent/FirstFrameAnimatorHelper.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.recent;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.util.Log;
-import android.view.View;
-import android.view.ViewPropertyAnimator;
-import android.view.ViewTreeObserver;
-
-/*
- * This is a helper class that listens to updates from the corresponding animation.
- * For the first two frames, it adjusts the current play time of the animation to
- * prevent jank at the beginning of the animation
- */
-public class FirstFrameAnimatorHelper extends AnimatorListenerAdapter
- implements ValueAnimator.AnimatorUpdateListener {
- private static final boolean DEBUG = false;
- private static final int MAX_DELAY = 1000;
- private static final int IDEAL_FRAME_DURATION = 16;
- private View mTarget;
- private long mStartFrame;
- private long mStartTime = -1;
- private boolean mHandlingOnAnimationUpdate;
- private boolean mAdjustedSecondFrameTime;
-
- private static ViewTreeObserver.OnDrawListener sGlobalDrawListener;
- private static long sGlobalFrameCounter;
-
- public FirstFrameAnimatorHelper(ValueAnimator animator, View target) {
- mTarget = target;
- animator.addUpdateListener(this);
- }
-
- public FirstFrameAnimatorHelper(ViewPropertyAnimator vpa, View target) {
- mTarget = target;
- vpa.setListener(this);
- }
-
- // only used for ViewPropertyAnimators
- public void onAnimationStart(Animator animation) {
- final ValueAnimator va = (ValueAnimator) animation;
- va.addUpdateListener(FirstFrameAnimatorHelper.this);
- onAnimationUpdate(va);
- }
-
- public static void initializeDrawListener(View view) {
- if (sGlobalDrawListener != null) {
- view.getViewTreeObserver().removeOnDrawListener(sGlobalDrawListener);
- }
- sGlobalDrawListener = new ViewTreeObserver.OnDrawListener() {
- private long mTime = System.currentTimeMillis();
- public void onDraw() {
- sGlobalFrameCounter++;
- if (DEBUG) {
- long newTime = System.currentTimeMillis();
- Log.d("FirstFrameAnimatorHelper", "TICK " + (newTime - mTime));
- mTime = newTime;
- }
- }
- };
- view.getViewTreeObserver().addOnDrawListener(sGlobalDrawListener);
- }
-
- public void onAnimationUpdate(final ValueAnimator animation) {
- final long currentTime = System.currentTimeMillis();
- if (mStartTime == -1) {
- mStartFrame = sGlobalFrameCounter;
- mStartTime = currentTime;
- }
-
- if (!mHandlingOnAnimationUpdate &&
- // If the current play time exceeds the duration, the animation
- // will get finished, even if we call setCurrentPlayTime -- therefore
- // don't adjust the animation in that case
- animation.getCurrentPlayTime() < animation.getDuration()) {
- mHandlingOnAnimationUpdate = true;
- long frameNum = sGlobalFrameCounter - mStartFrame;
- // If we haven't drawn our first frame, reset the time to t = 0
- // (give up after MAX_DELAY ms of waiting though - might happen, for example, if we
- // are no longer in the foreground and no frames are being rendered ever)
- if (frameNum == 0 && currentTime < mStartTime + MAX_DELAY) {
- // The first frame on animations doesn't always trigger an invalidate...
- // force an invalidate here to make sure the animation continues to advance
- mTarget.getRootView().invalidate();
- animation.setCurrentPlayTime(0);
-
- // For the second frame, if the first frame took more than 16ms,
- // adjust the start time and pretend it took only 16ms anyway. This
- // prevents a large jump in the animation due to an expensive first frame
- } else if (frameNum == 1 && currentTime < mStartTime + MAX_DELAY &&
- !mAdjustedSecondFrameTime &&
- currentTime > mStartTime + IDEAL_FRAME_DURATION) {
- animation.setCurrentPlayTime(IDEAL_FRAME_DURATION);
- mAdjustedSecondFrameTime = true;
- } else {
- if (frameNum > 1) {
- mTarget.post(new Runnable() {
- public void run() {
- animation.removeUpdateListener(FirstFrameAnimatorHelper.this);
- }
- });
- }
- if (DEBUG) print(animation);
- }
- mHandlingOnAnimationUpdate = false;
- } else {
- if (DEBUG) print(animation);
- }
- }
-
- public void print(ValueAnimator animation) {
- float flatFraction = animation.getCurrentPlayTime() / (float) animation.getDuration();
- Log.d("FirstFrameAnimatorHelper", sGlobalFrameCounter +
- "(" + (sGlobalFrameCounter - mStartFrame) + ") " + mTarget + " dirty? " +
- mTarget.isDirty() + " " + flatFraction + " " + this + " " + animation);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java b/packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java
deleted file mode 100644
index 34430d9..0000000
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java
+++ /dev/null
@@ -1,586 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.recent;
-
-import android.app.ActivityManager;
-import android.app.AppGlobals;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-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.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.os.AsyncTask;
-import android.os.Handler;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.View;
-
-import com.android.systemui.R;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-
-public class RecentTasksLoader implements View.OnTouchListener {
- static final String TAG = "RecentTasksLoader";
- static final boolean DEBUG = PhoneStatusBar.DEBUG || false;
-
- private static final int DISPLAY_TASKS = 20;
- private static final int MAX_TASKS = DISPLAY_TASKS + 1; // allow extra for non-apps
-
- private Context mContext;
- private RecentsPanelView mRecentsPanel;
-
- private Object mFirstTaskLock = new Object();
- private TaskDescription mFirstTask;
- private boolean mFirstTaskLoaded;
-
- private AsyncTask<Void, ArrayList<TaskDescription>, Void> mTaskLoader;
- private AsyncTask<Void, TaskDescription, Void> mThumbnailLoader;
- private Handler mHandler;
-
- private int mIconDpi;
- private ColorDrawableWithDimensions mDefaultThumbnailBackground;
- private ColorDrawableWithDimensions mDefaultIconBackground;
- private int mNumTasksInFirstScreenful = Integer.MAX_VALUE;
-
- private boolean mFirstScreenful;
- private ArrayList<TaskDescription> mLoadedTasks;
-
- private enum State { LOADING, LOADED, CANCELLED };
- private State mState = State.CANCELLED;
-
-
- private static RecentTasksLoader sInstance;
- public static RecentTasksLoader getInstance(Context context) {
- if (sInstance == null) {
- sInstance = new RecentTasksLoader(context);
- }
- return sInstance;
- }
-
- private RecentTasksLoader(Context context) {
- mContext = context;
- mHandler = new Handler();
-
- final Resources res = context.getResources();
-
- // get the icon size we want -- on tablets, we use bigger icons
- boolean isTablet = res.getBoolean(R.bool.config_recents_interface_for_tablets);
- if (isTablet) {
- ActivityManager activityManager =
- (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
- mIconDpi = activityManager.getLauncherLargeIconDensity();
- } else {
- mIconDpi = res.getDisplayMetrics().densityDpi;
- }
-
- // Render default icon (just a blank image)
- int defaultIconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.app_icon_size);
- int iconSize = (int) (defaultIconSize * mIconDpi / res.getDisplayMetrics().densityDpi);
- mDefaultIconBackground = new ColorDrawableWithDimensions(0x00000000, iconSize, iconSize);
-
- // Render the default thumbnail background
- int thumbnailWidth =
- (int) res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_width);
- int thumbnailHeight =
- (int) res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_height);
- int color = res.getColor(R.drawable.status_bar_recents_app_thumbnail_background);
-
- mDefaultThumbnailBackground =
- new ColorDrawableWithDimensions(color, thumbnailWidth, thumbnailHeight);
- }
-
- public void setRecentsPanel(RecentsPanelView newRecentsPanel, RecentsPanelView caller) {
- // Only allow clearing mRecentsPanel if the caller is the current recentsPanel
- if (newRecentsPanel != null || mRecentsPanel == caller) {
- mRecentsPanel = newRecentsPanel;
- if (mRecentsPanel != null) {
- mNumTasksInFirstScreenful = mRecentsPanel.numItemsInOneScreenful();
- }
- }
- }
-
- public Drawable getDefaultThumbnail() {
- return mDefaultThumbnailBackground;
- }
-
- public Drawable getDefaultIcon() {
- return mDefaultIconBackground;
- }
-
- public ArrayList<TaskDescription> getLoadedTasks() {
- return mLoadedTasks;
- }
-
- public void remove(TaskDescription td) {
- mLoadedTasks.remove(td);
- }
-
- public boolean isFirstScreenful() {
- return mFirstScreenful;
- }
-
- private boolean isCurrentHomeActivity(ComponentName component, ActivityInfo homeInfo) {
- if (homeInfo == null) {
- final PackageManager pm = mContext.getPackageManager();
- homeInfo = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
- .resolveActivityInfo(pm, 0);
- }
- return homeInfo != null
- && homeInfo.packageName.equals(component.getPackageName())
- && homeInfo.name.equals(component.getClassName());
- }
-
- // Create an TaskDescription, returning null if the title or icon is null
- TaskDescription createTaskDescription(int taskId, int persistentTaskId, Intent baseIntent,
- ComponentName origActivity, CharSequence description, int userId) {
- Intent intent = new Intent(baseIntent);
- if (origActivity != null) {
- intent.setComponent(origActivity);
- }
- final PackageManager pm = mContext.getPackageManager();
- final IPackageManager ipm = AppGlobals.getPackageManager();
- intent.setFlags((intent.getFlags()&~Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
- | Intent.FLAG_ACTIVITY_NEW_TASK);
- ResolveInfo resolveInfo = null;
- try {
- resolveInfo = ipm.resolveIntent(intent, null, 0, userId);
- } catch (RemoteException re) {
- }
- if (resolveInfo != null) {
- final ActivityInfo info = resolveInfo.activityInfo;
- final String title = info.loadLabel(pm).toString();
-
- if (title != null && title.length() > 0) {
- if (DEBUG) Log.v(TAG, "creating activity desc for id="
- + persistentTaskId + ", label=" + title);
-
- TaskDescription item = new TaskDescription(taskId,
- persistentTaskId, resolveInfo, baseIntent, info.packageName,
- description, userId);
- item.setLabel(title);
-
- return item;
- } else {
- if (DEBUG) Log.v(TAG, "SKIPPING item " + persistentTaskId);
- }
- }
- return null;
- }
-
- void loadThumbnailAndIcon(TaskDescription td) {
- final ActivityManager am = (ActivityManager)
- mContext.getSystemService(Context.ACTIVITY_SERVICE);
- final PackageManager pm = mContext.getPackageManager();
- final Bitmap thumbnail = SystemServicesProxy.getThumbnail(am, td.persistentTaskId);
- Drawable icon = getFullResIcon(td.resolveInfo, pm);
- if (td.userId != UserHandle.myUserId()) {
- // Need to badge the icon
- icon = mContext.getPackageManager().getUserBadgedIcon(icon, new UserHandle(td.userId));
- }
- if (DEBUG) Log.v(TAG, "Loaded bitmap for task "
- + td + ": " + thumbnail);
- synchronized (td) {
- if (thumbnail != null) {
- td.setThumbnail(new BitmapDrawable(mContext.getResources(), thumbnail));
- } else {
- td.setThumbnail(mDefaultThumbnailBackground);
- }
- if (icon != null) {
- td.setIcon(icon);
- }
- td.setLoaded(true);
- }
- }
-
- Drawable getFullResDefaultActivityIcon() {
- return getFullResIcon(Resources.getSystem(),
- com.android.internal.R.mipmap.sym_def_app_icon);
- }
-
- Drawable getFullResIcon(Resources resources, int iconId) {
- try {
- return resources.getDrawableForDensity(iconId, mIconDpi);
- } catch (Resources.NotFoundException e) {
- return getFullResDefaultActivityIcon();
- }
- }
-
- private Drawable getFullResIcon(ResolveInfo info, PackageManager packageManager) {
- Resources resources;
- try {
- resources = packageManager.getResourcesForApplication(
- info.activityInfo.applicationInfo);
- } catch (PackageManager.NameNotFoundException e) {
- resources = null;
- }
- if (resources != null) {
- int iconId = info.activityInfo.getIconResource();
- if (iconId != 0) {
- return getFullResIcon(resources, iconId);
- }
- }
- return getFullResDefaultActivityIcon();
- }
-
- Runnable mPreloadTasksRunnable = new Runnable() {
- public void run() {
- loadTasksInBackground();
- }
- };
-
- // additional optimization when we have software system buttons - start loading the recent
- // tasks on touch down
- @Override
- public boolean onTouch(View v, MotionEvent ev) {
- int action = ev.getAction() & MotionEvent.ACTION_MASK;
- if (action == MotionEvent.ACTION_DOWN) {
- preloadRecentTasksList();
- } else if (action == MotionEvent.ACTION_CANCEL) {
- cancelPreloadingRecentTasksList();
- } else if (action == MotionEvent.ACTION_UP) {
- // Remove the preloader if we haven't called it yet
- mHandler.removeCallbacks(mPreloadTasksRunnable);
- if (!v.isPressed()) {
- cancelLoadingThumbnailsAndIcons();
- }
-
- }
- return false;
- }
-
- public void preloadRecentTasksList() {
- mHandler.post(mPreloadTasksRunnable);
- }
-
- public void cancelPreloadingRecentTasksList() {
- cancelLoadingThumbnailsAndIcons();
- mHandler.removeCallbacks(mPreloadTasksRunnable);
- }
-
- public void cancelLoadingThumbnailsAndIcons(RecentsPanelView caller) {
- // Only oblige this request if it comes from the current RecentsPanel
- // (eg when you rotate, the old RecentsPanel request should be ignored)
- if (mRecentsPanel == caller) {
- cancelLoadingThumbnailsAndIcons();
- }
- }
-
-
- private void cancelLoadingThumbnailsAndIcons() {
- if (mRecentsPanel != null && mRecentsPanel.isShowing()) {
- return;
- }
-
- if (mTaskLoader != null) {
- mTaskLoader.cancel(false);
- mTaskLoader = null;
- }
- if (mThumbnailLoader != null) {
- mThumbnailLoader.cancel(false);
- mThumbnailLoader = null;
- }
- mLoadedTasks = null;
- if (mRecentsPanel != null) {
- mRecentsPanel.onTaskLoadingCancelled();
- }
- mFirstScreenful = false;
- mState = State.CANCELLED;
- }
-
- private void clearFirstTask() {
- synchronized (mFirstTaskLock) {
- mFirstTask = null;
- mFirstTaskLoaded = false;
- }
- }
-
- public void preloadFirstTask() {
- Thread bgLoad = new Thread() {
- public void run() {
- TaskDescription first = loadFirstTask();
- synchronized(mFirstTaskLock) {
- if (mCancelPreloadingFirstTask) {
- clearFirstTask();
- } else {
- mFirstTask = first;
- mFirstTaskLoaded = true;
- }
- mPreloadingFirstTask = false;
- }
- }
- };
- synchronized(mFirstTaskLock) {
- if (!mPreloadingFirstTask) {
- clearFirstTask();
- mPreloadingFirstTask = true;
- bgLoad.start();
- }
- }
- }
-
- public void cancelPreloadingFirstTask() {
- synchronized(mFirstTaskLock) {
- if (mPreloadingFirstTask) {
- mCancelPreloadingFirstTask = true;
- } else {
- clearFirstTask();
- }
- }
- }
-
- boolean mPreloadingFirstTask;
- boolean mCancelPreloadingFirstTask;
- public TaskDescription getFirstTask() {
- while(true) {
- synchronized(mFirstTaskLock) {
- if (mFirstTaskLoaded) {
- return mFirstTask;
- } else if (!mFirstTaskLoaded && !mPreloadingFirstTask) {
- mFirstTask = loadFirstTask();
- mFirstTaskLoaded = true;
- return mFirstTask;
- }
- }
- try {
- Thread.sleep(3);
- } catch (InterruptedException e) {
- }
- }
- }
-
- public TaskDescription loadFirstTask() {
- final ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
-
- final List<ActivityManager.RecentTaskInfo> recentTasks = am.getRecentTasksForUser(1,
- ActivityManager.RECENT_IGNORE_UNAVAILABLE | ActivityManager.RECENT_INCLUDE_PROFILES,
- UserHandle.CURRENT.getIdentifier());
- TaskDescription item = null;
- if (recentTasks.size() > 0) {
- ActivityManager.RecentTaskInfo recentInfo = recentTasks.get(0);
-
- Intent intent = new Intent(recentInfo.baseIntent);
- if (recentInfo.origActivity != null) {
- intent.setComponent(recentInfo.origActivity);
- }
-
- // Don't load the current home activity.
- if (isCurrentHomeActivity(intent.getComponent(), null)) {
- return null;
- }
-
- // Don't load ourselves
- if (intent.getComponent().getPackageName().equals(mContext.getPackageName())) {
- return null;
- }
-
- item = createTaskDescription(recentInfo.id,
- recentInfo.persistentId, recentInfo.baseIntent,
- recentInfo.origActivity, recentInfo.description,
- recentInfo.userId);
- if (item != null) {
- loadThumbnailAndIcon(item);
- }
- return item;
- }
- return null;
- }
-
- public void loadTasksInBackground() {
- loadTasksInBackground(false);
- }
- public void loadTasksInBackground(final boolean zeroeth) {
- if (mState != State.CANCELLED) {
- return;
- }
- mState = State.LOADING;
- mFirstScreenful = true;
-
- final LinkedBlockingQueue<TaskDescription> tasksWaitingForThumbnails =
- new LinkedBlockingQueue<TaskDescription>();
- mTaskLoader = new AsyncTask<Void, ArrayList<TaskDescription>, Void>() {
- @Override
- protected void onProgressUpdate(ArrayList<TaskDescription>... values) {
- if (!isCancelled()) {
- ArrayList<TaskDescription> newTasks = values[0];
- // do a callback to RecentsPanelView to let it know we have more values
- // how do we let it know we're all done? just always call back twice
- if (mRecentsPanel != null) {
- mRecentsPanel.onTasksLoaded(newTasks, mFirstScreenful);
- }
- if (mLoadedTasks == null) {
- mLoadedTasks = new ArrayList<TaskDescription>();
- }
- mLoadedTasks.addAll(newTasks);
- mFirstScreenful = false;
- }
- }
- @Override
- protected Void doInBackground(Void... params) {
- // We load in two stages: first, we update progress with just the first screenful
- // of items. Then, we update with the rest of the items
- final int origPri = Process.getThreadPriority(Process.myTid());
- Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
- final PackageManager pm = mContext.getPackageManager();
- final ActivityManager am = (ActivityManager)
- mContext.getSystemService(Context.ACTIVITY_SERVICE);
-
- final List<ActivityManager.RecentTaskInfo> recentTasks =
- am.getRecentTasks(MAX_TASKS, ActivityManager.RECENT_IGNORE_UNAVAILABLE
- | ActivityManager.RECENT_INCLUDE_PROFILES);
- int numTasks = recentTasks.size();
- ActivityInfo homeInfo = new Intent(Intent.ACTION_MAIN)
- .addCategory(Intent.CATEGORY_HOME).resolveActivityInfo(pm, 0);
-
- boolean firstScreenful = true;
- ArrayList<TaskDescription> tasks = new ArrayList<TaskDescription>();
-
- // skip the first task - assume it's either the home screen or the current activity.
- final int first = 0;
- for (int i = first, index = 0; i < numTasks && (index < MAX_TASKS); ++i) {
- if (isCancelled()) {
- break;
- }
- final ActivityManager.RecentTaskInfo recentInfo = recentTasks.get(i);
-
- Intent intent = new Intent(recentInfo.baseIntent);
- if (recentInfo.origActivity != null) {
- intent.setComponent(recentInfo.origActivity);
- }
-
- // Don't load the current home activity.
- if (isCurrentHomeActivity(intent.getComponent(), homeInfo)) {
- continue;
- }
-
- // Don't load ourselves
- if (intent.getComponent().getPackageName().equals(mContext.getPackageName())) {
- continue;
- }
-
- TaskDescription item = createTaskDescription(recentInfo.id,
- recentInfo.persistentId, recentInfo.baseIntent,
- recentInfo.origActivity, recentInfo.description,
- recentInfo.userId);
-
- if (item != null) {
- while (true) {
- try {
- tasksWaitingForThumbnails.put(item);
- break;
- } catch (InterruptedException e) {
- }
- }
- tasks.add(item);
- if (firstScreenful && tasks.size() == mNumTasksInFirstScreenful) {
- publishProgress(tasks);
- tasks = new ArrayList<TaskDescription>();
- firstScreenful = false;
- //break;
- }
- ++index;
- }
- }
-
- if (!isCancelled()) {
- publishProgress(tasks);
- if (firstScreenful) {
- // always should publish two updates
- publishProgress(new ArrayList<TaskDescription>());
- }
- }
-
- while (true) {
- try {
- tasksWaitingForThumbnails.put(new TaskDescription());
- break;
- } catch (InterruptedException e) {
- }
- }
-
- Process.setThreadPriority(origPri);
- return null;
- }
- };
- mTaskLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- loadThumbnailsAndIconsInBackground(tasksWaitingForThumbnails);
- }
-
- private void loadThumbnailsAndIconsInBackground(
- final BlockingQueue<TaskDescription> tasksWaitingForThumbnails) {
- // continually read items from tasksWaitingForThumbnails and load
- // thumbnails and icons for them. finish thread when cancelled or there
- // is a null item in tasksWaitingForThumbnails
- mThumbnailLoader = new AsyncTask<Void, TaskDescription, Void>() {
- @Override
- protected void onProgressUpdate(TaskDescription... values) {
- if (!isCancelled()) {
- TaskDescription td = values[0];
- if (td.isNull()) { // end sentinel
- mState = State.LOADED;
- } else {
- if (mRecentsPanel != null) {
- mRecentsPanel.onTaskThumbnailLoaded(td);
- }
- }
- }
- }
- @Override
- protected Void doInBackground(Void... params) {
- final int origPri = Process.getThreadPriority(Process.myTid());
- Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
-
- while (true) {
- if (isCancelled()) {
- break;
- }
- TaskDescription td = null;
- while (td == null) {
- try {
- td = tasksWaitingForThumbnails.take();
- } catch (InterruptedException e) {
- }
- }
- if (td.isNull()) { // end sentinel
- publishProgress(td);
- break;
- }
- loadThumbnailAndIcon(td);
-
- publishProgress(td);
- }
-
- Process.setThreadPriority(origPri);
- return null;
- }
- };
- mThumbnailLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recent/Recents.java b/packages/SystemUI/src/com/android/systemui/recent/Recents.java
deleted file mode 100644
index e9f3cf9..0000000
--- a/packages/SystemUI/src/com/android/systemui/recent/Recents.java
+++ /dev/null
@@ -1,326 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.recent;
-
-import android.app.ActivityOptions;
-import android.content.ActivityNotFoundException;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.Display;
-import android.view.View;
-import com.android.systemui.R;
-import com.android.systemui.RecentsComponent;
-import com.android.systemui.SystemUI;
-import com.android.systemui.recents.AlternateRecentsComponent;
-
-
-public class Recents extends SystemUI implements RecentsComponent {
- private static final String TAG = "Recents";
- private static final boolean DEBUG = true;
-
- // Which recents to use
- boolean mUseAlternateRecents = true;
- boolean mBootCompleted = false;
- static AlternateRecentsComponent sAlternateRecents;
-
- /** Returns the Recents component, creating a new one in-process if necessary. */
- public static AlternateRecentsComponent getRecentsComponent(Context context,
- boolean forceInitialize) {
- if (sAlternateRecents == null) {
- sAlternateRecents = new AlternateRecentsComponent(context);
- if (forceInitialize) {
- sAlternateRecents.onStart();
- sAlternateRecents.onBootCompleted();
- }
- }
- return sAlternateRecents;
- }
-
- @Override
- public void start() {
- if (mUseAlternateRecents) {
- if (sAlternateRecents == null) {
- sAlternateRecents = getRecentsComponent(mContext, false);
- }
- sAlternateRecents.onStart();
- }
-
- putComponent(RecentsComponent.class, this);
- }
-
- @Override
- protected void onBootCompleted() {
- if (mUseAlternateRecents) {
- if (sAlternateRecents != null) {
- sAlternateRecents.onBootCompleted();
- }
- }
- mBootCompleted = true;
- }
-
- @Override
- public void showRecents(boolean triggeredFromAltTab, View statusBarView) {
- if (mUseAlternateRecents) {
- sAlternateRecents.onShowRecents(triggeredFromAltTab);
- }
- }
-
- @Override
- public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
- if (mUseAlternateRecents) {
- sAlternateRecents.onHideRecents(triggeredFromAltTab, triggeredFromHomeKey);
- } else {
- Intent intent = new Intent(RecentsActivity.CLOSE_RECENTS_INTENT);
- intent.setPackage("com.android.systemui");
- sendBroadcastSafely(intent);
-
- RecentTasksLoader.getInstance(mContext).cancelPreloadingFirstTask();
- }
- }
-
- @Override
- public void toggleRecents(Display display, int layoutDirection, View statusBarView) {
- if (mUseAlternateRecents) {
- // Launch the alternate recents if required
- sAlternateRecents.onToggleRecents();
- return;
- }
-
- if (DEBUG) Log.d(TAG, "toggle recents panel");
- try {
- TaskDescription firstTask = RecentTasksLoader.getInstance(mContext).getFirstTask();
-
- Intent intent = new Intent(RecentsActivity.TOGGLE_RECENTS_INTENT);
- intent.setClassName("com.android.systemui",
- "com.android.systemui.recent.RecentsActivity");
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
- | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
-
- if (firstTask == null) {
- if (RecentsActivity.forceOpaqueBackground(mContext)) {
- ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext,
- R.anim.recents_launch_from_launcher_enter,
- R.anim.recents_launch_from_launcher_exit);
- mContext.startActivityAsUser(intent, opts.toBundle(), new UserHandle(
- UserHandle.USER_CURRENT));
- } else {
- // The correct window animation will be applied via the activity's style
- mContext.startActivityAsUser(intent, new UserHandle(
- UserHandle.USER_CURRENT));
- }
-
- } else {
- Bitmap first = null;
- if (firstTask.getThumbnail() instanceof BitmapDrawable) {
- first = ((BitmapDrawable) firstTask.getThumbnail()).getBitmap();
- } else {
- first = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
- Drawable d = RecentTasksLoader.getInstance(mContext).getDefaultThumbnail();
- d.draw(new Canvas(first));
- }
- final Resources res = mContext.getResources();
-
- float thumbWidth = res
- .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_width);
- float thumbHeight = res
- .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_height);
- if (first == null) {
- throw new RuntimeException("Recents thumbnail is null");
- }
- if (first.getWidth() != thumbWidth || first.getHeight() != thumbHeight) {
- first = Bitmap.createScaledBitmap(first, (int) thumbWidth, (int) thumbHeight,
- true);
- if (first == null) {
- throw new RuntimeException("Recents thumbnail is null");
- }
- }
-
-
- DisplayMetrics dm = new DisplayMetrics();
- display.getMetrics(dm);
- // calculate it here, but consider moving it elsewhere
- // first, determine which orientation you're in.
- final Configuration config = res.getConfiguration();
- int x, y;
-
- if (config.orientation == Configuration.ORIENTATION_PORTRAIT) {
- float appLabelLeftMargin = res.getDimensionPixelSize(
- R.dimen.status_bar_recents_app_label_left_margin);
- float appLabelWidth = res.getDimensionPixelSize(
- R.dimen.status_bar_recents_app_label_width);
- float thumbLeftMargin = res.getDimensionPixelSize(
- R.dimen.status_bar_recents_thumbnail_left_margin);
- float thumbBgPadding = res.getDimensionPixelSize(
- R.dimen.status_bar_recents_thumbnail_bg_padding);
-
- float width = appLabelLeftMargin +
- +appLabelWidth
- + thumbLeftMargin
- + thumbWidth
- + 2 * thumbBgPadding;
-
- x = (int) ((dm.widthPixels - width) / 2f + appLabelLeftMargin + appLabelWidth
- + thumbBgPadding + thumbLeftMargin);
- y = (int) (dm.heightPixels
- - res.getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_height)
- - thumbBgPadding);
- if (layoutDirection == View.LAYOUT_DIRECTION_RTL) {
- x = dm.widthPixels - x - res.getDimensionPixelSize(
- R.dimen.status_bar_recents_thumbnail_width);
- }
-
- } else { // if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
- float thumbTopMargin = res.getDimensionPixelSize(
- R.dimen.status_bar_recents_thumbnail_top_margin);
- float thumbBgPadding = res.getDimensionPixelSize(
- R.dimen.status_bar_recents_thumbnail_bg_padding);
- float textPadding = res.getDimensionPixelSize(
- R.dimen.status_bar_recents_text_description_padding);
- float labelTextSize = res.getDimensionPixelSize(
- R.dimen.status_bar_recents_app_label_text_size);
- Paint p = new Paint();
- p.setTextSize(labelTextSize);
- float labelTextHeight = p.getFontMetricsInt().bottom
- - p.getFontMetricsInt().top;
- float descriptionTextSize = res.getDimensionPixelSize(
- R.dimen.status_bar_recents_app_description_text_size);
- p.setTextSize(descriptionTextSize);
- float descriptionTextHeight = p.getFontMetricsInt().bottom
- - p.getFontMetricsInt().top;
-
- float statusBarHeight = res.getDimensionPixelSize(
- com.android.internal.R.dimen.status_bar_height);
- float recentsItemTopPadding = statusBarHeight;
-
- float height = thumbTopMargin
- + thumbHeight
- + 2 * thumbBgPadding + textPadding + labelTextHeight
- + recentsItemTopPadding + textPadding + descriptionTextHeight;
- float recentsItemRightPadding = res
- .getDimensionPixelSize(R.dimen.status_bar_recents_item_padding);
- float recentsScrollViewRightPadding = res
- .getDimensionPixelSize(R.dimen.status_bar_recents_right_glow_margin);
- x = (int) (dm.widthPixels - res
- .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_width)
- - thumbBgPadding - recentsItemRightPadding
- - recentsScrollViewRightPadding);
- y = (int) ((dm.heightPixels - statusBarHeight - height) / 2f + thumbTopMargin
- + recentsItemTopPadding + thumbBgPadding + statusBarHeight);
- }
-
- ActivityOptions opts = ActivityOptions.makeThumbnailScaleDownAnimation(
- statusBarView,
- first, x, y,
- new ActivityOptions.OnAnimationStartedListener() {
- public void onAnimationStarted() {
- Intent intent =
- new Intent(RecentsActivity.WINDOW_ANIMATION_START_INTENT);
- intent.setPackage("com.android.systemui");
- sendBroadcastSafely(intent);
- }
- });
- intent.putExtra(RecentsActivity.WAITING_FOR_WINDOW_ANIMATION_PARAM, true);
- startActivitySafely(intent, opts.toBundle());
- }
- } catch (ActivityNotFoundException e) {
- Log.e(TAG, "Failed to launch RecentAppsIntent", e);
- }
- }
-
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {
- if (mUseAlternateRecents) {
- sAlternateRecents.onConfigurationChanged(newConfig);
- }
- }
-
- @Override
- public void preloadRecents() {
- if (mUseAlternateRecents) {
- sAlternateRecents.onPreloadRecents();
- } else {
- Intent intent = new Intent(RecentsActivity.PRELOAD_INTENT);
- intent.setClassName("com.android.systemui",
- "com.android.systemui.recent.RecentsPreloadReceiver");
- sendBroadcastSafely(intent);
-
- RecentTasksLoader.getInstance(mContext).preloadFirstTask();
- }
- }
-
- @Override
- public void cancelPreloadingRecents() {
- if (mUseAlternateRecents) {
- sAlternateRecents.onCancelPreloadingRecents();
- } else {
- Intent intent = new Intent(RecentsActivity.CANCEL_PRELOAD_INTENT);
- intent.setClassName("com.android.systemui",
- "com.android.systemui.recent.RecentsPreloadReceiver");
- sendBroadcastSafely(intent);
-
- RecentTasksLoader.getInstance(mContext).cancelPreloadingFirstTask();
- }
- }
-
- @Override
- public void showNextAffiliatedTask() {
- if (mUseAlternateRecents) {
- sAlternateRecents.onShowNextAffiliatedTask();
- }
- }
-
- @Override
- public void showPrevAffiliatedTask() {
- if (mUseAlternateRecents) {
- sAlternateRecents.onShowPrevAffiliatedTask();
- }
- }
-
- @Override
- public void setCallback(Callbacks cb) {
- if (mUseAlternateRecents) {
- sAlternateRecents.setRecentsComponentCallback(cb);
- }
- }
-
- /**
- * Send broadcast only if BOOT_COMPLETED
- */
- private void sendBroadcastSafely(Intent intent) {
- if (!mBootCompleted) return;
- mContext.sendBroadcastAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
- }
-
- /**
- * Start activity only if BOOT_COMPLETED
- */
- private void startActivitySafely(Intent intent, Bundle opts) {
- if (!mBootCompleted) return;
- mContext.startActivityAsUser(intent, opts, new UserHandle(UserHandle.USER_CURRENT));
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsActivity.java
deleted file mode 100644
index 7ab40b0..0000000
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsActivity.java
+++ /dev/null
@@ -1,248 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recent;
-
-import android.app.Activity;
-import android.app.ActivityManager;
-import android.app.WallpaperManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.WindowManager;
-
-import com.android.systemui.R;
-import com.android.systemui.statusbar.StatusBarPanel;
-
-import java.util.List;
-
-public class RecentsActivity extends Activity {
- public static final String TOGGLE_RECENTS_INTENT = "com.android.systemui.recent.action.TOGGLE_RECENTS";
- public static final String PRELOAD_INTENT = "com.android.systemui.recent.action.PRELOAD";
- public static final String CANCEL_PRELOAD_INTENT = "com.android.systemui.recent.CANCEL_PRELOAD";
- public static final String CLOSE_RECENTS_INTENT = "com.android.systemui.recent.action.CLOSE";
- public static final String WINDOW_ANIMATION_START_INTENT = "com.android.systemui.recent.action.WINDOW_ANIMATION_START";
- public static final String PRELOAD_PERMISSION = "com.android.systemui.recent.permission.PRELOAD";
- public static final String WAITING_FOR_WINDOW_ANIMATION_PARAM = "com.android.systemui.recent.WAITING_FOR_WINDOW_ANIMATION";
- private static final String WAS_SHOWING = "was_showing";
-
- private RecentsPanelView mRecentsPanel;
- private IntentFilter mIntentFilter;
- private boolean mShowing;
- private boolean mForeground;
-
- private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (CLOSE_RECENTS_INTENT.equals(intent.getAction())) {
- if (mRecentsPanel != null && mRecentsPanel.isShowing()) {
- if (mShowing && !mForeground) {
- // Captures the case right before we transition to another activity
- mRecentsPanel.show(false);
- }
- }
- } else if (WINDOW_ANIMATION_START_INTENT.equals(intent.getAction())) {
- if (mRecentsPanel != null) {
- mRecentsPanel.onWindowAnimationStart();
- }
- }
- }
- };
-
- public class TouchOutsideListener implements View.OnTouchListener {
- private StatusBarPanel mPanel;
-
- public TouchOutsideListener(StatusBarPanel panel) {
- mPanel = panel;
- }
-
- public boolean onTouch(View v, MotionEvent ev) {
- final int action = ev.getAction();
- if (action == MotionEvent.ACTION_OUTSIDE
- || (action == MotionEvent.ACTION_DOWN
- && !mPanel.isInContentArea((int) ev.getX(), (int) ev.getY()))) {
- dismissAndGoHome();
- return true;
- }
- return false;
- }
- }
-
- @Override
- public void onPause() {
- overridePendingTransition(
- R.anim.recents_return_to_launcher_enter,
- R.anim.recents_return_to_launcher_exit);
- mForeground = false;
- super.onPause();
- }
-
- @Override
- public void onStop() {
- mShowing = false;
- if (mRecentsPanel != null) {
- mRecentsPanel.onUiHidden();
- }
- super.onStop();
- }
-
- private void updateWallpaperVisibility(boolean visible) {
- int wpflags = visible ? WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER : 0;
- int curflags = getWindow().getAttributes().flags
- & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
- if (wpflags != curflags) {
- getWindow().setFlags(wpflags, WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER);
- }
- }
-
- public static boolean forceOpaqueBackground(Context context) {
- return WallpaperManager.getInstance(context).getWallpaperInfo() != null;
- }
-
- @Override
- public void onStart() {
- // Hide wallpaper if it's not a static image
- if (forceOpaqueBackground(this)) {
- updateWallpaperVisibility(false);
- } else {
- updateWallpaperVisibility(true);
- }
- mShowing = true;
- if (mRecentsPanel != null) {
- // Call and refresh the recent tasks list in case we didn't preload tasks
- // or in case we don't get an onNewIntent
- mRecentsPanel.refreshRecentTasksList();
- mRecentsPanel.refreshViews();
- }
- super.onStart();
- }
-
- @Override
- public void onResume() {
- mForeground = true;
- super.onResume();
- }
-
- @Override
- public void onBackPressed() {
- dismissAndGoBack();
- }
-
- public void dismissAndGoHome() {
- if (mRecentsPanel != null) {
- Intent homeIntent = new Intent(Intent.ACTION_MAIN, null);
- homeIntent.addCategory(Intent.CATEGORY_HOME);
- homeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
- | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
- startActivityAsUser(homeIntent, new UserHandle(UserHandle.USER_CURRENT));
- mRecentsPanel.show(false);
- }
- }
-
- public void dismissAndGoBack() {
- if (mRecentsPanel != null) {
- final ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
-
- final List<ActivityManager.RecentTaskInfo> recentTasks =
- am.getRecentTasks(2,
- ActivityManager.RECENT_WITH_EXCLUDED |
- ActivityManager.RECENT_IGNORE_UNAVAILABLE |
- ActivityManager.RECENT_INCLUDE_PROFILES);
- if (recentTasks.size() > 1 &&
- mRecentsPanel.simulateClick(recentTasks.get(1).persistentId)) {
- // recents panel will take care of calling show(false) through simulateClick
- return;
- }
- mRecentsPanel.show(false);
- }
- finish();
- }
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- getWindow().addPrivateFlags(
- WindowManager.LayoutParams.PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR);
- setContentView(R.layout.status_bar_recent_panel);
- mRecentsPanel = (RecentsPanelView) findViewById(R.id.recents_root);
- mRecentsPanel.setOnTouchListener(new TouchOutsideListener(mRecentsPanel));
- mRecentsPanel.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
- | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
- | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
-
- final RecentTasksLoader recentTasksLoader = RecentTasksLoader.getInstance(this);
- recentTasksLoader.setRecentsPanel(mRecentsPanel, mRecentsPanel);
- mRecentsPanel.setMinSwipeAlpha(
- getResources().getInteger(R.integer.config_recent_item_min_alpha) / 100f);
-
- if (savedInstanceState == null ||
- savedInstanceState.getBoolean(WAS_SHOWING)) {
- handleIntent(getIntent(), (savedInstanceState == null));
- }
- mIntentFilter = new IntentFilter();
- mIntentFilter.addAction(CLOSE_RECENTS_INTENT);
- mIntentFilter.addAction(WINDOW_ANIMATION_START_INTENT);
- registerReceiver(mIntentReceiver, mIntentFilter);
- super.onCreate(savedInstanceState);
- }
-
- @Override
- protected void onSaveInstanceState(Bundle outState) {
- outState.putBoolean(WAS_SHOWING, mRecentsPanel.isShowing());
- }
-
- @Override
- protected void onDestroy() {
- RecentTasksLoader.getInstance(this).setRecentsPanel(null, mRecentsPanel);
- unregisterReceiver(mIntentReceiver);
- super.onDestroy();
- }
-
- @Override
- protected void onNewIntent(Intent intent) {
- handleIntent(intent, true);
- }
-
- private void handleIntent(Intent intent, boolean checkWaitingForAnimationParam) {
- super.onNewIntent(intent);
-
- if (TOGGLE_RECENTS_INTENT.equals(intent.getAction())) {
- if (mRecentsPanel != null) {
- if (mRecentsPanel.isShowing()) {
- dismissAndGoBack();
- } else {
- final RecentTasksLoader recentTasksLoader = RecentTasksLoader.getInstance(this);
- boolean waitingForWindowAnimation = checkWaitingForAnimationParam &&
- intent.getBooleanExtra(WAITING_FOR_WINDOW_ANIMATION_PARAM, false);
- mRecentsPanel.show(true, recentTasksLoader.getLoadedTasks(),
- recentTasksLoader.isFirstScreenful(), waitingForWindowAnimation);
- }
- }
- }
- }
-
- boolean isForeground() {
- return mForeground;
- }
-
- boolean isActivityShowing() {
- return mShowing;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java
deleted file mode 100644
index cf5d3a6..0000000
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java
+++ /dev/null
@@ -1,391 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.recent;
-
-import android.animation.LayoutTransition;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.database.DataSetObserver;
-import android.graphics.Canvas;
-import android.util.AttributeSet;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewTreeObserver;
-import android.view.ViewTreeObserver.OnGlobalLayoutListener;
-import android.widget.HorizontalScrollView;
-import android.widget.LinearLayout;
-
-import com.android.systemui.R;
-import com.android.systemui.SwipeHelper;
-import com.android.systemui.recent.RecentsPanelView.TaskDescriptionAdapter;
-
-import java.util.HashSet;
-import java.util.Iterator;
-
-public class RecentsHorizontalScrollView extends HorizontalScrollView
- implements SwipeHelper.Callback, RecentsPanelView.RecentsScrollView {
- private static final String TAG = RecentsPanelView.TAG;
- private static final boolean DEBUG = RecentsPanelView.DEBUG;
- private LinearLayout mLinearLayout;
- private TaskDescriptionAdapter mAdapter;
- private RecentsCallback mCallback;
- protected int mLastScrollPosition;
- private SwipeHelper mSwipeHelper;
- private FadedEdgeDrawHelper mFadedEdgeDrawHelper;
- private HashSet<View> mRecycledViews;
- private int mNumItemsInOneScreenful;
- private Runnable mOnScrollListener;
-
- public RecentsHorizontalScrollView(Context context, AttributeSet attrs) {
- super(context, attrs, 0);
- mSwipeHelper = new SwipeHelper(SwipeHelper.Y, this, context);
- mFadedEdgeDrawHelper = FadedEdgeDrawHelper.create(context, attrs, this, false);
- mRecycledViews = new HashSet<View>();
- }
-
- public void setMinSwipeAlpha(float minAlpha) {
- mSwipeHelper.setMinSwipeProgress(minAlpha);
- }
-
- private int scrollPositionOfMostRecent() {
- return mLinearLayout.getWidth() - getWidth();
- }
-
- private void addToRecycledViews(View v) {
- if (mRecycledViews.size() < mNumItemsInOneScreenful) {
- mRecycledViews.add(v);
- }
- }
-
- public View findViewForTask(int persistentTaskId) {
- for (int i = 0; i < mLinearLayout.getChildCount(); i++) {
- View v = mLinearLayout.getChildAt(i);
- RecentsPanelView.ViewHolder holder = (RecentsPanelView.ViewHolder) v.getTag();
- if (holder.taskDescription.persistentTaskId == persistentTaskId) {
- return v;
- }
- }
- return null;
- }
-
- private void update() {
- for (int i = 0; i < mLinearLayout.getChildCount(); i++) {
- View v = mLinearLayout.getChildAt(i);
- addToRecycledViews(v);
- mAdapter.recycleView(v);
- }
- LayoutTransition transitioner = getLayoutTransition();
- setLayoutTransition(null);
-
- mLinearLayout.removeAllViews();
- Iterator<View> recycledViews = mRecycledViews.iterator();
- for (int i = 0; i < mAdapter.getCount(); i++) {
- View old = null;
- if (recycledViews.hasNext()) {
- old = recycledViews.next();
- recycledViews.remove();
- old.setVisibility(VISIBLE);
- }
-
- final View view = mAdapter.getView(i, old, mLinearLayout);
-
- if (mFadedEdgeDrawHelper != null) {
- mFadedEdgeDrawHelper.addViewCallback(view);
- }
-
- OnTouchListener noOpListener = new OnTouchListener() {
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- return true;
- }
- };
-
- view.setOnClickListener(new OnClickListener() {
- public void onClick(View v) {
- mCallback.dismiss();
- }
- });
- // We don't want a click sound when we dimiss recents
- view.setSoundEffectsEnabled(false);
-
- OnClickListener launchAppListener = new OnClickListener() {
- public void onClick(View v) {
- mCallback.handleOnClick(view);
- }
- };
-
- RecentsPanelView.ViewHolder holder = (RecentsPanelView.ViewHolder) view.getTag();
- final View thumbnailView = holder.thumbnailView;
- OnLongClickListener longClickListener = new OnLongClickListener() {
- public boolean onLongClick(View v) {
- final View anchorView = view.findViewById(R.id.app_description);
- mCallback.handleLongPress(view, anchorView, thumbnailView);
- return true;
- }
- };
- thumbnailView.setClickable(true);
- thumbnailView.setOnClickListener(launchAppListener);
- thumbnailView.setOnLongClickListener(longClickListener);
-
- // We don't want to dismiss recents if a user clicks on the app title
- // (we also don't want to launch the app either, though, because the
- // app title is a small target and doesn't have great click feedback)
- final View appTitle = view.findViewById(R.id.app_label);
- appTitle.setContentDescription(" ");
- appTitle.setOnTouchListener(noOpListener);
- mLinearLayout.addView(view);
- }
- setLayoutTransition(transitioner);
-
- // Scroll to end after initial layout.
-
- final OnGlobalLayoutListener updateScroll = new OnGlobalLayoutListener() {
- public void onGlobalLayout() {
- mLastScrollPosition = scrollPositionOfMostRecent();
- scrollTo(mLastScrollPosition, 0);
- final ViewTreeObserver observer = getViewTreeObserver();
- if (observer.isAlive()) {
- observer.removeOnGlobalLayoutListener(this);
- }
- }
- };
- getViewTreeObserver().addOnGlobalLayoutListener(updateScroll);
- }
-
- @Override
- public void removeViewInLayout(final View view) {
- dismissChild(view);
- }
-
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- if (DEBUG) Log.v(TAG, "onInterceptTouchEvent()");
- return mSwipeHelper.onInterceptTouchEvent(ev) ||
- super.onInterceptTouchEvent(ev);
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent ev) {
- return mSwipeHelper.onTouchEvent(ev) ||
- super.onTouchEvent(ev);
- }
-
- public boolean canChildBeDismissed(View v) {
- return true;
- }
-
- @Override
- public boolean isAntiFalsingNeeded() {
- return false;
- }
-
- @Override
- public float getFalsingThresholdFactor() {
- return 1.0f;
- }
-
- public void dismissChild(View v) {
- mSwipeHelper.dismissChild(v, 0);
- }
-
- public void onChildDismissed(View v) {
- addToRecycledViews(v);
- mLinearLayout.removeView(v);
- mCallback.handleSwipe(v);
- // Restore the alpha/translation parameters to what they were before swiping
- // (for when these items are recycled)
- View contentView = getChildContentView(v);
- contentView.setAlpha(1f);
- contentView.setTranslationY(0);
- }
-
- public void onBeginDrag(View v) {
- // We do this so the underlying ScrollView knows that it won't get
- // the chance to intercept events anymore
- requestDisallowInterceptTouchEvent(true);
- }
-
- public void onDragCancelled(View v) {
- }
-
- @Override
- public void onChildSnappedBack(View animView) {
- }
-
- @Override
- public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) {
- return false;
- }
-
- public View getChildAtPosition(MotionEvent ev) {
- final float x = ev.getX() + getScrollX();
- final float y = ev.getY() + getScrollY();
- for (int i = 0; i < mLinearLayout.getChildCount(); i++) {
- View item = mLinearLayout.getChildAt(i);
- if (x >= item.getLeft() && x < item.getRight()
- && y >= item.getTop() && y < item.getBottom()) {
- return item;
- }
- }
- return null;
- }
-
- public View getChildContentView(View v) {
- return v.findViewById(R.id.recent_item);
- }
-
- @Override
- public void drawFadedEdges(Canvas canvas, int left, int right, int top, int bottom) {
- if (mFadedEdgeDrawHelper != null) {
-
- mFadedEdgeDrawHelper.drawCallback(canvas,
- left, right, top, bottom, getScrollX(), getScrollY(),
- 0, 0,
- getLeftFadingEdgeStrength(), getRightFadingEdgeStrength(), getPaddingTop());
- }
- }
-
- @Override
- protected void onScrollChanged(int l, int t, int oldl, int oldt) {
- super.onScrollChanged(l, t, oldl, oldt);
- if (mOnScrollListener != null) {
- mOnScrollListener.run();
- }
- }
-
- public void setOnScrollListener(Runnable listener) {
- mOnScrollListener = listener;
- }
-
- @Override
- public int getVerticalFadingEdgeLength() {
- if (mFadedEdgeDrawHelper != null) {
- return mFadedEdgeDrawHelper.getVerticalFadingEdgeLength();
- } else {
- return super.getVerticalFadingEdgeLength();
- }
- }
-
- @Override
- public int getHorizontalFadingEdgeLength() {
- if (mFadedEdgeDrawHelper != null) {
- return mFadedEdgeDrawHelper.getHorizontalFadingEdgeLength();
- } else {
- return super.getHorizontalFadingEdgeLength();
- }
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- setScrollbarFadingEnabled(true);
- mLinearLayout = (LinearLayout) findViewById(R.id.recents_linear_layout);
- final int leftPadding = getContext().getResources()
- .getDimensionPixelOffset(R.dimen.status_bar_recents_thumbnail_left_margin);
- setOverScrollEffectPadding(leftPadding, 0);
- }
-
- @Override
- public void onAttachedToWindow() {
- if (mFadedEdgeDrawHelper != null) {
- mFadedEdgeDrawHelper.onAttachedToWindowCallback(mLinearLayout, isHardwareAccelerated());
- }
- }
-
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- float densityScale = getResources().getDisplayMetrics().density;
- mSwipeHelper.setDensityScale(densityScale);
- float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
- mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
- }
-
- private void setOverScrollEffectPadding(int leftPadding, int i) {
- // TODO Add to (Vertical)ScrollView
- }
-
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- super.onSizeChanged(w, h, oldw, oldh);
-
- // Skip this work if a transition is running; it sets the scroll values independently
- // and should not have those animated values clobbered by this logic
- LayoutTransition transition = mLinearLayout.getLayoutTransition();
- if (transition != null && transition.isRunning()) {
- return;
- }
- // Keep track of the last visible item in the list so we can restore it
- // to the bottom when the orientation changes.
- mLastScrollPosition = scrollPositionOfMostRecent();
-
- // This has to happen post-layout, so run it "in the future"
- post(new Runnable() {
- public void run() {
- // Make sure we're still not clobbering the transition-set values, since this
- // runnable launches asynchronously
- LayoutTransition transition = mLinearLayout.getLayoutTransition();
- if (transition == null || !transition.isRunning()) {
- scrollTo(mLastScrollPosition, 0);
- }
- }
- });
- }
-
- public void setAdapter(TaskDescriptionAdapter adapter) {
- mAdapter = adapter;
- mAdapter.registerDataSetObserver(new DataSetObserver() {
- public void onChanged() {
- update();
- }
-
- public void onInvalidated() {
- update();
- }
- });
- DisplayMetrics dm = getResources().getDisplayMetrics();
- int childWidthMeasureSpec =
- MeasureSpec.makeMeasureSpec(dm.widthPixels, MeasureSpec.AT_MOST);
- int childheightMeasureSpec =
- MeasureSpec.makeMeasureSpec(dm.heightPixels, MeasureSpec.AT_MOST);
- View child = mAdapter.createView(mLinearLayout);
- child.measure(childWidthMeasureSpec, childheightMeasureSpec);
- mNumItemsInOneScreenful =
- (int) Math.ceil(dm.widthPixels / (double) child.getMeasuredWidth());
- addToRecycledViews(child);
-
- for (int i = 0; i < mNumItemsInOneScreenful - 1; i++) {
- addToRecycledViews(mAdapter.createView(mLinearLayout));
- }
- }
-
- public int numItemsInOneScreenful() {
- return mNumItemsInOneScreenful;
- }
-
- @Override
- public void setLayoutTransition(LayoutTransition transition) {
- // The layout transition applies to our embedded LinearLayout
- mLinearLayout.setLayoutTransition(transition);
- }
-
- public void setCallback(RecentsCallback callback) {
- mCallback = callback;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
deleted file mode 100644
index 4c3460e..0000000
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
+++ /dev/null
@@ -1,813 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.recent;
-
-import android.animation.Animator;
-import android.animation.LayoutTransition;
-import android.animation.TimeInterpolator;
-import android.app.ActivityManager;
-import android.app.ActivityManagerNative;
-import android.app.ActivityOptions;
-import android.app.TaskStackBuilder;
-import android.content.ActivityNotFoundException;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Matrix;
-import android.graphics.Shader.TileMode;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.MenuItem;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewPropertyAnimator;
-import android.view.ViewRootImpl;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.animation.AnimationUtils;
-import android.view.animation.DecelerateInterpolator;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemClickListener;
-import android.widget.BaseAdapter;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.ImageView.ScaleType;
-import android.widget.PopupMenu;
-import android.widget.TextView;
-
-import com.android.systemui.R;
-import com.android.systemui.statusbar.BaseStatusBar;
-import com.android.systemui.statusbar.StatusBarPanel;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
-
-import java.util.ArrayList;
-
-public class RecentsPanelView extends FrameLayout implements OnItemClickListener, RecentsCallback,
- StatusBarPanel, Animator.AnimatorListener {
- static final String TAG = "RecentsPanelView";
- static final boolean DEBUG = PhoneStatusBar.DEBUG || false;
- private PopupMenu mPopup;
- private View mRecentsScrim;
- private View mRecentsNoApps;
- private RecentsScrollView mRecentsContainer;
-
- private boolean mShowing;
- private boolean mWaitingToShow;
- private ViewHolder mItemToAnimateInWhenWindowAnimationIsFinished;
- private boolean mAnimateIconOfFirstTask;
- private boolean mWaitingForWindowAnimation;
- private long mWindowAnimationStartTime;
- private boolean mCallUiHiddenBeforeNextReload;
-
- private RecentTasksLoader mRecentTasksLoader;
- private ArrayList<TaskDescription> mRecentTaskDescriptions;
- private TaskDescriptionAdapter mListAdapter;
- private int mThumbnailWidth;
- private boolean mFitThumbnailToXY;
- private int mRecentItemLayoutId;
- private boolean mHighEndGfx;
-
- public static interface RecentsScrollView {
- public int numItemsInOneScreenful();
- public void setAdapter(TaskDescriptionAdapter adapter);
- public void setCallback(RecentsCallback callback);
- public void setMinSwipeAlpha(float minAlpha);
- public View findViewForTask(int persistentTaskId);
- public void drawFadedEdges(Canvas c, int left, int right, int top, int bottom);
- public void setOnScrollListener(Runnable listener);
- }
-
- private final class OnLongClickDelegate implements View.OnLongClickListener {
- View mOtherView;
- OnLongClickDelegate(View other) {
- mOtherView = other;
- }
- public boolean onLongClick(View v) {
- return mOtherView.performLongClick();
- }
- }
-
- /* package */ final static class ViewHolder {
- View thumbnailView;
- ImageView thumbnailViewImage;
- Drawable thumbnailViewDrawable;
- ImageView iconView;
- TextView labelView;
- TextView descriptionView;
- View calloutLine;
- TaskDescription taskDescription;
- boolean loadedThumbnailAndIcon;
- }
-
- /* package */ final class TaskDescriptionAdapter extends BaseAdapter {
- private LayoutInflater mInflater;
-
- public TaskDescriptionAdapter(Context context) {
- mInflater = LayoutInflater.from(context);
- }
-
- public int getCount() {
- return mRecentTaskDescriptions != null ? mRecentTaskDescriptions.size() : 0;
- }
-
- public Object getItem(int position) {
- return position; // we only need the index
- }
-
- public long getItemId(int position) {
- return position; // we just need something unique for this position
- }
-
- public View createView(ViewGroup parent) {
- View convertView = mInflater.inflate(mRecentItemLayoutId, parent, false);
- ViewHolder holder = new ViewHolder();
- holder.thumbnailView = convertView.findViewById(R.id.app_thumbnail);
- holder.thumbnailViewImage =
- (ImageView) convertView.findViewById(R.id.app_thumbnail_image);
- // If we set the default thumbnail now, we avoid an onLayout when we update
- // the thumbnail later (if they both have the same dimensions)
- updateThumbnail(holder, mRecentTasksLoader.getDefaultThumbnail(), false, false);
- holder.iconView = (ImageView) convertView.findViewById(R.id.app_icon);
- holder.iconView.setImageDrawable(mRecentTasksLoader.getDefaultIcon());
- holder.labelView = (TextView) convertView.findViewById(R.id.app_label);
- holder.calloutLine = convertView.findViewById(R.id.recents_callout_line);
- holder.descriptionView = (TextView) convertView.findViewById(R.id.app_description);
-
- convertView.setTag(holder);
- return convertView;
- }
-
- public View getView(int position, View convertView, ViewGroup parent) {
- if (convertView == null) {
- convertView = createView(parent);
- }
- final ViewHolder holder = (ViewHolder) convertView.getTag();
-
- // index is reverse since most recent appears at the bottom...
- final int index = mRecentTaskDescriptions.size() - position - 1;
-
- final TaskDescription td = mRecentTaskDescriptions.get(index);
-
- holder.labelView.setText(td.getLabel());
- holder.thumbnailView.setContentDescription(td.getLabel());
- holder.loadedThumbnailAndIcon = td.isLoaded();
- if (td.isLoaded()) {
- updateThumbnail(holder, td.getThumbnail(), true, false);
- updateIcon(holder, td.getIcon(), true, false);
- }
- if (index == 0) {
- if (mAnimateIconOfFirstTask) {
- ViewHolder oldHolder = mItemToAnimateInWhenWindowAnimationIsFinished;
- if (oldHolder != null) {
- oldHolder.iconView.setAlpha(1f);
- oldHolder.iconView.setTranslationX(0f);
- oldHolder.iconView.setTranslationY(0f);
- oldHolder.labelView.setAlpha(1f);
- oldHolder.labelView.setTranslationX(0f);
- oldHolder.labelView.setTranslationY(0f);
- if (oldHolder.calloutLine != null) {
- oldHolder.calloutLine.setAlpha(1f);
- oldHolder.calloutLine.setTranslationX(0f);
- oldHolder.calloutLine.setTranslationY(0f);
- }
- }
- mItemToAnimateInWhenWindowAnimationIsFinished = holder;
- int translation = -getResources().getDimensionPixelSize(
- R.dimen.status_bar_recents_app_icon_translate_distance);
- final Configuration config = getResources().getConfiguration();
- if (config.orientation == Configuration.ORIENTATION_PORTRAIT) {
- if (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
- translation = -translation;
- }
- holder.iconView.setAlpha(0f);
- holder.iconView.setTranslationX(translation);
- holder.labelView.setAlpha(0f);
- holder.labelView.setTranslationX(translation);
- holder.calloutLine.setAlpha(0f);
- holder.calloutLine.setTranslationX(translation);
- } else {
- holder.iconView.setAlpha(0f);
- holder.iconView.setTranslationY(translation);
- }
- if (!mWaitingForWindowAnimation) {
- animateInIconOfFirstTask();
- }
- }
- }
-
- holder.thumbnailView.setTag(td);
- holder.thumbnailView.setOnLongClickListener(new OnLongClickDelegate(convertView));
- holder.taskDescription = td;
- return convertView;
- }
-
- public void recycleView(View v) {
- ViewHolder holder = (ViewHolder) v.getTag();
- updateThumbnail(holder, mRecentTasksLoader.getDefaultThumbnail(), false, false);
- holder.iconView.setImageDrawable(mRecentTasksLoader.getDefaultIcon());
- holder.iconView.setVisibility(INVISIBLE);
- holder.iconView.animate().cancel();
- holder.labelView.setText(null);
- holder.labelView.animate().cancel();
- holder.thumbnailView.setContentDescription(null);
- holder.thumbnailView.setTag(null);
- holder.thumbnailView.setOnLongClickListener(null);
- holder.thumbnailView.setVisibility(INVISIBLE);
- holder.iconView.setAlpha(1f);
- holder.iconView.setTranslationX(0f);
- holder.iconView.setTranslationY(0f);
- holder.labelView.setAlpha(1f);
- holder.labelView.setTranslationX(0f);
- holder.labelView.setTranslationY(0f);
- if (holder.calloutLine != null) {
- holder.calloutLine.setAlpha(1f);
- holder.calloutLine.setTranslationX(0f);
- holder.calloutLine.setTranslationY(0f);
- holder.calloutLine.animate().cancel();
- }
- holder.taskDescription = null;
- holder.loadedThumbnailAndIcon = false;
- }
- }
-
- public RecentsPanelView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public RecentsPanelView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- updateValuesFromResources();
-
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RecentsPanelView,
- defStyle, 0);
-
- mRecentItemLayoutId = a.getResourceId(R.styleable.RecentsPanelView_recentItemLayout, 0);
- mRecentTasksLoader = RecentTasksLoader.getInstance(context);
- a.recycle();
- }
-
- public int numItemsInOneScreenful() {
- return mRecentsContainer.numItemsInOneScreenful();
- }
-
- private boolean pointInside(int x, int y, View v) {
- final int l = v.getLeft();
- final int r = v.getRight();
- final int t = v.getTop();
- final int b = v.getBottom();
- return x >= l && x < r && y >= t && y < b;
- }
-
- public boolean isInContentArea(int x, int y) {
- return pointInside(x, y, (View) mRecentsContainer);
- }
-
- public void show(boolean show) {
- show(show, null, false, false);
- }
-
- public void show(boolean show, ArrayList<TaskDescription> recentTaskDescriptions,
- boolean firstScreenful, boolean animateIconOfFirstTask) {
- if (show && mCallUiHiddenBeforeNextReload) {
- onUiHidden();
- recentTaskDescriptions = null;
- mAnimateIconOfFirstTask = false;
- mWaitingForWindowAnimation = false;
- } else {
- mAnimateIconOfFirstTask = animateIconOfFirstTask;
- mWaitingForWindowAnimation = animateIconOfFirstTask;
- }
- if (show) {
- mWaitingToShow = true;
- refreshRecentTasksList(recentTaskDescriptions, firstScreenful);
- showIfReady();
- } else {
- showImpl(false);
- }
- }
-
- private void showIfReady() {
- // mWaitingToShow => there was a touch up on the recents button
- // mRecentTaskDescriptions != null => we've created views for the first screenful of items
- if (mWaitingToShow && mRecentTaskDescriptions != null) {
- showImpl(true);
- }
- }
-
- static void sendCloseSystemWindows(Context context, String reason) {
- if (ActivityManagerNative.isSystemReady()) {
- try {
- ActivityManagerNative.getDefault().closeSystemDialogs(reason);
- } catch (RemoteException e) {
- }
- }
- }
-
- private void showImpl(boolean show) {
- sendCloseSystemWindows(getContext(), BaseStatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS);
-
- mShowing = show;
-
- if (show) {
- // if there are no apps, bring up a "No recent apps" message
- boolean noApps = mRecentTaskDescriptions != null
- && (mRecentTaskDescriptions.size() == 0);
- mRecentsNoApps.setAlpha(1f);
- mRecentsNoApps.setVisibility(noApps ? View.VISIBLE : View.INVISIBLE);
-
- onAnimationEnd(null);
- setFocusable(true);
- setFocusableInTouchMode(true);
- requestFocus();
- } else {
- mWaitingToShow = false;
- // call onAnimationEnd() and clearRecentTasksList() in onUiHidden()
- mCallUiHiddenBeforeNextReload = true;
- if (mPopup != null) {
- mPopup.dismiss();
- }
- }
- }
-
- protected void onAttachedToWindow () {
- super.onAttachedToWindow();
- final ViewRootImpl root = getViewRootImpl();
- if (root != null) {
- root.setDrawDuringWindowsAnimating(true);
- }
- }
-
- public void onUiHidden() {
- mCallUiHiddenBeforeNextReload = false;
- if (!mShowing && mRecentTaskDescriptions != null) {
- onAnimationEnd(null);
- clearRecentTasksList();
- }
- }
-
- public void dismiss() {
- ((RecentsActivity) getContext()).dismissAndGoHome();
- }
-
- public void dismissAndGoBack() {
- ((RecentsActivity) getContext()).dismissAndGoBack();
- }
-
- public void onAnimationCancel(Animator animation) {
- }
-
- public void onAnimationEnd(Animator animation) {
- if (mShowing) {
- final LayoutTransition transitioner = new LayoutTransition();
- ((ViewGroup)mRecentsContainer).setLayoutTransition(transitioner);
- createCustomAnimations(transitioner);
- } else {
- ((ViewGroup)mRecentsContainer).setLayoutTransition(null);
- }
- }
-
- public void onAnimationRepeat(Animator animation) {
- }
-
- public void onAnimationStart(Animator animation) {
- }
-
- @Override
- public boolean dispatchHoverEvent(MotionEvent event) {
- // Ignore hover events outside of this panel bounds since such events
- // generate spurious accessibility events with the panel content when
- // tapping outside of it, thus confusing the user.
- final int x = (int) event.getX();
- final int y = (int) event.getY();
- if (x >= 0 && x < getWidth() && y >= 0 && y < getHeight()) {
- return super.dispatchHoverEvent(event);
- }
- return true;
- }
-
- /**
- * Whether the panel is showing, or, if it's animating, whether it will be
- * when the animation is done.
- */
- public boolean isShowing() {
- return mShowing;
- }
-
- public void setRecentTasksLoader(RecentTasksLoader loader) {
- mRecentTasksLoader = loader;
- }
-
- public void updateValuesFromResources() {
- final Resources res = getContext().getResources();
- mThumbnailWidth = Math.round(res.getDimension(R.dimen.status_bar_recents_thumbnail_width));
- mFitThumbnailToXY = res.getBoolean(R.bool.config_recents_thumbnail_image_fits_to_xy);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
-
- mRecentsContainer = (RecentsScrollView) findViewById(R.id.recents_container);
- mRecentsContainer.setOnScrollListener(new Runnable() {
- public void run() {
- // need to redraw the faded edges
- invalidate();
- }
- });
- mListAdapter = new TaskDescriptionAdapter(getContext());
- mRecentsContainer.setAdapter(mListAdapter);
- mRecentsContainer.setCallback(this);
-
- mRecentsScrim = findViewById(R.id.recents_bg_protect);
- mRecentsNoApps = findViewById(R.id.recents_no_apps);
-
- if (mRecentsScrim != null) {
- mHighEndGfx = ActivityManager.isHighEndGfx();
- if (!mHighEndGfx) {
- mRecentsScrim.setBackground(null);
- } else if (mRecentsScrim.getBackground() instanceof BitmapDrawable) {
- // In order to save space, we make the background texture repeat in the Y direction
- ((BitmapDrawable) mRecentsScrim.getBackground()).setTileModeY(TileMode.REPEAT);
- }
- }
- }
-
- public void setMinSwipeAlpha(float minAlpha) {
- mRecentsContainer.setMinSwipeAlpha(minAlpha);
- }
-
- private void createCustomAnimations(LayoutTransition transitioner) {
- transitioner.setDuration(200);
- transitioner.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, 0);
- transitioner.setAnimator(LayoutTransition.DISAPPEARING, null);
- }
-
- private void updateIcon(ViewHolder h, Drawable icon, boolean show, boolean anim) {
- if (icon != null) {
- h.iconView.setImageDrawable(icon);
- if (show && h.iconView.getVisibility() != View.VISIBLE) {
- if (anim) {
- h.iconView.setAnimation(
- AnimationUtils.loadAnimation(getContext(), R.anim.recent_appear));
- }
- h.iconView.setVisibility(View.VISIBLE);
- }
- }
- }
-
- private void updateThumbnail(ViewHolder h, Drawable thumbnail, boolean show, boolean anim) {
- if (thumbnail != null) {
- // Should remove the default image in the frame
- // that this now covers, to improve scrolling speed.
- // That can't be done until the anim is complete though.
- h.thumbnailViewImage.setImageDrawable(thumbnail);
-
- // scale the image to fill the full width of the ImageView. do this only if
- // we haven't set a bitmap before, or if the bitmap size has changed
- if (h.thumbnailViewDrawable == null ||
- h.thumbnailViewDrawable.getIntrinsicWidth() != thumbnail.getIntrinsicWidth() ||
- h.thumbnailViewDrawable.getIntrinsicHeight() != thumbnail.getIntrinsicHeight()) {
- if (mFitThumbnailToXY) {
- h.thumbnailViewImage.setScaleType(ScaleType.FIT_XY);
- } else {
- Matrix scaleMatrix = new Matrix();
- float scale = mThumbnailWidth / (float) thumbnail.getIntrinsicWidth();
- scaleMatrix.setScale(scale, scale);
- h.thumbnailViewImage.setScaleType(ScaleType.MATRIX);
- h.thumbnailViewImage.setImageMatrix(scaleMatrix);
- }
- }
- if (show && h.thumbnailView.getVisibility() != View.VISIBLE) {
- if (anim) {
- h.thumbnailView.setAnimation(
- AnimationUtils.loadAnimation(getContext(), R.anim.recent_appear));
- }
- h.thumbnailView.setVisibility(View.VISIBLE);
- }
- h.thumbnailViewDrawable = thumbnail;
- }
- }
-
- void onTaskThumbnailLoaded(TaskDescription td) {
- synchronized (td) {
- if (mRecentsContainer != null) {
- ViewGroup container = (ViewGroup) mRecentsContainer;
- if (container instanceof RecentsScrollView) {
- container = (ViewGroup) container.findViewById(
- R.id.recents_linear_layout);
- }
- // Look for a view showing this thumbnail, to update.
- for (int i=0; i < container.getChildCount(); i++) {
- View v = container.getChildAt(i);
- if (v.getTag() instanceof ViewHolder) {
- ViewHolder h = (ViewHolder)v.getTag();
- if (!h.loadedThumbnailAndIcon && h.taskDescription == td) {
- // only fade in the thumbnail if recents is already visible-- we
- // show it immediately otherwise
- //boolean animateShow = mShowing &&
- // mRecentsContainer.getAlpha() > ViewConfiguration.ALPHA_THRESHOLD;
- boolean animateShow = false;
- updateIcon(h, td.getIcon(), true, animateShow);
- updateThumbnail(h, td.getThumbnail(), true, animateShow);
- h.loadedThumbnailAndIcon = true;
- }
- }
- }
- }
- }
- showIfReady();
- }
-
- private void animateInIconOfFirstTask() {
- if (mItemToAnimateInWhenWindowAnimationIsFinished != null &&
- !mRecentTasksLoader.isFirstScreenful()) {
- int timeSinceWindowAnimation =
- (int) (System.currentTimeMillis() - mWindowAnimationStartTime);
- final int minStartDelay = 150;
- final int startDelay = Math.max(0, Math.min(
- minStartDelay - timeSinceWindowAnimation, minStartDelay));
- final int duration = 250;
- final ViewHolder holder = mItemToAnimateInWhenWindowAnimationIsFinished;
- final TimeInterpolator cubic = new DecelerateInterpolator(1.5f);
- FirstFrameAnimatorHelper.initializeDrawListener(holder.iconView);
- for (View v :
- new View[] { holder.iconView, holder.labelView, holder.calloutLine }) {
- if (v != null) {
- ViewPropertyAnimator vpa = v.animate().translationX(0).translationY(0)
- .alpha(1f).setStartDelay(startDelay)
- .setDuration(duration).setInterpolator(cubic);
- FirstFrameAnimatorHelper h = new FirstFrameAnimatorHelper(vpa, v);
- }
- }
- mItemToAnimateInWhenWindowAnimationIsFinished = null;
- mAnimateIconOfFirstTask = false;
- }
- }
-
- public void onWindowAnimationStart() {
- mWaitingForWindowAnimation = false;
- mWindowAnimationStartTime = System.currentTimeMillis();
- animateInIconOfFirstTask();
- }
-
- public void clearRecentTasksList() {
- // Clear memory used by screenshots
- if (mRecentTaskDescriptions != null) {
- mRecentTasksLoader.cancelLoadingThumbnailsAndIcons(this);
- onTaskLoadingCancelled();
- }
- }
-
- public void onTaskLoadingCancelled() {
- // Gets called by RecentTasksLoader when it's cancelled
- if (mRecentTaskDescriptions != null) {
- mRecentTaskDescriptions = null;
- mListAdapter.notifyDataSetInvalidated();
- }
- }
-
- public void refreshViews() {
- mListAdapter.notifyDataSetInvalidated();
- updateUiElements();
- showIfReady();
- }
-
- public void refreshRecentTasksList() {
- refreshRecentTasksList(null, false);
- }
-
- private void refreshRecentTasksList(
- ArrayList<TaskDescription> recentTasksList, boolean firstScreenful) {
- if (mRecentTaskDescriptions == null && recentTasksList != null) {
- onTasksLoaded(recentTasksList, firstScreenful);
- } else {
- mRecentTasksLoader.loadTasksInBackground();
- }
- }
-
- public void onTasksLoaded(ArrayList<TaskDescription> tasks, boolean firstScreenful) {
- if (mRecentTaskDescriptions == null) {
- mRecentTaskDescriptions = new ArrayList<TaskDescription>(tasks);
- } else {
- mRecentTaskDescriptions.addAll(tasks);
- }
- if (((RecentsActivity) getContext()).isActivityShowing()) {
- refreshViews();
- }
- }
-
- private void updateUiElements() {
- final int items = mRecentTaskDescriptions != null
- ? mRecentTaskDescriptions.size() : 0;
-
- ((View) mRecentsContainer).setVisibility(items > 0 ? View.VISIBLE : View.GONE);
-
- // Set description for accessibility
- int numRecentApps = mRecentTaskDescriptions != null
- ? mRecentTaskDescriptions.size() : 0;
- String recentAppsAccessibilityDescription;
- if (numRecentApps == 0) {
- recentAppsAccessibilityDescription =
- getResources().getString(R.string.status_bar_no_recent_apps);
- } else {
- recentAppsAccessibilityDescription = getResources().getQuantityString(
- R.plurals.status_bar_accessibility_recent_apps, numRecentApps, numRecentApps);
- }
- setContentDescription(recentAppsAccessibilityDescription);
- }
-
- public boolean simulateClick(int persistentTaskId) {
- View v = mRecentsContainer.findViewForTask(persistentTaskId);
- if (v != null) {
- handleOnClick(v);
- return true;
- }
- return false;
- }
-
- public void handleOnClick(View view) {
- ViewHolder holder = (ViewHolder) view.getTag();
- TaskDescription ad = holder.taskDescription;
- final Context context = view.getContext();
- final ActivityManager am = (ActivityManager)
- context.getSystemService(Context.ACTIVITY_SERVICE);
-
- Bitmap bm = null;
- boolean usingDrawingCache = true;
- if (holder.thumbnailViewDrawable instanceof BitmapDrawable) {
- bm = ((BitmapDrawable) holder.thumbnailViewDrawable).getBitmap();
- if (bm.getWidth() == holder.thumbnailViewImage.getWidth() &&
- bm.getHeight() == holder.thumbnailViewImage.getHeight()) {
- usingDrawingCache = false;
- }
- }
- if (usingDrawingCache) {
- holder.thumbnailViewImage.setDrawingCacheEnabled(true);
- bm = holder.thumbnailViewImage.getDrawingCache();
- }
- Bundle opts = (bm == null) ?
- null :
- ActivityOptions.makeThumbnailScaleUpAnimation(
- holder.thumbnailViewImage, bm, 0, 0, null).toBundle();
-
- show(false);
- if (ad.taskId >= 0) {
- // This is an active task; it should just go to the foreground.
- am.moveTaskToFront(ad.taskId, ActivityManager.MOVE_TASK_WITH_HOME,
- opts);
- } else {
- Intent intent = ad.intent;
- intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
- | Intent.FLAG_ACTIVITY_TASK_ON_HOME
- | Intent.FLAG_ACTIVITY_NEW_TASK);
- if (DEBUG) Log.v(TAG, "Starting activity " + intent);
- try {
- context.startActivityAsUser(intent, opts,
- new UserHandle(ad.userId));
- } catch (SecurityException e) {
- Log.e(TAG, "Recents does not have the permission to launch " + intent, e);
- } catch (ActivityNotFoundException e) {
- Log.e(TAG, "Error launching activity " + intent, e);
- }
- }
- if (usingDrawingCache) {
- holder.thumbnailViewImage.setDrawingCacheEnabled(false);
- }
- }
-
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- handleOnClick(view);
- }
-
- public void handleSwipe(View view) {
- TaskDescription ad = ((ViewHolder) view.getTag()).taskDescription;
- if (ad == null) {
- Log.v(TAG, "Not able to find activity description for swiped task; view=" + view +
- " tag=" + view.getTag());
- return;
- }
- if (DEBUG) Log.v(TAG, "Jettison " + ad.getLabel());
- mRecentTaskDescriptions.remove(ad);
- mRecentTasksLoader.remove(ad);
-
- // Handled by widget containers to enable LayoutTransitions properly
- // mListAdapter.notifyDataSetChanged();
-
- if (mRecentTaskDescriptions.size() == 0) {
- dismissAndGoBack();
- }
-
- // Currently, either direction means the same thing, so ignore direction and remove
- // the task.
- final ActivityManager am = (ActivityManager)
- getContext().getSystemService(Context.ACTIVITY_SERVICE);
- if (am != null) {
- am.removeTask(ad.persistentTaskId);
-
- // Accessibility feedback
- setContentDescription(
- getContext().getString(R.string.accessibility_recents_item_dismissed, ad.getLabel()));
- sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
- setContentDescription(null);
- }
- }
-
- private void startApplicationDetailsActivity(String packageName, int userId) {
- Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
- Uri.fromParts("package", packageName, null));
- intent.setComponent(intent.resolveActivity(getContext().getPackageManager()));
- TaskStackBuilder.create(getContext())
- .addNextIntentWithParentStack(intent).startActivities(null, new UserHandle(userId));
- }
-
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- if (mPopup != null) {
- return true;
- } else {
- return super.onInterceptTouchEvent(ev);
- }
- }
-
- public void handleLongPress(
- final View selectedView, final View anchorView, final View thumbnailView) {
- thumbnailView.setSelected(true);
- final PopupMenu popup =
- new PopupMenu(getContext(), anchorView == null ? selectedView : anchorView);
- mPopup = popup;
- popup.getMenuInflater().inflate(R.menu.recent_popup_menu, popup.getMenu());
- popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
- public boolean onMenuItemClick(MenuItem item) {
- if (item.getItemId() == R.id.recent_remove_item) {
- ((ViewGroup) mRecentsContainer).removeViewInLayout(selectedView);
- } else if (item.getItemId() == R.id.recent_inspect_item) {
- ViewHolder viewHolder = (ViewHolder) selectedView.getTag();
- if (viewHolder != null) {
- final TaskDescription ad = viewHolder.taskDescription;
- startApplicationDetailsActivity(ad.packageName, ad.userId);
- show(false);
- } else {
- throw new IllegalStateException("Oops, no tag on view " + selectedView);
- }
- } else {
- return false;
- }
- return true;
- }
- });
- popup.setOnDismissListener(new PopupMenu.OnDismissListener() {
- public void onDismiss(PopupMenu menu) {
- thumbnailView.setSelected(false);
- mPopup = null;
- }
- });
- popup.show();
- }
-
- @Override
- protected void dispatchDraw(Canvas canvas) {
- super.dispatchDraw(canvas);
-
- int paddingLeft = getPaddingLeft();
- final boolean offsetRequired = isPaddingOffsetRequired();
- if (offsetRequired) {
- paddingLeft += getLeftPaddingOffset();
- }
-
- int left = getScrollX() + paddingLeft;
- int right = left + getRight() - getLeft() - getPaddingRight() - paddingLeft;
- int top = getScrollY() + getFadeTop(offsetRequired);
- int bottom = top + getFadeHeight(offsetRequired);
-
- if (offsetRequired) {
- right += getRightPaddingOffset();
- bottom += getBottomPaddingOffset();
- }
- mRecentsContainer.drawFadedEdges(canvas, left, right, top, bottom);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsPreloadReceiver.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsPreloadReceiver.java
deleted file mode 100644
index eb58920..0000000
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsPreloadReceiver.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recent;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-
-public class RecentsPreloadReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (RecentsActivity.PRELOAD_INTENT.equals(intent.getAction())) {
- RecentTasksLoader.getInstance(context).preloadRecentTasksList();
- } else if (RecentsActivity.CANCEL_PRELOAD_INTENT.equals(intent.getAction())){
- RecentTasksLoader.getInstance(context).cancelPreloadingRecentTasksList();
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java
deleted file mode 100644
index d518f74..0000000
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java
+++ /dev/null
@@ -1,401 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.recent;
-
-import android.animation.LayoutTransition;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.database.DataSetObserver;
-import android.graphics.Canvas;
-import android.util.AttributeSet;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewTreeObserver;
-import android.view.ViewTreeObserver.OnGlobalLayoutListener;
-import android.widget.LinearLayout;
-import android.widget.ScrollView;
-
-import com.android.systemui.R;
-import com.android.systemui.SwipeHelper;
-import com.android.systemui.recent.RecentsPanelView.TaskDescriptionAdapter;
-
-import java.util.HashSet;
-import java.util.Iterator;
-
-public class RecentsVerticalScrollView extends ScrollView
- implements SwipeHelper.Callback, RecentsPanelView.RecentsScrollView {
- private static final String TAG = RecentsPanelView.TAG;
- private static final boolean DEBUG = RecentsPanelView.DEBUG;
- private LinearLayout mLinearLayout;
- private TaskDescriptionAdapter mAdapter;
- private RecentsCallback mCallback;
- protected int mLastScrollPosition;
- private SwipeHelper mSwipeHelper;
- private FadedEdgeDrawHelper mFadedEdgeDrawHelper;
- private HashSet<View> mRecycledViews;
- private int mNumItemsInOneScreenful;
- private Runnable mOnScrollListener;
-
- public RecentsVerticalScrollView(Context context, AttributeSet attrs) {
- super(context, attrs, 0);
- mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, context);
-
- mFadedEdgeDrawHelper = FadedEdgeDrawHelper.create(context, attrs, this, true);
- mRecycledViews = new HashSet<View>();
- }
-
- public void setMinSwipeAlpha(float minAlpha) {
- mSwipeHelper.setMinSwipeProgress(minAlpha);
- }
-
- private int scrollPositionOfMostRecent() {
- return mLinearLayout.getHeight() - getHeight() + getPaddingTop();
- }
-
- private void addToRecycledViews(View v) {
- if (mRecycledViews.size() < mNumItemsInOneScreenful) {
- mRecycledViews.add(v);
- }
- }
-
- public View findViewForTask(int persistentTaskId) {
- for (int i = 0; i < mLinearLayout.getChildCount(); i++) {
- View v = mLinearLayout.getChildAt(i);
- RecentsPanelView.ViewHolder holder = (RecentsPanelView.ViewHolder) v.getTag();
- if (holder.taskDescription.persistentTaskId == persistentTaskId) {
- return v;
- }
- }
- return null;
- }
-
- private void update() {
- for (int i = 0; i < mLinearLayout.getChildCount(); i++) {
- View v = mLinearLayout.getChildAt(i);
- addToRecycledViews(v);
- mAdapter.recycleView(v);
- }
- LayoutTransition transitioner = getLayoutTransition();
- setLayoutTransition(null);
-
- mLinearLayout.removeAllViews();
-
- // Once we can clear the data associated with individual item views,
- // we can get rid of the removeAllViews() and the code below will
- // recycle them.
- Iterator<View> recycledViews = mRecycledViews.iterator();
- for (int i = 0; i < mAdapter.getCount(); i++) {
- View old = null;
- if (recycledViews.hasNext()) {
- old = recycledViews.next();
- recycledViews.remove();
- old.setVisibility(VISIBLE);
- }
- final View view = mAdapter.getView(i, old, mLinearLayout);
-
- if (mFadedEdgeDrawHelper != null) {
- mFadedEdgeDrawHelper.addViewCallback(view);
- }
-
- OnTouchListener noOpListener = new OnTouchListener() {
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- return true;
- }
- };
-
- view.setOnClickListener(new OnClickListener() {
- public void onClick(View v) {
- mCallback.dismiss();
- }
- });
- // We don't want a click sound when we dimiss recents
- view.setSoundEffectsEnabled(false);
-
- OnClickListener launchAppListener = new OnClickListener() {
- public void onClick(View v) {
- mCallback.handleOnClick(view);
- }
- };
-
- RecentsPanelView.ViewHolder holder = (RecentsPanelView.ViewHolder) view.getTag();
- final View thumbnailView = holder.thumbnailView;
- OnLongClickListener longClickListener = new OnLongClickListener() {
- public boolean onLongClick(View v) {
- final View anchorView = view.findViewById(R.id.app_description);
- mCallback.handleLongPress(view, anchorView, thumbnailView);
- return true;
- }
- };
- thumbnailView.setClickable(true);
- thumbnailView.setOnClickListener(launchAppListener);
- thumbnailView.setOnLongClickListener(longClickListener);
-
- // We don't want to dismiss recents if a user clicks on the app title
- // (we also don't want to launch the app either, though, because the
- // app title is a small target and doesn't have great click feedback)
- final View appTitle = view.findViewById(R.id.app_label);
- appTitle.setContentDescription(" ");
- appTitle.setOnTouchListener(noOpListener);
- final View calloutLine = view.findViewById(R.id.recents_callout_line);
- if (calloutLine != null) {
- calloutLine.setOnTouchListener(noOpListener);
- }
-
- mLinearLayout.addView(view);
- }
- setLayoutTransition(transitioner);
-
- // Scroll to end after initial layout.
- final OnGlobalLayoutListener updateScroll = new OnGlobalLayoutListener() {
- public void onGlobalLayout() {
- mLastScrollPosition = scrollPositionOfMostRecent();
- scrollTo(0, mLastScrollPosition);
- final ViewTreeObserver observer = getViewTreeObserver();
- if (observer.isAlive()) {
- observer.removeOnGlobalLayoutListener(this);
- }
- }
- };
- getViewTreeObserver().addOnGlobalLayoutListener(updateScroll);
- }
-
- @Override
- public void removeViewInLayout(final View view) {
- dismissChild(view);
- }
-
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- if (DEBUG) Log.v(TAG, "onInterceptTouchEvent()");
- return mSwipeHelper.onInterceptTouchEvent(ev) ||
- super.onInterceptTouchEvent(ev);
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent ev) {
- return mSwipeHelper.onTouchEvent(ev) ||
- super.onTouchEvent(ev);
- }
-
- public boolean canChildBeDismissed(View v) {
- return true;
- }
-
- @Override
- public boolean isAntiFalsingNeeded() {
- return false;
- }
-
- @Override
- public float getFalsingThresholdFactor() {
- return 1.0f;
- }
-
- public void dismissChild(View v) {
- mSwipeHelper.dismissChild(v, 0);
- }
-
- public void onChildDismissed(View v) {
- addToRecycledViews(v);
- mLinearLayout.removeView(v);
- mCallback.handleSwipe(v);
- // Restore the alpha/translation parameters to what they were before swiping
- // (for when these items are recycled)
- View contentView = getChildContentView(v);
- contentView.setAlpha(1f);
- contentView.setTranslationX(0);
- }
-
- public void onBeginDrag(View v) {
- // We do this so the underlying ScrollView knows that it won't get
- // the chance to intercept events anymore
- requestDisallowInterceptTouchEvent(true);
- }
-
- public void onDragCancelled(View v) {
- }
-
- @Override
- public void onChildSnappedBack(View animView) {
- }
-
- @Override
- public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) {
- return false;
- }
-
- public View getChildAtPosition(MotionEvent ev) {
- final float x = ev.getX() + getScrollX();
- final float y = ev.getY() + getScrollY();
- for (int i = 0; i < mLinearLayout.getChildCount(); i++) {
- View item = mLinearLayout.getChildAt(i);
- if (item.getVisibility() == View.VISIBLE
- && x >= item.getLeft() && x < item.getRight()
- && y >= item.getTop() && y < item.getBottom()) {
- return item;
- }
- }
- return null;
- }
-
- public View getChildContentView(View v) {
- return v.findViewById(R.id.recent_item);
- }
-
- @Override
- public void drawFadedEdges(Canvas canvas, int left, int right, int top, int bottom) {
- if (mFadedEdgeDrawHelper != null) {
- final boolean offsetRequired = isPaddingOffsetRequired();
- mFadedEdgeDrawHelper.drawCallback(canvas,
- left, right, top + getFadeTop(offsetRequired), bottom, getScrollX(), getScrollY(),
- getTopFadingEdgeStrength(), getBottomFadingEdgeStrength(),
- 0, 0, getPaddingTop());
- }
- }
-
- @Override
- protected void onScrollChanged(int l, int t, int oldl, int oldt) {
- super.onScrollChanged(l, t, oldl, oldt);
- if (mOnScrollListener != null) {
- mOnScrollListener.run();
- }
- }
-
- public void setOnScrollListener(Runnable listener) {
- mOnScrollListener = listener;
- }
-
- @Override
- public int getVerticalFadingEdgeLength() {
- if (mFadedEdgeDrawHelper != null) {
- return mFadedEdgeDrawHelper.getVerticalFadingEdgeLength();
- } else {
- return super.getVerticalFadingEdgeLength();
- }
- }
-
- @Override
- public int getHorizontalFadingEdgeLength() {
- if (mFadedEdgeDrawHelper != null) {
- return mFadedEdgeDrawHelper.getHorizontalFadingEdgeLength();
- } else {
- return super.getHorizontalFadingEdgeLength();
- }
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- setScrollbarFadingEnabled(true);
- mLinearLayout = (LinearLayout) findViewById(R.id.recents_linear_layout);
- final int leftPadding = getContext().getResources()
- .getDimensionPixelOffset(R.dimen.status_bar_recents_thumbnail_left_margin);
- setOverScrollEffectPadding(leftPadding, 0);
- }
-
- @Override
- public void onAttachedToWindow() {
- if (mFadedEdgeDrawHelper != null) {
- mFadedEdgeDrawHelper.onAttachedToWindowCallback(mLinearLayout, isHardwareAccelerated());
- }
- }
-
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- float densityScale = getResources().getDisplayMetrics().density;
- mSwipeHelper.setDensityScale(densityScale);
- float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
- mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
- }
-
- private void setOverScrollEffectPadding(int leftPadding, int i) {
- // TODO Add to (Vertical)ScrollView
- }
-
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- super.onSizeChanged(w, h, oldw, oldh);
-
- // Skip this work if a transition is running; it sets the scroll values independently
- // and should not have those animated values clobbered by this logic
- LayoutTransition transition = mLinearLayout.getLayoutTransition();
- if (transition != null && transition.isRunning()) {
- return;
- }
- // Keep track of the last visible item in the list so we can restore it
- // to the bottom when the orientation changes.
- mLastScrollPosition = scrollPositionOfMostRecent();
-
- // This has to happen post-layout, so run it "in the future"
- post(new Runnable() {
- public void run() {
- // Make sure we're still not clobbering the transition-set values, since this
- // runnable launches asynchronously
- LayoutTransition transition = mLinearLayout.getLayoutTransition();
- if (transition == null || !transition.isRunning()) {
- scrollTo(0, mLastScrollPosition);
- }
- }
- });
- }
-
- public void setAdapter(TaskDescriptionAdapter adapter) {
- mAdapter = adapter;
- mAdapter.registerDataSetObserver(new DataSetObserver() {
- public void onChanged() {
- update();
- }
-
- public void onInvalidated() {
- update();
- }
- });
-
- DisplayMetrics dm = getResources().getDisplayMetrics();
- int childWidthMeasureSpec =
- MeasureSpec.makeMeasureSpec(dm.widthPixels, MeasureSpec.AT_MOST);
- int childheightMeasureSpec =
- MeasureSpec.makeMeasureSpec(dm.heightPixels, MeasureSpec.AT_MOST);
- View child = mAdapter.createView(mLinearLayout);
- child.measure(childWidthMeasureSpec, childheightMeasureSpec);
- mNumItemsInOneScreenful =
- (int) Math.ceil(dm.heightPixels / (double) child.getMeasuredHeight());
- addToRecycledViews(child);
-
- for (int i = 0; i < mNumItemsInOneScreenful - 1; i++) {
- addToRecycledViews(mAdapter.createView(mLinearLayout));
- }
- }
-
- public int numItemsInOneScreenful() {
- return mNumItemsInOneScreenful;
- }
-
- @Override
- public void setLayoutTransition(LayoutTransition transition) {
- // The layout transition applies to our embedded LinearLayout
- mLinearLayout.setLayoutTransition(transition);
- }
-
- public void setCallback(RecentsCallback callback) {
- mCallback = callback;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recent/TaskDescription.java b/packages/SystemUI/src/com/android/systemui/recent/TaskDescription.java
deleted file mode 100644
index 5ad965f..0000000
--- a/packages/SystemUI/src/com/android/systemui/recent/TaskDescription.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.recent;
-
-import android.os.UserHandle;
-import android.content.Intent;
-import android.content.pm.ResolveInfo;
-import android.graphics.drawable.Drawable;
-
-public final class TaskDescription {
- final ResolveInfo resolveInfo;
- final int taskId; // application task id for curating apps
- final int persistentTaskId; // persistent id
- final Intent intent; // launch intent for application
- final String packageName; // used to override animations (see onClick())
- final CharSequence description;
- final int userId;
-
- private Drawable mThumbnail; // generated by Activity.onCreateThumbnail()
- private Drawable mIcon; // application package icon
- private CharSequence mLabel; // application package label
- private boolean mLoaded;
-
- public TaskDescription(int _taskId, int _persistentTaskId,
- ResolveInfo _resolveInfo, Intent _intent,
- String _packageName, CharSequence _description, int _userId) {
- resolveInfo = _resolveInfo;
- intent = _intent;
- taskId = _taskId;
- persistentTaskId = _persistentTaskId;
-
- description = _description;
- packageName = _packageName;
- userId = _userId;
- }
-
- public TaskDescription() {
- resolveInfo = null;
- intent = null;
- taskId = -1;
- persistentTaskId = -1;
-
- description = null;
- packageName = null;
- userId = UserHandle.USER_NULL;
- }
-
- public void setLoaded(boolean loaded) {
- mLoaded = loaded;
- }
-
- public boolean isLoaded() {
- return mLoaded;
- }
-
- public boolean isNull() {
- return resolveInfo == null;
- }
-
- // mark all these as locked?
- public CharSequence getLabel() {
- return mLabel;
- }
-
- public void setLabel(CharSequence label) {
- mLabel = label;
- }
-
- public Drawable getIcon() {
- return mIcon;
- }
-
- public void setIcon(Drawable icon) {
- mIcon = icon;
- }
-
- public void setThumbnail(Drawable thumbnail) {
- mThumbnail = thumbnail;
- }
-
- public Drawable getThumbnail() {
- return mThumbnail;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
index 0a1718d..18c213d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
@@ -29,18 +29,16 @@ public class Constants {
public static final boolean EnableTransitionThumbnailDebugMode = false;
// Enables the filtering of tasks according to their grouping
public static final boolean EnableTaskFiltering = false;
- // Enables clipping of tasks against each other
- public static final boolean EnableTaskStackClipping = true;
- // Enables tapping on the TaskBar to launch the task
- public static final boolean EnableTaskBarTouchEvents = true;
- // Enables app-info pane on long-pressing the icon
- public static final boolean EnableDevAppInfoOnLongPress = true;
+ // Enables dismiss-all
+ public static final boolean EnableDismissAll = false;
// Enables debug mode
public static final boolean EnableDebugMode = false;
// Enables the search bar layout
public static final boolean EnableSearchLayout = true;
// Enables the thumbnail alpha on the front-most task
public static final boolean EnableThumbnailAlphaOnFrontmost = false;
+ // Enables all system stacks to show up in the same recents stack
+ public static final boolean EnableMultiStackToSingleStack = true;
// This disables the bitmap and icon caches
public static final boolean DisableBackgroundCache = false;
// Enables the simulated task affiliations
@@ -59,8 +57,6 @@ public class Constants {
public static class Values {
public static class App {
public static int AppWidgetHostId = 1024;
- public static String Key_SearchAppWidgetId = "searchAppWidgetId";
- public static String Key_DebugModeEnabled = "debugModeEnabled";
public static String DebugModeVersion = "A";
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index 2ddab48..6a45369 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -20,11 +20,9 @@ import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ITaskStackListener;
-import android.appwidget.AppWidgetHost;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -33,14 +31,19 @@ import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
+import android.os.AsyncTask;
import android.os.Handler;
import android.os.SystemClock;
import android.os.UserHandle;
-import android.util.Pair;
+import android.util.MutableBoolean;
+import android.view.Display;
import android.view.LayoutInflater;
import android.view.View;
+import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.RecentsComponent;
+import com.android.systemui.SystemUI;
+import com.android.systemui.SystemUIApplication;
import com.android.systemui.recents.misc.Console;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.model.RecentsTaskLoadPlan;
@@ -52,10 +55,9 @@ import com.android.systemui.recents.views.TaskStackView;
import com.android.systemui.recents.views.TaskStackViewLayoutAlgorithm;
import com.android.systemui.recents.views.TaskViewHeader;
import com.android.systemui.recents.views.TaskViewTransform;
+import com.android.systemui.statusbar.phone.PhoneStatusBar;
import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
/**
* Annotation for a method that is only called from the primary user's SystemUI process and will be
@@ -69,7 +71,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
@interface ProxyFromAnyToPrimaryUser {}
/** A proxy implementation for the recents component */
-public class AlternateRecentsComponent implements ActivityOptions.OnAnimationStartedListener {
+public class Recents extends SystemUI
+ implements ActivityOptions.OnAnimationStartedListener, RecentsComponent {
final public static String EXTRA_TRIGGERED_FROM_ALT_TAB = "triggeredFromAltTab";
final public static String EXTRA_TRIGGERED_FROM_HOME_KEY = "triggeredFromHomeKey";
@@ -78,6 +81,8 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
// Owner proxy events
final public static String ACTION_PROXY_NOTIFY_RECENTS_VISIBLITY_TO_OWNER =
"action_notify_recents_visibility_change";
+ final public static String ACTION_PROXY_SCREEN_PINNING_REQUEST_TO_OWNER =
+ "action_screen_pinning_request";
final public static String ACTION_START_ENTER_ANIMATION = "action_start_enter_animation";
final public static String ACTION_TOGGLE_RECENTS_ACTIVITY = "action_toggle_recents_activity";
@@ -85,7 +90,7 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
final static int sMinToggleDelay = 350;
- final static String sToggleRecentsAction = "com.android.systemui.recents.SHOW_RECENTS";
+ public final static String sToggleRecentsAction = "com.android.systemui.recents.SHOW_RECENTS";
public final static String sRecentsPackage = "com.android.systemui";
public final static String sRecentsActivity = "com.android.systemui.recents.RecentsActivity";
@@ -109,6 +114,9 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
/** Preloads the next task */
public void run() {
+ // Temporarily skip this if multi stack is enabled
+ if (mConfig.multiStackEnabled) return;
+
RecentsConfiguration config = RecentsConfiguration.getInstance();
if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
@@ -144,19 +152,23 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
case ACTION_PROXY_NOTIFY_RECENTS_VISIBLITY_TO_OWNER:
visibilityChanged(intent.getBooleanExtra(EXTRA_RECENTS_VISIBILITY, false));
break;
+ case ACTION_PROXY_SCREEN_PINNING_REQUEST_TO_OWNER:
+ onStartScreenPinning(context);
+ break;
}
}
}
static RecentsComponent.Callbacks sRecentsComponentCallbacks;
static RecentsTaskLoadPlan sInstanceLoadPlan;
+ static Recents sInstance;
- Context mContext;
LayoutInflater mInflater;
SystemServicesProxy mSystemServicesProxy;
Handler mHandler;
TaskStackListenerImpl mTaskStackListener;
RecentsOwnerEventProxyReceiver mProxyBroadcastReceiver;
+ RecentsAppWidgetHost mAppWidgetHost;
boolean mBootCompleted;
boolean mStartAnimationTriggered;
boolean mCanReuseTaskStackViews = true;
@@ -173,19 +185,56 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
// Header (for transition)
TaskViewHeader mHeaderBar;
+ final Object mHeaderBarLock = new Object();
TaskStackView mDummyStackView;
// Variables to keep track of if we need to start recents after binding
boolean mTriggeredFromAltTab;
long mLastToggleTime;
- public AlternateRecentsComponent(Context context) {
- RecentsTaskLoader.initialize(context);
- mInflater = LayoutInflater.from(context);
- mContext = context;
- mSystemServicesProxy = new SystemServicesProxy(context);
+ Bitmap mThumbnailTransitionBitmapCache;
+ Task mThumbnailTransitionBitmapCacheKey;
+
+ public Recents() {
+ }
+
+ /**
+ * Gets the singleton instance and starts it if needed. On the primary user on the device, this
+ * component gets started as a normal {@link SystemUI} component. On a secondary user, this
+ * lifecycle doesn't exist, so we need to start it manually here if needed.
+ */
+ public static Recents getInstanceAndStartIfNeeded(Context ctx) {
+ if (sInstance == null) {
+ sInstance = new Recents();
+ sInstance.mContext = ctx;
+ sInstance.start();
+ sInstance.onBootCompleted();
+ }
+ return sInstance;
+ }
+
+ /** Creates a new broadcast intent */
+ static Intent createLocalBroadcastIntent(Context context, String action) {
+ Intent intent = new Intent(action);
+ intent.setPackage(context.getPackageName());
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT |
+ Intent.FLAG_RECEIVER_FOREGROUND);
+ return intent;
+ }
+
+ /** Initializes the Recents. */
+ @ProxyFromPrimaryToCurrentUser
+ @Override
+ public void start() {
+ if (sInstance == null) {
+ sInstance = this;
+ }
+ RecentsTaskLoader.initialize(mContext);
+ mInflater = LayoutInflater.from(mContext);
+ mSystemServicesProxy = new SystemServicesProxy(mContext);
mHandler = new Handler();
mTaskStackBounds = new Rect();
+ mAppWidgetHost = new RecentsAppWidgetHost(mContext, Constants.Values.App.AppWidgetHostId);
// Register the task stack listener
mTaskStackListener = new TaskStackListenerImpl(mHandler);
@@ -197,28 +246,16 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
if (mSystemServicesProxy.isForegroundUserOwner()) {
mProxyBroadcastReceiver = new RecentsOwnerEventProxyReceiver();
IntentFilter filter = new IntentFilter();
- filter.addAction(AlternateRecentsComponent.ACTION_PROXY_NOTIFY_RECENTS_VISIBLITY_TO_OWNER);
+ filter.addAction(Recents.ACTION_PROXY_NOTIFY_RECENTS_VISIBLITY_TO_OWNER);
+ filter.addAction(Recents.ACTION_PROXY_SCREEN_PINNING_REQUEST_TO_OWNER);
mContext.registerReceiverAsUser(mProxyBroadcastReceiver, UserHandle.CURRENT, filter,
null, mHandler);
}
- }
- /** Creates a new broadcast intent */
- static Intent createLocalBroadcastIntent(Context context, String action) {
- Intent intent = new Intent(action);
- intent.setPackage(context.getPackageName());
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT |
- Intent.FLAG_RECEIVER_FOREGROUND);
- return intent;
- }
-
- /** Initializes the Recents. */
- @ProxyFromPrimaryToCurrentUser
- public void onStart() {
// Initialize some static datastructures
TaskStackViewLayoutAlgorithm.initializeCurve();
// Load the header bar layout
- reloadHeaderBarLayout(true);
+ reloadHeaderBarLayout();
// 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.
@@ -230,17 +267,20 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize();
launchOpts.onlyLoadForCache = true;
loader.loadTasks(mContext, plan, launchOpts);
+ putComponent(Recents.class, this);
}
+ @Override
public void onBootCompleted() {
mBootCompleted = true;
}
/** Shows the Recents. */
@ProxyFromPrimaryToCurrentUser
- public void onShowRecents(boolean triggeredFromAltTab) {
+ @Override
+ public void showRecents(boolean triggeredFromAltTab, View statusBarView) {
if (mSystemServicesProxy.isForegroundUserOwner()) {
- showRecents(triggeredFromAltTab);
+ showRecentsInternal(triggeredFromAltTab);
} else {
Intent intent = createLocalBroadcastIntent(mContext,
RecentsUserEventProxyReceiver.ACTION_PROXY_SHOW_RECENTS_TO_USER);
@@ -248,7 +288,8 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
}
}
- void showRecents(boolean triggeredFromAltTab) {
+
+ void showRecentsInternal(boolean triggeredFromAltTab) {
mTriggeredFromAltTab = triggeredFromAltTab;
try {
@@ -260,9 +301,10 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
/** Hides the Recents. */
@ProxyFromPrimaryToCurrentUser
- public void onHideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
+ @Override
+ public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
if (mSystemServicesProxy.isForegroundUserOwner()) {
- hideRecents(triggeredFromAltTab, triggeredFromHomeKey);
+ hideRecentsInternal(triggeredFromAltTab, triggeredFromHomeKey);
} else {
Intent intent = createLocalBroadcastIntent(mContext,
RecentsUserEventProxyReceiver.ACTION_PROXY_HIDE_RECENTS_TO_USER);
@@ -271,31 +313,32 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
}
}
- void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
+
+ void hideRecentsInternal(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
if (mBootCompleted) {
- ActivityManager.RunningTaskInfo topTask = mSystemServicesProxy.getTopMostTask();
- if (topTask != null && mSystemServicesProxy.isRecentsTopMost(topTask, null)) {
- // Notify recents to hide itself
- Intent intent = createLocalBroadcastIntent(mContext, ACTION_HIDE_RECENTS_ACTIVITY);
- intent.putExtra(EXTRA_TRIGGERED_FROM_ALT_TAB, triggeredFromAltTab);
- intent.putExtra(EXTRA_TRIGGERED_FROM_HOME_KEY, triggeredFromHomeKey);
- mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
- }
+ // Defer to the activity to handle hiding recents, if it handles it, then it must still
+ // be visible
+ Intent intent = createLocalBroadcastIntent(mContext, ACTION_HIDE_RECENTS_ACTIVITY);
+ intent.putExtra(EXTRA_TRIGGERED_FROM_ALT_TAB, triggeredFromAltTab);
+ intent.putExtra(EXTRA_TRIGGERED_FROM_HOME_KEY, triggeredFromHomeKey);
+ mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
}
}
/** Toggles the Recents activity. */
@ProxyFromPrimaryToCurrentUser
- public void onToggleRecents() {
+ @Override
+ public void toggleRecents(Display display, int layoutDirection, View statusBarView) {
if (mSystemServicesProxy.isForegroundUserOwner()) {
- toggleRecents();
+ toggleRecentsInternal();
} else {
Intent intent = createLocalBroadcastIntent(mContext,
RecentsUserEventProxyReceiver.ACTION_PROXY_TOGGLE_RECENTS_TO_USER);
mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
}
}
- void toggleRecents() {
+
+ void toggleRecentsInternal() {
mTriggeredFromAltTab = false;
try {
@@ -307,35 +350,56 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
/** Preloads info for the Recents activity. */
@ProxyFromPrimaryToCurrentUser
- public void onPreloadRecents() {
+ @Override
+ public void preloadRecents() {
if (mSystemServicesProxy.isForegroundUserOwner()) {
- preloadRecents();
+ preloadRecentsInternal();
} else {
Intent intent = createLocalBroadcastIntent(mContext,
RecentsUserEventProxyReceiver.ACTION_PROXY_PRELOAD_RECENTS_TO_USER);
mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
}
}
- void preloadRecents() {
+
+ void preloadRecentsInternal() {
// Preload only the raw task list into a new load plan (which will be consumed by the
- // RecentsActivity)
+ // RecentsActivity) only if there is a task to animate to.
+ ActivityManager.RunningTaskInfo topTask = mSystemServicesProxy.getTopMostTask();
+ MutableBoolean topTaskHome = new MutableBoolean(true);
RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
sInstanceLoadPlan = loader.createLoadPlan(mContext);
- sInstanceLoadPlan.preloadRawTasks(true);
+ if (topTask != null && !mSystemServicesProxy.isRecentsTopMost(topTask, topTaskHome)) {
+ sInstanceLoadPlan.preloadRawTasks(topTaskHome.value);
+ loader.preloadTasks(sInstanceLoadPlan, topTaskHome.value);
+ TaskStack top = sInstanceLoadPlan.getAllTaskStacks().get(0);
+ if (top.getTaskCount() > 0) {
+ preCacheThumbnailTransitionBitmapAsync(topTask, top, mDummyStackView,
+ topTaskHome.value);
+ }
+ }
}
- public void onCancelPreloadingRecents() {
+ @Override
+ public void cancelPreloadingRecents() {
// Do nothing
}
void showRelativeAffiliatedTask(boolean showNextTask) {
+ // Return early if there is no focused stack
+ int focusedStackId = mSystemServicesProxy.getFocusedStack();
+ TaskStack focusedStack = null;
RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
loader.preloadTasks(plan, true /* isTopTaskHome */);
- TaskStack stack = plan.getTaskStack();
+ if (mConfig.multiStackEnabled) {
+ if (focusedStackId < 0) return;
+ focusedStack = plan.getTaskStack(focusedStackId);
+ } else {
+ focusedStack = plan.getAllTaskStacks().get(0);
+ }
- // Return early if there are no tasks
- if (stack.getTaskCount() == 0) return;
+ // Return early if there are no tasks in the focused stack
+ if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
ActivityManager.RunningTaskInfo runningTask = mSystemServicesProxy.getTopMostTask();
// Return early if there is no running task (can't determine affiliated tasks in this case)
@@ -344,7 +408,7 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
if (mSystemServicesProxy.isInHomeStack(runningTask.id)) return;
// Find the task in the recents list
- ArrayList<Task> tasks = stack.getTasks();
+ ArrayList<Task> tasks = focusedStack.getTasks();
Task toTask = null;
ActivityOptions launchOpts = null;
int taskCount = tasks.size();
@@ -366,7 +430,7 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
R.anim.recents_launch_prev_affiliated_task_source);
}
if (toTaskKey != null) {
- toTask = stack.findTaskWithId(toTaskKey.id);
+ toTask = focusedStack.findTaskWithId(toTaskKey.id);
}
numAffiliatedTasks = group.getTaskCount();
break;
@@ -399,11 +463,13 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
}
}
- public void onShowNextAffiliatedTask() {
+ @Override
+ public void showNextAffiliatedTask() {
showRelativeAffiliatedTask(true);
}
- public void onShowPrevAffiliatedTask() {
+ @Override
+ public void showPrevAffiliatedTask() {
showRelativeAffiliatedTask(false);
}
@@ -422,11 +488,11 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
// Don't reuse task stack views if the configuration changes
mCanReuseTaskStackViews = false;
// Reload the header bar layout
- reloadHeaderBarLayout(false);
+ reloadHeaderBarLayout();
}
/** Prepares the header bar layout. */
- void reloadHeaderBarLayout(boolean reloadWidget) {
+ void reloadHeaderBarLayout() {
Resources res = mContext.getResources();
mWindowRect = mSystemServicesProxy.getWindowRect();
mStatusBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
@@ -434,12 +500,17 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
mNavBarWidth = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_width);
mConfig = RecentsConfiguration.reinitialize(mContext, mSystemServicesProxy);
mConfig.updateOnConfigurationChange();
- if (reloadWidget) {
- // Reload the widget id before we get the task stack bounds
- reloadSearchBarAppWidget(mContext, mSystemServicesProxy);
- }
- mConfig.getTaskStackBounds(mWindowRect.width(), mWindowRect.height(), mStatusBarHeight,
- (mConfig.hasTransposedNavBar ? mNavBarWidth : 0), mTaskStackBounds);
+ Rect searchBarBounds = new Rect();
+ // Try and pre-emptively bind the search widget on startup to ensure that we
+ // have the right thumbnail bounds to animate to.
+ // Note: We have to reload the widget id before we get the task stack bounds below
+ if (mSystemServicesProxy.getOrBindSearchAppWidget(mContext, mAppWidgetHost) != null) {
+ mConfig.getSearchBarBounds(mWindowRect.width(), mWindowRect.height(),
+ mStatusBarHeight, searchBarBounds);
+ }
+ mConfig.getAvailableTaskStackBounds(mWindowRect.width(), mWindowRect.height(),
+ mStatusBarHeight, (mConfig.hasTransposedNavBar ? mNavBarWidth : 0), searchBarBounds,
+ mTaskStackBounds);
if (mConfig.isLandscape && mConfig.hasTransposedNavBar) {
mSystemInsets.set(0, mStatusBarHeight, mNavBarWidth, 0);
} else {
@@ -455,29 +526,13 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
algo.computeRects(mWindowRect.width(), mWindowRect.height(), taskStackBounds);
Rect taskViewSize = algo.getUntransformedTaskViewSize();
int taskBarHeight = res.getDimensionPixelSize(R.dimen.recents_task_bar_height);
- mHeaderBar = (TaskViewHeader) mInflater.inflate(R.layout.recents_task_view_header, null,
- false);
- mHeaderBar.measure(
- View.MeasureSpec.makeMeasureSpec(taskViewSize.width(), View.MeasureSpec.EXACTLY),
- View.MeasureSpec.makeMeasureSpec(taskBarHeight, View.MeasureSpec.EXACTLY));
- mHeaderBar.layout(0, 0, taskViewSize.width(), taskBarHeight);
- }
-
- /** Prepares the search bar app widget */
- void reloadSearchBarAppWidget(Context context, SystemServicesProxy ssp) {
- // Try and pre-emptively bind the search widget on startup to ensure that we
- // have the right thumbnail bounds to animate to.
- if (Constants.DebugFlags.App.EnableSearchLayout) {
- // If there is no id, then bind a new search app widget
- if (mConfig.searchBarAppWidgetId < 0) {
- AppWidgetHost host = new RecentsAppWidgetHost(context,
- Constants.Values.App.AppWidgetHostId);
- Pair<Integer, AppWidgetProviderInfo> widgetInfo = ssp.bindSearchAppWidget(host);
- if (widgetInfo != null) {
- // Save the app widget id into the settings
- mConfig.updateSearchBarAppWidgetId(context, widgetInfo.first);
- }
- }
+ synchronized (mHeaderBarLock) {
+ mHeaderBar = (TaskViewHeader) mInflater.inflate(R.layout.recents_task_view_header, null,
+ false);
+ mHeaderBar.measure(
+ View.MeasureSpec.makeMeasureSpec(taskViewSize.width(), View.MeasureSpec.EXACTLY),
+ View.MeasureSpec.makeMeasureSpec(taskBarHeight, View.MeasureSpec.EXACTLY));
+ mHeaderBar.layout(0, 0, taskViewSize.width(), taskBarHeight);
}
}
@@ -493,7 +548,7 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
// If Recents is the front most activity, then we should just communicate with it directly
// to launch the first task or dismiss itself
ActivityManager.RunningTaskInfo topTask = mSystemServicesProxy.getTopMostTask();
- AtomicBoolean isTopTaskHome = new AtomicBoolean(true);
+ MutableBoolean isTopTaskHome = new MutableBoolean(true);
if (topTask != null && mSystemServicesProxy.isRecentsTopMost(topTask, isTopTaskHome)) {
// Notify recents to toggle itself
Intent intent = createLocalBroadcastIntent(mContext, ACTION_TOGGLE_RECENTS_ACTIVITY);
@@ -502,7 +557,7 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
return;
} else {
// Otherwise, start the recents activity
- startRecentsActivity(topTask, isTopTaskHome.get());
+ startRecentsActivity(topTask, isTopTaskHome.value);
}
}
@@ -510,9 +565,9 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
void startRecentsActivity() {
// Check if the top task is in the home stack, and start the recents activity
ActivityManager.RunningTaskInfo topTask = mSystemServicesProxy.getTopMostTask();
- AtomicBoolean isTopTaskHome = new AtomicBoolean(true);
+ MutableBoolean isTopTaskHome = new MutableBoolean(true);
if (topTask == null || !mSystemServicesProxy.isRecentsTopMost(topTask, isTopTaskHome)) {
- startRecentsActivity(topTask, isTopTaskHome.get());
+ startRecentsActivity(topTask, isTopTaskHome.value);
}
}
@@ -549,26 +604,24 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
*/
ActivityOptions getThumbnailTransitionActivityOptions(ActivityManager.RunningTaskInfo topTask,
TaskStack stack, TaskStackView stackView) {
+
// Update the destination rect
Task toTask = new Task();
TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
topTask.id, toTask);
- if (toTransform != null && toTask.key != null) {
- Rect toTaskRect = toTransform.rect;
- int toHeaderWidth = (int) (mHeaderBar.getMeasuredWidth() * toTransform.scale);
- int toHeaderHeight = (int) (mHeaderBar.getMeasuredHeight() * toTransform.scale);
- Bitmap thumbnail = Bitmap.createBitmap(toHeaderWidth, toHeaderHeight,
- Bitmap.Config.ARGB_8888);
- if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) {
- thumbnail.eraseColor(0xFFff0000);
- } else {
- Canvas c = new Canvas(thumbnail);
- c.scale(toTransform.scale, toTransform.scale);
- mHeaderBar.rebindToTask(toTask);
- mHeaderBar.draw(c);
- c.setBitmap(null);
- }
-
+ Rect toTaskRect = toTransform.rect;
+ Bitmap thumbnail;
+ if (mThumbnailTransitionBitmapCacheKey != null
+ && mThumbnailTransitionBitmapCacheKey.key != null
+ && mThumbnailTransitionBitmapCacheKey.key.equals(toTask.key)) {
+ thumbnail = mThumbnailTransitionBitmapCache;
+ mThumbnailTransitionBitmapCacheKey = null;
+ mThumbnailTransitionBitmapCache = null;
+ } else {
+ preloadIcon(topTask);
+ thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform);
+ }
+ if (thumbnail != null) {
mStartAnimationTriggered = false;
return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
thumbnail, toTaskRect.left, toTaskRect.top, toTaskRect.width(),
@@ -579,6 +632,72 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
return getUnknownTransitionActivityOptions();
}
+ /**
+ * Preloads the icon of a task.
+ */
+ void preloadIcon(ActivityManager.RunningTaskInfo task) {
+
+ // Ensure that we load the running task's icon
+ RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
+ launchOpts.runningTaskId = task.id;
+ launchOpts.loadThumbnails = false;
+ launchOpts.onlyLoadForCache = true;
+ RecentsTaskLoader.getInstance().loadTasks(mContext, sInstanceLoadPlan, launchOpts);
+ }
+
+ /**
+ * Caches the header thumbnail used for a window animation asynchronously into
+ * {@link #mThumbnailTransitionBitmapCache}.
+ */
+ void preCacheThumbnailTransitionBitmapAsync(ActivityManager.RunningTaskInfo topTask,
+ TaskStack stack, TaskStackView stackView, boolean isTopTaskHome) {
+ preloadIcon(topTask);
+
+ // Update the destination rect
+ mDummyStackView.updateMinMaxScrollForStack(stack, mTriggeredFromAltTab, isTopTaskHome);
+ final Task toTask = new Task();
+ final TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
+ topTask.id, toTask);
+ new AsyncTask<Void, Void, Bitmap>() {
+ @Override
+ protected Bitmap doInBackground(Void... params) {
+ return drawThumbnailTransitionBitmap(toTask, toTransform);
+ }
+
+ @Override
+ protected void onPostExecute(Bitmap bitmap) {
+ mThumbnailTransitionBitmapCache = bitmap;
+ mThumbnailTransitionBitmapCacheKey = toTask;
+ }
+ }.execute();
+ }
+
+ /**
+ * Draws the header of a task used for the window animation into a bitmap.
+ */
+ Bitmap drawThumbnailTransitionBitmap(Task toTask, TaskViewTransform toTransform) {
+ if (toTransform != null && toTask.key != null) {
+ Bitmap thumbnail;
+ synchronized (mHeaderBarLock) {
+ int toHeaderWidth = (int) (mHeaderBar.getMeasuredWidth() * toTransform.scale);
+ int toHeaderHeight = (int) (mHeaderBar.getMeasuredHeight() * toTransform.scale);
+ thumbnail = Bitmap.createBitmap(toHeaderWidth, toHeaderHeight,
+ Bitmap.Config.ARGB_8888);
+ if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) {
+ thumbnail.eraseColor(0xFFff0000);
+ } else {
+ Canvas c = new Canvas(thumbnail);
+ c.scale(toTransform.scale, toTransform.scale);
+ mHeaderBar.rebindToTask(toTask);
+ mHeaderBar.draw(c);
+ c.setBitmap(null);
+ }
+ }
+ return thumbnail.createAshmemBitmap();
+ }
+ return null;
+ }
+
/** Returns the transition rect for the given task id. */
TaskViewTransform getThumbnailTransitionTransform(TaskStack stack, TaskStackView stackView,
int runningTaskId, Task runningTaskOut) {
@@ -600,6 +719,7 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
if (task == null) {
// If no task is specified or we can not find the task just use the front most one
task = tasks.get(tasks.size() - 1);
+ runningTaskOut.copyFrom(task);
}
// Get the transform for the running task
@@ -611,14 +731,34 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
/** Starts the recents activity */
void startRecentsActivity(ActivityManager.RunningTaskInfo topTask, boolean isTopTaskHome) {
+ RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
+ RecentsConfiguration.reinitialize(mContext, mSystemServicesProxy);
+
if (sInstanceLoadPlan == null) {
// Create a new load plan if onPreloadRecents() was never triggered
- RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
sInstanceLoadPlan = loader.createLoadPlan(mContext);
}
- RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
- loader.preloadTasks(sInstanceLoadPlan, isTopTaskHome);
- TaskStack stack = sInstanceLoadPlan.getTaskStack();
+
+ // Temporarily skip the transition (use a dummy fade) if multi stack is enabled.
+ // For multi-stack we need to figure out where each of the tasks are going.
+ if (mConfig.multiStackEnabled) {
+ loader.preloadTasks(sInstanceLoadPlan, true);
+ ArrayList<TaskStack> stacks = sInstanceLoadPlan.getAllTaskStacks();
+ TaskStack stack = stacks.get(0);
+ mDummyStackView.updateMinMaxScrollForStack(stack, mTriggeredFromAltTab, true);
+ TaskStackViewLayoutAlgorithm.VisibilityReport stackVr =
+ mDummyStackView.computeStackVisibilityReport();
+ ActivityOptions opts = getUnknownTransitionActivityOptions();
+ startAlternateRecentsActivity(topTask, opts, true /* fromHome */,
+ false /* fromSearchHome */, false /* fromThumbnail */, stackVr);
+ return;
+ }
+
+ if (!sInstanceLoadPlan.hasTasks()) {
+ loader.preloadTasks(sInstanceLoadPlan, isTopTaskHome);
+ }
+ ArrayList<TaskStack> stacks = sInstanceLoadPlan.getAllTaskStacks();
+ TaskStack stack = stacks.get(0);
// Prepare the dummy stack for the transition
mDummyStackView.updateMinMaxScrollForStack(stack, mTriggeredFromAltTab, isTopTaskHome);
@@ -628,12 +768,6 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
boolean useThumbnailTransition = (topTask != null) && !isTopTaskHome && hasRecentTasks;
if (useThumbnailTransition) {
- // Ensure that we load the running task's icon
- RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
- launchOpts.runningTaskId = topTask.id;
- launchOpts.loadThumbnails = false;
- launchOpts.onlyLoadForCache = true;
- loader.loadTasks(mContext, sInstanceLoadPlan, launchOpts);
// Try starting with a thumbnail transition
ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask, stack,
@@ -651,27 +785,13 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
// If there is no thumbnail transition, but is launching from home into recents, then
// use a quick home transition and do the animation from home
if (hasRecentTasks) {
- // Get the home activity info
String homeActivityPackage = mSystemServicesProxy.getHomeActivityPackageName();
- // Get the search widget info
- AppWidgetProviderInfo searchWidget = null;
- String searchWidgetPackage = null;
- if (mConfig.hasSearchBarAppWidget()) {
- searchWidget = mSystemServicesProxy.getAppWidgetInfo(
- mConfig.searchBarAppWidgetId);
- } else {
- searchWidget = mSystemServicesProxy.resolveSearchAppWidget();
- }
- if (searchWidget != null && searchWidget.provider != null) {
- searchWidgetPackage = searchWidget.provider.getPackageName();
- }
- // Determine whether we are coming from a search owned home activity
- boolean fromSearchHome = false;
- if (homeActivityPackage != null && searchWidgetPackage != null &&
- homeActivityPackage.equals(searchWidgetPackage)) {
- fromSearchHome = true;
- }
+ String searchWidgetPackage =
+ Prefs.getString(mContext, Prefs.Key.SEARCH_APP_WIDGET_PACKAGE, null);
+ // Determine whether we are coming from a search owned home activity
+ boolean fromSearchHome = (homeActivityPackage != null) &&
+ homeActivityPackage.equals(searchWidgetPackage);
ActivityOptions opts = getHomeTransitionActivityOptions(fromSearchHome);
startAlternateRecentsActivity(topTask, opts, true /* fromHome */, fromSearchHome,
false /* fromThumbnail */, stackVr);
@@ -714,7 +834,8 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
}
/** Sets the RecentsComponent callbacks. */
- public void setRecentsComponentCallback(RecentsComponent.Callbacks cb) {
+ @Override
+ public void setCallback(RecentsComponent.Callbacks cb) {
sRecentsComponentCallbacks = cb;
}
@@ -737,6 +858,27 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
}
}
+ /** Notifies the status bar to trigger screen pinning. */
+ @ProxyFromAnyToPrimaryUser
+ public static void startScreenPinning(Context context, SystemServicesProxy ssp) {
+ if (ssp.isForegroundUserOwner()) {
+ onStartScreenPinning(context);
+ } else {
+ Intent intent = createLocalBroadcastIntent(context,
+ ACTION_PROXY_SCREEN_PINNING_REQUEST_TO_OWNER);
+ context.sendBroadcastAsUser(intent, UserHandle.OWNER);
+ }
+ }
+ static void onStartScreenPinning(Context context) {
+ // For the primary user, the context for the SystemUI component is the SystemUIApplication
+ SystemUIApplication app = (SystemUIApplication)
+ getInstanceAndStartIfNeeded(context).mContext;
+ PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class);
+ if (statusBar != null) {
+ statusBar.showScreenPinningRequest(false);
+ }
+ }
+
/**
* Returns the preloaded load plan and invalidates it.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 7422e36..bf15c68 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -19,42 +19,36 @@ package com.android.systemui.recents;
import android.app.Activity;
import android.app.ActivityOptions;
import android.app.SearchManager;
-import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.SystemClock;
import android.os.UserHandle;
-import android.util.Pair;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewStub;
import android.widget.Toast;
+import com.android.systemui.Prefs;
import com.android.systemui.R;
+import com.android.systemui.recents.misc.Console;
import com.android.systemui.recents.misc.DebugTrigger;
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.RecentsTaskLoadPlan;
import com.android.systemui.recents.model.RecentsTaskLoader;
-import com.android.systemui.recents.model.SpaceNode;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
import com.android.systemui.recents.views.DebugOverlayView;
import com.android.systemui.recents.views.RecentsView;
import com.android.systemui.recents.views.SystemBarScrimViews;
import com.android.systemui.recents.views.ViewAnimation;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
-import com.android.systemui.SystemUIApplication;
import java.lang.ref.WeakReference;
-import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
/**
@@ -75,15 +69,19 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
View mEmptyView;
DebugOverlayView mDebugOverlay;
+ // Resize task debug
+ RecentsResizeTaskDialog mResizeTaskDebugDialog;
+
// Search AppWidget
+ AppWidgetProviderInfo mSearchWidgetInfo;
RecentsAppWidgetHost mAppWidgetHost;
- AppWidgetProviderInfo mSearchAppWidgetInfo;
- AppWidgetHostView mSearchAppWidgetHostView;
+ RecentsAppWidgetHostView mSearchWidgetHostView;
// Runnables to finish the Recents activity
FinishRecentsRunnable mFinishLaunchHomeRunnable;
- private PhoneStatusBar mStatusBar;
+ // Runnable to be executed after we paused ourselves
+ Runnable mAfterPauseRunnable;
/**
* A common Runnable to finish Recents either by calling finish() (with a custom animation) or
@@ -109,10 +107,15 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
public void run() {
// Finish Recents
if (mLaunchIntent != null) {
- if (mLaunchOpts != null) {
- startActivityAsUser(mLaunchIntent, mLaunchOpts.toBundle(), UserHandle.CURRENT);
- } else {
- startActivityAsUser(mLaunchIntent, UserHandle.CURRENT);
+ try {
+ if (mLaunchOpts != null) {
+ startActivityAsUser(mLaunchIntent, mLaunchOpts.toBundle(), UserHandle.CURRENT);
+ } else {
+ startActivityAsUser(mLaunchIntent, UserHandle.CURRENT);
+ }
+ } catch (Exception e) {
+ Console.logError(RecentsActivity.this,
+ getString(R.string.recents_launch_error_message, "Home"));
}
} else {
finish();
@@ -129,20 +132,20 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
- if (action.equals(AlternateRecentsComponent.ACTION_HIDE_RECENTS_ACTIVITY)) {
- if (intent.getBooleanExtra(AlternateRecentsComponent.EXTRA_TRIGGERED_FROM_ALT_TAB, false)) {
+ if (action.equals(Recents.ACTION_HIDE_RECENTS_ACTIVITY)) {
+ if (intent.getBooleanExtra(Recents.EXTRA_TRIGGERED_FROM_ALT_TAB, false)) {
// If we are hiding from releasing Alt-Tab, dismiss Recents to the focused app
dismissRecentsToFocusedTaskOrHome(false);
- } else if (intent.getBooleanExtra(AlternateRecentsComponent.EXTRA_TRIGGERED_FROM_HOME_KEY, false)) {
+ } else if (intent.getBooleanExtra(Recents.EXTRA_TRIGGERED_FROM_HOME_KEY, false)) {
// Otherwise, dismiss Recents to Home
- dismissRecentsToHome(true);
+ dismissRecentsToHomeRaw(true);
} else {
- // Do nothing, another activity is being launched on top of Recents
+ // Do nothing
}
- } else if (action.equals(AlternateRecentsComponent.ACTION_TOGGLE_RECENTS_ACTIVITY)) {
+ } else if (action.equals(Recents.ACTION_TOGGLE_RECENTS_ACTIVITY)) {
// If we are toggling Recents, then first unfilter any filtered stacks first
dismissRecentsToFocusedTaskOrHome(true);
- } else if (action.equals(AlternateRecentsComponent.ACTION_START_ENTER_ANIMATION)) {
+ } else if (action.equals(Recents.ACTION_START_ENTER_ANIMATION)) {
// Trigger the enter animation
onEnterAnimationTriggered();
// Notify the fallback receiver that we have successfully got the broadcast
@@ -163,8 +166,10 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
// When the screen turns off, dismiss Recents to Home
dismissRecentsToHome(false);
} else if (action.equals(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED)) {
- // When the search activity changes, update the Search widget
- refreshSearchWidget();
+ // When the search activity changes, update the search widget view
+ SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
+ mSearchWidgetInfo = ssp.getOrBindSearchAppWidget(context, mAppWidgetHost);
+ refreshSearchWidgetView();
}
}
};
@@ -180,17 +185,17 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
});
/** Updates the set of recent tasks */
- void updateRecentsTasks(Intent launchIntent) {
+ void updateRecentsTasks() {
// If AlternateRecentsComponent has preloaded a load plan, then use that to prevent
// reconstructing the task stack
RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
- RecentsTaskLoadPlan plan = AlternateRecentsComponent.consumeInstanceLoadPlan();
+ RecentsTaskLoadPlan plan = Recents.consumeInstanceLoadPlan();
if (plan == null) {
plan = loader.createLoadPlan(this);
}
// Start loading tasks according to the load plan
- if (plan.getTaskStack() == null) {
+ if (!plan.hasTasks()) {
loader.preloadTasks(plan, mConfig.launchedFromHome);
}
RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options();
@@ -199,13 +204,11 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
loadOpts.numVisibleTaskThumbnails = mConfig.launchedNumVisibleThumbnails;
loader.loadTasks(this, plan, loadOpts);
- SpaceNode root = plan.getSpaceNode();
- ArrayList<TaskStack> stacks = root.getStacks();
- boolean hasTasks = root.hasTasks();
- if (hasTasks) {
+ ArrayList<TaskStack> stacks = plan.getAllTaskStacks();
+ mConfig.launchedWithNoRecentTasks = !plan.hasTasks();
+ if (!mConfig.launchedWithNoRecentTasks) {
mRecentsView.setTaskStacks(stacks);
}
- mConfig.launchedWithNoRecentTasks = !hasTasks;
// Create the home intent runnable
Intent homeIntent = new Intent(Intent.ACTION_MAIN, null);
@@ -247,10 +250,10 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
if (mEmptyView != null) {
mEmptyView.setVisibility(View.GONE);
}
- if (mRecentsView.hasSearchBar()) {
+ if (mRecentsView.hasValidSearchBar()) {
mRecentsView.setSearchBarVisibility(View.VISIBLE);
} else {
- addSearchBarAppWidgetView();
+ refreshSearchWidgetView();
}
}
@@ -258,60 +261,6 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
mScrimViews.prepareEnterRecentsAnimation();
}
- /** Attempts to allocate and bind the search bar app widget */
- void bindSearchBarAppWidget() {
- if (Constants.DebugFlags.App.EnableSearchLayout) {
- SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
-
- // Reset the host view and widget info
- mSearchAppWidgetHostView = null;
- mSearchAppWidgetInfo = null;
-
- // Try and load the app widget id from the settings
- int appWidgetId = mConfig.searchBarAppWidgetId;
- if (appWidgetId >= 0) {
- mSearchAppWidgetInfo = ssp.getAppWidgetInfo(appWidgetId);
- if (mSearchAppWidgetInfo == null) {
- // If there is no actual widget associated with that id, then delete it and
- // prepare to bind another app widget in its place
- ssp.unbindSearchAppWidget(mAppWidgetHost, appWidgetId);
- appWidgetId = -1;
- }
- }
-
- // If there is no id, then bind a new search app widget
- if (appWidgetId < 0) {
- Pair<Integer, AppWidgetProviderInfo> widgetInfo =
- ssp.bindSearchAppWidget(mAppWidgetHost);
- if (widgetInfo != null) {
- // Save the app widget id into the settings
- mConfig.updateSearchBarAppWidgetId(this, widgetInfo.first);
- mSearchAppWidgetInfo = widgetInfo.second;
- }
- }
- }
- }
-
- /** Creates the search bar app widget view */
- void addSearchBarAppWidgetView() {
- if (Constants.DebugFlags.App.EnableSearchLayout) {
- int appWidgetId = mConfig.searchBarAppWidgetId;
- if (appWidgetId >= 0) {
- mSearchAppWidgetHostView = mAppWidgetHost.createView(this, appWidgetId,
- mSearchAppWidgetInfo);
- Bundle opts = new Bundle();
- opts.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
- AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX);
- mSearchAppWidgetHostView.updateAppWidgetOptions(opts);
- // Set the padding to 0 for this search widget
- mSearchAppWidgetHostView.setPadding(0, 0, 0, 0);
- mRecentsView.setSearchBar(mSearchAppWidgetHostView);
- } else {
- mRecentsView.setSearchBar(null);
- }
- }
- }
-
/** Dismisses recents if we are already visible and the intent is to toggle the recents view */
boolean dismissRecentsToFocusedTaskOrHome(boolean checkFilteredStackState) {
SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
@@ -347,6 +296,12 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
}
}
+ /** Dismisses Recents directly to Home without transition animation. */
+ void dismissRecentsToHomeWithoutTransitionAnimation() {
+ finish();
+ overridePendingTransition(0, 0);
+ }
+
/** Dismisses Recents directly to Home if we currently aren't transitioning. */
boolean dismissRecentsToHome(boolean animated) {
SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
@@ -362,12 +317,11 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- // For the non-primary user, ensure that the SystemSericesProxy is initialized
+ // For the non-primary user, ensure that the SystemServicesProxy and configuration is
+ // initialized
RecentsTaskLoader.initialize(this);
-
- // Initialize the loader and the configuration
- mConfig = RecentsConfiguration.reinitialize(this,
- RecentsTaskLoader.getInstance().getSystemServicesProxy());
+ SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
+ mConfig = RecentsConfiguration.reinitialize(this, ssp);
// Initialize the widget host (the host id is static and does not change)
mAppWidgetHost = new RecentsAppWidgetHost(this, Constants.Values.App.AppWidgetHostId);
@@ -382,27 +336,16 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
mEmptyViewStub = (ViewStub) findViewById(R.id.empty_view_stub);
mDebugOverlayStub = (ViewStub) findViewById(R.id.debug_overlay_stub);
mScrimViews = new SystemBarScrimViews(this, mConfig);
- mStatusBar = ((SystemUIApplication) getApplication())
- .getComponent(PhoneStatusBar.class);
inflateDebugOverlay();
// Bind the search app widget when we first start up
- bindSearchBarAppWidget();
+ mSearchWidgetInfo = ssp.getOrBindSearchAppWidget(this, mAppWidgetHost);
// 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(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
registerReceiver(mSystemBroadcastReceiver, filter);
-
- // Private API calls to make the shadows look better
- try {
- Utilities.setShadowProperty("ambientRatio", String.valueOf(1.5f));
- } catch (IllegalAccessException e) {
- e.printStackTrace();
- } catch (InvocationTargetException e) {
- e.printStackTrace();
- }
}
/** Inflates the debug overlay if debug mode is enabled. */
@@ -422,9 +365,6 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
super.onNewIntent(intent);
setIntent(intent);
- // Reinitialize the configuration
- RecentsConfiguration.reinitialize(this, RecentsTaskLoader.getInstance().getSystemServicesProxy());
-
// Clear any debug rects
if (mDebugOverlay != null) {
mDebugOverlay.clear();
@@ -436,20 +376,39 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
super.onStart();
RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
SystemServicesProxy ssp = loader.getSystemServicesProxy();
- AlternateRecentsComponent.notifyVisibilityChanged(this, ssp, true);
+ Recents.notifyVisibilityChanged(this, ssp, true);
// Register the broadcast receiver to handle messages from our service
IntentFilter filter = new IntentFilter();
- filter.addAction(AlternateRecentsComponent.ACTION_HIDE_RECENTS_ACTIVITY);
- filter.addAction(AlternateRecentsComponent.ACTION_TOGGLE_RECENTS_ACTIVITY);
- filter.addAction(AlternateRecentsComponent.ACTION_START_ENTER_ANIMATION);
+ filter.addAction(Recents.ACTION_HIDE_RECENTS_ACTIVITY);
+ filter.addAction(Recents.ACTION_TOGGLE_RECENTS_ACTIVITY);
+ filter.addAction(Recents.ACTION_START_ENTER_ANIMATION);
registerReceiver(mServiceBroadcastReceiver, filter);
// Register any broadcast receivers for the task loader
loader.registerReceivers(this, mRecentsView);
// Update the recent tasks
- updateRecentsTasks(getIntent());
+ updateRecentsTasks();
+
+ // If this is a new instance from a configuration change, then we have to manually trigger
+ // the enter animation state
+ if (mConfig.launchedHasConfigurationChanged) {
+ onEnterAnimationTriggered();
+ }
+
+ if (!mConfig.launchedHasConfigurationChanged) {
+ mRecentsView.disableLayersForOneFrame();
+ }
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ if (mAfterPauseRunnable != null) {
+ mRecentsView.post(mAfterPauseRunnable);
+ mAfterPauseRunnable = null;
+ }
}
@Override
@@ -457,7 +416,7 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
super.onStop();
RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
SystemServicesProxy ssp = loader.getSystemServicesProxy();
- AlternateRecentsComponent.notifyVisibilityChanged(this, ssp, false);
+ Recents.notifyVisibilityChanged(this, ssp, false);
// Notify the views that we are no longer visible
mRecentsView.onRecentsHidden();
@@ -485,7 +444,8 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
ReferenceCountedTrigger t = new ReferenceCountedTrigger(this, null, null, null);
ViewAnimation.TaskViewEnterContext ctx = new ViewAnimation.TaskViewEnterContext(t);
mRecentsView.startEnterRecentsAnimation(ctx);
- if (mConfig.searchBarAppWidgetId >= 0) {
+
+ if (mSearchWidgetInfo != null) {
final WeakReference<RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks> cbRef =
new WeakReference<RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks>(
RecentsActivity.this);
@@ -565,10 +525,9 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
/** Called when debug mode is triggered */
public void onDebugModeTriggered() {
if (mConfig.developerOptionsEnabled) {
- SharedPreferences settings = getSharedPreferences(getPackageName(), 0);
- if (settings.getBoolean(Constants.Values.App.Key_DebugModeEnabled, false)) {
+ if (Prefs.getBoolean(this, Prefs.Key.DEBUG_MODE_ENABLED, false /* boolean */)) {
// Disable the debug mode
- settings.edit().remove(Constants.Values.App.Key_DebugModeEnabled).apply();
+ Prefs.remove(this, Prefs.Key.DEBUG_MODE_ENABLED);
mConfig.debugModeEnabled = false;
inflateDebugOverlay();
if (mDebugOverlay != null) {
@@ -576,7 +535,7 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
}
} else {
// Enable the debug mode
- settings.edit().putBoolean(Constants.Values.App.Key_DebugModeEnabled, true).apply();
+ Prefs.putBoolean(this, Prefs.Key.DEBUG_MODE_ENABLED, true);
mConfig.debugModeEnabled = true;
inflateDebugOverlay();
if (mDebugOverlay != null) {
@@ -589,6 +548,21 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
}
}
+
+ /**** RecentsResizeTaskDialog ****/
+
+ private RecentsResizeTaskDialog getResizeTaskDebugDialog() {
+ if (mResizeTaskDebugDialog == null) {
+ mResizeTaskDebugDialog = new RecentsResizeTaskDialog(getFragmentManager(), this);
+ }
+ return mResizeTaskDebugDialog;
+ }
+
+ @Override
+ public void onTaskResize(Task t) {
+ getResizeTaskDebugDialog().showResizeTaskDialog(t, mRecentsView);
+ }
+
/**** RecentsView.RecentsViewCallbacks Implementation ****/
@Override
@@ -614,17 +588,35 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
@Override
public void onScreenPinningRequest() {
- if (mStatusBar != null) {
- mStatusBar.showScreenPinningRequest(false);
- }
+ RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
+ SystemServicesProxy ssp = loader.getSystemServicesProxy();
+ Recents.startScreenPinning(this, ssp);
+ }
+
+ @Override
+ public void runAfterPause(Runnable r) {
+ mAfterPauseRunnable = r;
}
/**** RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks Implementation ****/
@Override
- public void refreshSearchWidget() {
- bindSearchBarAppWidget();
- addSearchBarAppWidgetView();
+ public void refreshSearchWidgetView() {
+ if (mSearchWidgetInfo != null) {
+ SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
+ int searchWidgetId = ssp.getSearchAppWidgetId(this);
+ mSearchWidgetHostView = (RecentsAppWidgetHostView) mAppWidgetHost.createView(
+ this, searchWidgetId, mSearchWidgetInfo);
+ Bundle opts = new Bundle();
+ opts.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
+ AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX);
+ mSearchWidgetHostView.updateAppWidgetOptions(opts);
+ // Set the padding to 0 for this search widget
+ mSearchWidgetHostView.setPadding(0, 0, 0, 0);
+ mRecentsView.setSearchBar(mSearchWidgetHostView);
+ } else {
+ mRecentsView.setSearchBar(null);
+ }
}
/**** DebugOverlayView.DebugOverlayViewCallbacks ****/
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java
index 5bae37a..0102332 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java
@@ -17,28 +17,23 @@
package com.android.systemui.recents;
import android.appwidget.AppWidgetHost;
+import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.model.RecentsTaskLoader;
/** Our special app widget host for the Search widget */
public class RecentsAppWidgetHost extends AppWidgetHost {
/* Callbacks to notify when an app package changes */
interface RecentsAppWidgetHostCallbacks {
- public void refreshSearchWidget();
+ void refreshSearchWidgetView();
}
- Context mContext;
RecentsAppWidgetHostCallbacks mCb;
- RecentsConfiguration mConfig;
boolean mIsListening;
public RecentsAppWidgetHost(Context context, int hostId) {
super(context, hostId);
- mContext = context;
- mConfig = RecentsConfiguration.getInstance();
}
public void startListening(RecentsAppWidgetHostCallbacks cb) {
@@ -56,21 +51,23 @@ public class RecentsAppWidgetHost extends AppWidgetHost {
}
// Ensure that we release any references to the callbacks
mCb = null;
- mContext = null;
mIsListening = false;
}
@Override
- protected void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidgetInfo) {
- if (mCb == null) return;
+ protected AppWidgetHostView onCreateView(Context context, int appWidgetId,
+ AppWidgetProviderInfo appWidget) {
+ return new RecentsAppWidgetHostView(context);
+ }
- SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
- if (appWidgetId > -1 && appWidgetId == mConfig.searchBarAppWidgetId) {
- // The search provider may have changed, so just delete the old widget and bind it again
- ssp.unbindSearchAppWidget(this, appWidgetId);
- // Update the search widget
- mConfig.updateSearchBarAppWidgetId(mContext, -1);
- mCb.refreshSearchWidget();
+ /**
+ * Note: this is only called for packages that have updated, not removed.
+ */
+ @Override
+ protected void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidgetInfo) {
+ super.onProviderChanged(appWidgetId, appWidgetInfo);
+ if (mIsListening && mCb != null) {
+ mCb.refreshSearchWidgetView();
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHostView.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHostView.java
new file mode 100644
index 0000000..672d602
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHostView.java
@@ -0,0 +1,68 @@
+/*
+ * 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;
+
+import android.appwidget.AppWidgetHostView;
+import android.content.Context;
+import android.view.View;
+import android.widget.RemoteViews;
+
+public class RecentsAppWidgetHostView extends AppWidgetHostView {
+
+ private Context mContext;
+ private int mPreviousOrientation;
+
+ public RecentsAppWidgetHostView(Context context) {
+ super(context);
+ mContext = context;
+ }
+
+ @Override
+ public void updateAppWidget(RemoteViews remoteViews) {
+ // Store the orientation in which the widget was inflated
+ updateLastInflationOrientation();
+ super.updateAppWidget(remoteViews);
+ }
+
+ @Override
+ protected View getErrorView() {
+ // Just return an empty view as the error view when failing to inflate the Recents search
+ // bar widget (this is mainly to catch the case where we try and inflate the widget view
+ // while the search provider is updating)
+ return new View(mContext);
+ }
+
+ /**
+ * Updates the last orientation that this widget was inflated.
+ */
+ private void updateLastInflationOrientation() {
+ mPreviousOrientation = mContext.getResources().getConfiguration().orientation;
+ }
+
+ /**
+ * @return whether the search widget was updated while Recents was in a different orientation
+ * in the background.
+ */
+ public boolean isReinflateRequired() {
+ // Re-inflate is required if the orientation has changed since last inflated.
+ int orientation = mContext.getResources().getConfiguration().orientation;
+ if (mPreviousOrientation != orientation) {
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index 52e7e7f..dfe7e96 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -18,15 +18,15 @@ package com.android.systemui.recents;
import android.app.ActivityManager;
import android.content.Context;
-import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
import android.provider.Settings;
import android.util.DisplayMetrics;
-import android.util.TypedValue;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
+
+import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.recents.misc.Console;
import com.android.systemui.recents.misc.SystemServicesProxy;
@@ -50,9 +50,6 @@ public class RecentsConfiguration {
// Disable all thumbnail loading.
public static final int SVELTE_DISABLE_LOADING = 3;
- /** Animations */
- public float animationPxMovementPerSecond;
-
/** Interpolators */
public Interpolator fastOutSlowInInterpolator;
public Interpolator fastOutLinearInInterpolator;
@@ -76,13 +73,13 @@ public class RecentsConfiguration {
public int maxNumTasksToLoad;
/** Search bar */
- int searchBarAppWidgetId = -1;
public int searchBarSpaceHeightPx;
/** Task stack */
public int taskStackScrollDuration;
public int taskStackMaxDim;
public int taskStackTopPaddingPx;
+ public int dismissAllButtonSizePx;
public float taskStackWidthPaddingPct;
public float taskStackOverscrollPct;
@@ -137,6 +134,7 @@ public class RecentsConfiguration {
public boolean fakeShadows;
/** Dev options and global settings */
+ public boolean multiStackEnabled;
public boolean lockToAppEnabled;
public boolean developerOptionsEnabled;
public boolean debugModeEnabled;
@@ -179,12 +177,12 @@ public class RecentsConfiguration {
/** Updates the state, given the specified context */
void update(Context context) {
- SharedPreferences settings = context.getSharedPreferences(context.getPackageName(), 0);
Resources res = context.getResources();
DisplayMetrics dm = res.getDisplayMetrics();
// Debug mode
- debugModeEnabled = settings.getBoolean(Constants.Values.App.Key_DebugModeEnabled, false);
+ debugModeEnabled = Prefs.getBoolean(context, Prefs.Key.DEBUG_MODE_ENABLED,
+ false /* defaultValue */);
if (debugModeEnabled) {
Console.Enabled = true;
}
@@ -197,10 +195,6 @@ public class RecentsConfiguration {
// Insets
displayRect.set(0, 0, dm.widthPixels, dm.heightPixels);
- // Animations
- animationPxMovementPerSecond =
- res.getDimensionPixelSize(R.dimen.recents_animation_movement_in_dps_per_second);
-
// Filtering
filteringCurrentViewsAnimDuration =
res.getInteger(R.integer.recents_filter_animate_current_views_duration);
@@ -212,19 +206,15 @@ public class RecentsConfiguration {
// Search Bar
searchBarSpaceHeightPx = res.getDimensionPixelSize(R.dimen.recents_search_bar_space_height);
- searchBarAppWidgetId = settings.getInt(Constants.Values.App.Key_SearchAppWidgetId, -1);
// Task stack
taskStackScrollDuration =
res.getInteger(R.integer.recents_animate_task_stack_scroll_duration);
- TypedValue widthPaddingPctValue = new TypedValue();
- res.getValue(R.dimen.recents_stack_width_padding_percentage, widthPaddingPctValue, true);
- taskStackWidthPaddingPct = widthPaddingPctValue.getFloat();
- TypedValue stackOverscrollPctValue = new TypedValue();
- res.getValue(R.dimen.recents_stack_overscroll_percentage, stackOverscrollPctValue, true);
- taskStackOverscrollPct = stackOverscrollPctValue.getFloat();
+ taskStackWidthPaddingPct = res.getFloat(R.dimen.recents_stack_width_padding_percentage);
+ taskStackOverscrollPct = res.getFloat(R.dimen.recents_stack_overscroll_percentage);
taskStackMaxDim = res.getInteger(R.integer.recents_max_task_stack_view_dim);
taskStackTopPaddingPx = res.getDimensionPixelSize(R.dimen.recents_stack_top_padding);
+ dismissAllButtonSizePx = res.getDimensionPixelSize(R.dimen.recents_dismiss_all_button_size);
// Transition
transitionEnterFromAppDelay =
@@ -254,22 +244,16 @@ public class RecentsConfiguration {
taskViewTranslationZMaxPx = res.getDimensionPixelSize(R.dimen.recents_task_view_z_max);
taskViewAffiliateGroupEnterOffsetPx =
res.getDimensionPixelSize(R.dimen.recents_task_view_affiliate_group_enter_offset);
- TypedValue thumbnailAlphaValue = new TypedValue();
- res.getValue(R.dimen.recents_task_view_thumbnail_alpha, thumbnailAlphaValue, true);
- taskViewThumbnailAlpha = thumbnailAlphaValue.getFloat();
+ taskViewThumbnailAlpha = res.getFloat(R.dimen.recents_task_view_thumbnail_alpha);
// Task bar colors
- taskBarViewDefaultBackgroundColor =
- res.getColor(R.color.recents_task_bar_default_background_color);
- taskBarViewLightTextColor =
- res.getColor(R.color.recents_task_bar_light_text_color);
- taskBarViewDarkTextColor =
- res.getColor(R.color.recents_task_bar_dark_text_color);
- taskBarViewHighlightColor =
- res.getColor(R.color.recents_task_bar_highlight_color);
- TypedValue affMinAlphaPctValue = new TypedValue();
- res.getValue(R.dimen.recents_task_affiliation_color_min_alpha_percentage, affMinAlphaPctValue, true);
- taskBarViewAffiliationColorMinAlpha = affMinAlphaPctValue.getFloat();
+ taskBarViewDefaultBackgroundColor = context.getColor(
+ R.color.recents_task_bar_default_background_color);
+ taskBarViewLightTextColor = context.getColor(R.color.recents_task_bar_light_text_color);
+ taskBarViewDarkTextColor = context.getColor(R.color.recents_task_bar_dark_text_color);
+ taskBarViewHighlightColor = context.getColor(R.color.recents_task_bar_highlight_color);
+ taskBarViewAffiliationColorMinAlpha = res.getFloat(
+ R.dimen.recents_task_affiliation_color_min_alpha_percentage);
// Task bar size & animations
taskBarHeight = res.getDimensionPixelSize(R.dimen.recents_task_bar_height);
@@ -292,14 +276,6 @@ public class RecentsConfiguration {
systemInsets.set(insets);
}
- /** Updates the search bar app widget */
- public void updateSearchBarAppWidgetId(Context context, int appWidgetId) {
- searchBarAppWidgetId = appWidgetId;
- SharedPreferences settings = context.getSharedPreferences(context.getPackageName(), 0);
- settings.edit().putInt(Constants.Values.App.Key_SearchAppWidgetId,
- appWidgetId).apply();
- }
-
/** Updates the states that need to be re-read whenever we re-initialize. */
void updateOnReinitialize(Context context, SystemServicesProxy ssp) {
// Check if the developer options are enabled
@@ -307,6 +283,7 @@ public class RecentsConfiguration {
Settings.Global.DEVELOPMENT_SETTINGS_ENABLED) != 0;
lockToAppEnabled = ssp.getSystemSetting(context,
Settings.System.LOCK_TO_APP_ENABLED) != 0;
+ multiStackEnabled = "true".equals(ssp.getSystemProperty("persist.sys.debug.multi_window"));
}
/** Called when the configuration has changed, and we want to reset any configuration specific
@@ -318,11 +295,6 @@ public class RecentsConfiguration {
launchedHasConfigurationChanged = true;
}
- /** Returns whether the search bar app widget exists. */
- public boolean hasSearchBarAppWidget() {
- return searchBarAppWidgetId >= 0;
- }
-
/** Returns whether the status bar scrim should be animated when shown for the first time. */
public boolean shouldAnimateStatusBarScrim() {
return launchedFromHome;
@@ -344,19 +316,12 @@ public class RecentsConfiguration {
return !launchedWithNoRecentTasks && (!hasTransposedNavBar || !isLandscape);
}
- /** Returns whether the current layout is horizontal. */
- public boolean hasHorizontalLayout() {
- return isLandscape && hasTransposedSearchBar;
- }
-
/**
* Returns the task stack bounds in the current orientation. These bounds do not account for
* the system insets.
*/
- public void getTaskStackBounds(int windowWidth, int windowHeight, int topInset, int rightInset,
- Rect taskStackBounds) {
- Rect searchBarBounds = new Rect();
- getSearchBarBounds(windowWidth, windowHeight, topInset, searchBarBounds);
+ public void getAvailableTaskStackBounds(int windowWidth, int windowHeight, int topInset,
+ int rightInset, Rect searchBarBounds, Rect taskStackBounds) {
if (isLandscape && hasTransposedSearchBar) {
// In landscape, the search bar appears on the left, but we overlay it on top
taskStackBounds.set(0, topInset, windowWidth - rightInset, windowHeight);
@@ -371,13 +336,9 @@ public class RecentsConfiguration {
* the system insets.
*/
public void getSearchBarBounds(int windowWidth, int windowHeight, int topInset,
- Rect searchBarSpaceBounds) {
+ Rect searchBarSpaceBounds) {
// Return empty rects if search is not enabled
int searchBarSize = searchBarSpaceHeightPx;
- if (!Constants.DebugFlags.App.EnableSearchLayout || !hasSearchBarAppWidget()) {
- searchBarSize = 0;
- }
-
if (isLandscape && hasTransposedSearchBar) {
// In landscape, the search bar appears on the left
searchBarSpaceBounds.set(0, topInset, searchBarSize, windowHeight);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java
new file mode 100644
index 0000000..b701e0b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java
@@ -0,0 +1,240 @@
+/*
+ * 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;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.FragmentManager;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.util.Pair;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+import com.android.systemui.R;
+import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.recents.model.RecentsTaskLoader;
+import com.android.systemui.recents.model.Task;
+import com.android.systemui.recents.RecentsActivity;
+import com.android.systemui.recents.views.RecentsView;
+
+import java.util.ArrayList;
+
+/**
+ * A helper for the dialogs that show when task debugging is on.
+ */
+public class RecentsResizeTaskDialog extends DialogFragment {
+
+ static final String TAG = "RecentsResizeTaskDialog";
+
+ // The various window arrangements we can handle.
+ private static final int PLACE_LEFT = 1;
+ private static final int PLACE_RIGHT = 2;
+ private static final int PLACE_TOP = 3;
+ private static final int PLACE_BOTTOM = 4;
+ private static final int PLACE_TOP_LEFT = 5;
+ private static final int PLACE_TOP_RIGHT = 6;
+ private static final int PLACE_BOTTOM_LEFT = 7;
+ private static final int PLACE_BOTTOM_RIGHT = 8;
+ private static final int PLACE_FULL = 9;
+
+ // The button resource ID combined with the arrangement command.
+ private static final int[][] BUTTON_DEFINITIONS =
+ {{R.id.place_left, PLACE_LEFT},
+ {R.id.place_right, PLACE_RIGHT},
+ {R.id.place_top, PLACE_TOP},
+ {R.id.place_bottom, PLACE_BOTTOM},
+ {R.id.place_top_left, PLACE_TOP_LEFT},
+ {R.id.place_top_right, PLACE_TOP_RIGHT},
+ {R.id.place_bottom_left, PLACE_BOTTOM_LEFT},
+ {R.id.place_bottom_right, PLACE_BOTTOM_RIGHT},
+ {R.id.place_full, PLACE_FULL}};
+
+ // The task we want to resize.
+ private FragmentManager mFragmentManager;
+ private View mResizeTaskDialogContent;
+ private RecentsActivity mRecentsActivity;
+ private RecentsView mRecentsView;
+ private SystemServicesProxy mSsp;
+ private Rect[] mBounds = {new Rect(), new Rect(), new Rect(), new Rect()};
+ private Task[] mTasks = {null, null, null, null};
+
+ public RecentsResizeTaskDialog(FragmentManager mgr, RecentsActivity activity) {
+ mFragmentManager = mgr;
+ mRecentsActivity = activity;
+ mSsp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
+ }
+
+ /** Shows the resize-task dialog. */
+ void showResizeTaskDialog(Task mainTask, RecentsView rv) {
+ mTasks[0] = mainTask;
+ mRecentsView = rv;
+
+ show(mFragmentManager, TAG);
+ }
+
+ /** Creates a new resize-task dialog. */
+ private void createResizeTaskDialog(final Context context, LayoutInflater inflater,
+ AlertDialog.Builder builder) {
+ builder.setTitle(R.string.recents_caption_resize);
+ mResizeTaskDialogContent =
+ inflater.inflate(R.layout.recents_task_resize_dialog, null, false);
+
+ for (int i = 0; i < BUTTON_DEFINITIONS.length; i++) {
+ Button b = (Button)mResizeTaskDialogContent.findViewById(BUTTON_DEFINITIONS[i][0]);
+ if (b != null) {
+ final int action = BUTTON_DEFINITIONS[i][1];
+ b.setOnClickListener(
+ new View.OnClickListener() {
+ public void onClick(View v) {
+ placeTasks(action);
+ }
+ });
+ }
+ }
+
+ builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dismiss();
+ }
+ });
+
+ builder.setView(mResizeTaskDialogContent);
+ }
+
+ /** Helper function to place window(s) on the display according to an arrangement request. */
+ private void placeTasks(int arrangement) {
+ Rect rect = mSsp.getWindowRect();
+ for (int i = 0; i < mBounds.length; ++i) {
+ mBounds[i].set(rect);
+ if (i != 0) {
+ mTasks[i] = null;
+ }
+ }
+ int additionalTasks = 0;
+ switch (arrangement) {
+ case PLACE_LEFT:
+ mBounds[0].right = mBounds[0].centerX();
+ mBounds[1].left = mBounds[0].right;
+ additionalTasks = 1;
+ break;
+ case PLACE_RIGHT:
+ mBounds[1].right = mBounds[1].centerX();
+ mBounds[0].left = mBounds[1].right;
+ additionalTasks = 1;
+ break;
+ case PLACE_TOP:
+ mBounds[0].bottom = mBounds[0].centerY();
+ mBounds[1].top = mBounds[0].bottom;
+ additionalTasks = 1;
+ break;
+ case PLACE_BOTTOM:
+ mBounds[1].bottom = mBounds[1].centerY();
+ mBounds[0].top = mBounds[1].bottom;
+ additionalTasks = 1;
+ break;
+ case PLACE_TOP_LEFT: // TL, TR, BL, BR
+ mBounds[0].right = mBounds[0].centerX();
+ mBounds[0].bottom = mBounds[0].centerY();
+ mBounds[1].left = mBounds[0].right;
+ mBounds[1].bottom = mBounds[0].bottom;
+ mBounds[2].right = mBounds[0].right;
+ mBounds[2].top = mBounds[0].bottom;
+ mBounds[3].left = mBounds[0].right;
+ mBounds[3].top = mBounds[0].bottom;
+ additionalTasks = 3;
+ break;
+ case PLACE_TOP_RIGHT: // TR, TL, BR, BL
+ mBounds[0].left = mBounds[0].centerX();
+ mBounds[0].bottom = mBounds[0].centerY();
+ mBounds[1].right = mBounds[0].left;
+ mBounds[1].bottom = mBounds[0].bottom;
+ mBounds[2].left = mBounds[0].left;
+ mBounds[2].top = mBounds[0].bottom;
+ mBounds[3].right = mBounds[0].left;
+ mBounds[3].top = mBounds[0].bottom;
+ additionalTasks = 3;
+ break;
+ case PLACE_BOTTOM_LEFT: // BL, BR, TL, TR
+ mBounds[0].right = mBounds[0].centerX();
+ mBounds[0].top = mBounds[0].centerY();
+ mBounds[1].left = mBounds[0].right;
+ mBounds[1].top = mBounds[0].top;
+ mBounds[2].right = mBounds[0].right;
+ mBounds[2].bottom = mBounds[0].top;
+ mBounds[3].left = mBounds[0].right;
+ mBounds[3].bottom = mBounds[0].top;
+ additionalTasks = 3;
+ break;
+ case PLACE_BOTTOM_RIGHT: // BR, BL, TR, TL
+ mBounds[0].left = mBounds[0].centerX();
+ mBounds[0].top = mBounds[0].centerY();
+ mBounds[1].right = mBounds[0].left;
+ mBounds[1].top = mBounds[0].top;
+ mBounds[2].left = mBounds[0].left;
+ mBounds[2].bottom = mBounds[0].top;
+ mBounds[3].right = mBounds[0].left;
+ mBounds[3].bottom = mBounds[0].top;
+ additionalTasks = 3;
+ break;
+ case PLACE_FULL:
+ // Nothing to change.
+ break;
+ }
+
+ // Get the other tasks.
+ for (int i = 1; i <= additionalTasks && mTasks[i - 1] != null; ++i) {
+ mTasks[i] = mRecentsView.getNextTaskOrTopTask(mTasks[i - 1]);
+ // Do stop if we circled back to the first item.
+ if (mTasks[i] == mTasks[0]) {
+ mTasks[i] = null;
+ }
+ }
+
+ // Get rid of the dialog.
+ dismiss();
+ mRecentsActivity.dismissRecentsToHomeWithoutTransitionAnimation();
+
+ // Resize all tasks beginning from the "oldest" one.
+ for (int i = additionalTasks; i >= 0; --i) {
+ if (mTasks[i] != null) {
+ mSsp.resizeTask(mTasks[i].key.id, mBounds[i]);
+ }
+ }
+
+ // Show tasks as they might not be currently visible - beginning with the oldest so that
+ // the focus ends on the selected one.
+ for (int i = additionalTasks; i >= 0; --i) {
+ if (mTasks[i] != null) {
+ mRecentsView.launchTask(mTasks[i]);
+ }
+ }
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle args) {
+ final Context context = this.getActivity();
+ LayoutInflater inflater = getActivity().getLayoutInflater();
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ createResizeTaskDialog(context, inflater, builder);
+ return builder.create();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsUserEventProxyReceiver.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsUserEventProxyReceiver.java
index 236da5d..5eefbc7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsUserEventProxyReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsUserEventProxyReceiver.java
@@ -19,8 +19,6 @@ package com.android.systemui.recents;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import com.android.systemui.recent.Recents;
-
/**
* A proxy for Recents events which happens strictly for non-owner users.
@@ -39,28 +37,27 @@ public class RecentsUserEventProxyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
- AlternateRecentsComponent recents = Recents.getRecentsComponent(
- context.getApplicationContext(), true);
+ Recents recents = Recents.getInstanceAndStartIfNeeded(context);
switch (intent.getAction()) {
case ACTION_PROXY_SHOW_RECENTS_TO_USER: {
boolean triggeredFromAltTab = intent.getBooleanExtra(
- AlternateRecentsComponent.EXTRA_TRIGGERED_FROM_ALT_TAB, false);
- recents.showRecents(triggeredFromAltTab);
+ Recents.EXTRA_TRIGGERED_FROM_ALT_TAB, false);
+ recents.showRecentsInternal(triggeredFromAltTab);
break;
}
case ACTION_PROXY_HIDE_RECENTS_TO_USER: {
boolean triggeredFromAltTab = intent.getBooleanExtra(
- AlternateRecentsComponent.EXTRA_TRIGGERED_FROM_ALT_TAB, false);
+ Recents.EXTRA_TRIGGERED_FROM_ALT_TAB, false);
boolean triggeredFromHome = intent.getBooleanExtra(
- AlternateRecentsComponent.EXTRA_TRIGGERED_FROM_HOME_KEY, false);
- recents.hideRecents(triggeredFromAltTab, triggeredFromHome);
+ Recents.EXTRA_TRIGGERED_FROM_HOME_KEY, false);
+ recents.hideRecentsInternal(triggeredFromAltTab, triggeredFromHome);
break;
}
case ACTION_PROXY_TOGGLE_RECENTS_TO_USER:
- recents.toggleRecents();
+ recents.toggleRecentsInternal();
break;
case ACTION_PROXY_PRELOAD_RECENTS_TO_USER:
- recents.preloadRecents();
+ recents.preloadRecentsInternal();
break;
case ACTION_PROXY_CONFIG_CHANGE_TO_USER:
recents.configurationChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/recent/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
index 2fa0b58..cbf5c05 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/ScreenPinningRequest.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.recent;
+package com.android.systemui.recents;
import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
@@ -41,7 +41,6 @@ import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.systemui.R;
-import com.android.systemui.recents.model.RecentsTaskLoader;
import java.util.ArrayList;
@@ -145,7 +144,7 @@ public class ScreenPinningRequest implements View.OnClickListener {
boolean isLandscape = isLandscapePhone(mContext);
inflateView(isLandscape);
- int bgColor = mContext.getResources().getColor(
+ int bgColor = mContext.getColor(
R.color.screen_pinning_request_window_bg);
if (ActivityManager.isHighEndGfx()) {
mLayout.setAlpha(0f);
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 90b099c..b60c66f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -20,6 +20,7 @@ import android.app.ActivityManager;
import android.app.ActivityManagerNative;
import android.app.ActivityOptions;
import android.app.AppGlobals;
+import android.app.IActivityContainer;
import android.app.IActivityManager;
import android.app.ITaskStackListener;
import android.app.SearchManager;
@@ -49,25 +50,29 @@ import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Log;
+import android.util.MutableBoolean;
import android.util.Pair;
+import android.util.SparseArray;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
+import com.android.systemui.Prefs;
import com.android.systemui.R;
-import com.android.systemui.recents.AlternateRecentsComponent;
import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.RecentsAppWidgetHost;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
-import java.util.concurrent.atomic.AtomicBoolean;
/**
* Acts as a shim around the real system services that we need to access data from, and provides
@@ -187,7 +192,7 @@ public class SystemServicesProxy {
// Break early if we can't get a valid set of tasks
if (tasks == null) {
- return new ArrayList<ActivityManager.RecentTaskInfo>();
+ return new ArrayList<>();
}
boolean isFirstValidTask = true;
@@ -214,7 +219,7 @@ public class SystemServicesProxy {
}
/** Returns a list of the running tasks */
- public List<ActivityManager.RunningTaskInfo> getRunningTasks(int numTasks) {
+ private List<ActivityManager.RunningTaskInfo> getRunningTasks(int numTasks) {
if (mAm == null) return null;
return mAm.getRunningTasks(numTasks);
}
@@ -222,7 +227,7 @@ public class SystemServicesProxy {
/** Returns the top task. */
public ActivityManager.RunningTaskInfo getTopMostTask() {
List<ActivityManager.RunningTaskInfo> tasks = getRunningTasks(1);
- if (!tasks.isEmpty()) {
+ if (tasks != null && !tasks.isEmpty()) {
return tasks.get(0);
}
return null;
@@ -230,26 +235,77 @@ public class SystemServicesProxy {
/** Returns whether the recents is currently running */
public boolean isRecentsTopMost(ActivityManager.RunningTaskInfo topTask,
- AtomicBoolean isHomeTopMost) {
+ MutableBoolean isHomeTopMost) {
if (topTask != null) {
ComponentName topActivity = topTask.topActivity;
// Check if the front most activity is recents
- if (topActivity.getPackageName().equals(AlternateRecentsComponent.sRecentsPackage) &&
- topActivity.getClassName().equals(AlternateRecentsComponent.sRecentsActivity)) {
+ if (topActivity.getPackageName().equals(Recents.sRecentsPackage) &&
+ topActivity.getClassName().equals(Recents.sRecentsActivity)) {
if (isHomeTopMost != null) {
- isHomeTopMost.set(false);
+ isHomeTopMost.value = false;
}
return true;
}
if (isHomeTopMost != null) {
- isHomeTopMost.set(isInHomeStack(topTask.id));
+ isHomeTopMost.value = isInHomeStack(topTask.id);
}
}
return false;
}
+ /** Get the bounds of a stack / task. */
+ public Rect getTaskBounds(int stackId) {
+ ActivityManager.StackInfo info = getAllStackInfos().get(stackId);
+ if (info != null)
+ return info.bounds;
+ return new Rect();
+ }
+
+ /** Resize a given task. */
+ public void resizeTask(int taskId, Rect bounds) {
+ if (mIam == null) return;
+
+ try {
+ mIam.resizeTask(taskId, bounds);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /** Returns the stack info for all stacks. */
+ public SparseArray<ActivityManager.StackInfo> getAllStackInfos() {
+ if (mIam == null) return new SparseArray<ActivityManager.StackInfo>();
+
+ try {
+ SparseArray<ActivityManager.StackInfo> stacks =
+ new SparseArray<ActivityManager.StackInfo>();
+ List<ActivityManager.StackInfo> infos = mIam.getAllStackInfos();
+ int stackCount = infos.size();
+ for (int i = 0; i < stackCount; i++) {
+ ActivityManager.StackInfo info = infos.get(i);
+ stacks.put(info.stackId, info);
+ }
+ return stacks;
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ return new SparseArray<ActivityManager.StackInfo>();
+ }
+ }
+
+ /** Returns the focused stack id. */
+ public int getFocusedStack() {
+ if (mIam == null) return -1;
+
+ try {
+ return mIam.getFocusedStackId();
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ return -1;
+ }
+ }
+
/** Returns whether the specified task is in the home stack */
public boolean isInHomeStack(int taskId) {
if (mAm == null) return false;
@@ -313,7 +369,7 @@ public class SystemServicesProxy {
return thumbnail;
}
- /** Moves a task to the front with the specified activity options */
+ /** Moves a task to the front with the specified activity options. */
public void moveTaskToFront(int taskId, ActivityOptions opts) {
if (mAm == null) return;
if (Constants.DebugFlags.App.EnableSystemServicesProxy) return;
@@ -382,6 +438,33 @@ public class SystemServicesProxy {
return info.loadLabel(mPm).toString();
}
+ /** Returns the application label */
+ public String getApplicationLabel(Intent baseIntent, int userId) {
+ if (mPm == null) return null;
+
+ // If we are mocking, then return a mock label
+ if (Constants.DebugFlags.App.EnableSystemServicesProxy) {
+ return "Recent Task";
+ }
+
+ ResolveInfo ri = mPm.resolveActivityAsUser(baseIntent, 0, userId);
+ CharSequence label = (ri != null) ? ri.loadLabel(mPm) : null;
+ return (label != null) ? label.toString() : null;
+ }
+
+ /** Returns the content description for a given task */
+ public String getContentDescription(Intent baseIntent, int userId, String activityLabel,
+ Resources res) {
+ String applicationLabel = getApplicationLabel(baseIntent, userId);
+ if (applicationLabel == null) {
+ return getBadgedLabel(activityLabel, userId);
+ }
+ 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.
@@ -408,6 +491,16 @@ public class SystemServicesProxy {
return icon;
}
+ /**
+ * Returns the given label for a user, badging if necessary.
+ */
+ public String getBadgedLabel(String label, int userId) {
+ if (userId != UserHandle.myUserId()) {
+ label = mPm.getUserBadgedLabel(label, new UserHandle(userId)).toString();
+ }
+ return label;
+ }
+
/** Returns the package name of the home activity. */
public String getHomeActivityPackageName() {
if (mPm == null) return null;
@@ -436,14 +529,57 @@ public class SystemServicesProxy {
}
/**
- * Resolves and returns the first Recents widget from the same package as the global
- * assist activity.
+ * Returns the current search widget id.
*/
- public AppWidgetProviderInfo resolveSearchAppWidget() {
- if (mAwm == null) return null;
- if (mAssistComponent == null) return null;
+ public int getSearchAppWidgetId(Context context) {
+ return Prefs.getInt(context, Prefs.Key.SEARCH_APP_WIDGET_ID, -1);
+ }
+
+ /**
+ * Returns the current search widget info, binding a new one if necessary.
+ */
+ public AppWidgetProviderInfo getOrBindSearchAppWidget(Context context, AppWidgetHost host) {
+ int searchWidgetId = Prefs.getInt(context, Prefs.Key.SEARCH_APP_WIDGET_ID, -1);
+ AppWidgetProviderInfo searchWidgetInfo = mAwm.getAppWidgetInfo(searchWidgetId);
+ AppWidgetProviderInfo resolvedSearchWidgetInfo = resolveSearchAppWidget();
+
+ // Return the search widget info if it hasn't changed
+ if (searchWidgetInfo != null && resolvedSearchWidgetInfo != null &&
+ searchWidgetInfo.provider.equals(resolvedSearchWidgetInfo.provider)) {
+ if (Prefs.getString(context, Prefs.Key.SEARCH_APP_WIDGET_PACKAGE, null) == null) {
+ Prefs.putString(context, Prefs.Key.SEARCH_APP_WIDGET_PACKAGE,
+ searchWidgetInfo.provider.getPackageName());
+ }
+ return searchWidgetInfo;
+ }
+
+ // Delete the old widget
+ if (searchWidgetId != -1) {
+ host.deleteAppWidgetId(searchWidgetId);
+ }
+
+ // And rebind a new search widget
+ if (resolvedSearchWidgetInfo != null) {
+ Pair<Integer, AppWidgetProviderInfo> widgetInfo = bindSearchAppWidget(host,
+ resolvedSearchWidgetInfo);
+ if (widgetInfo != null) {
+ Prefs.putInt(context, Prefs.Key.SEARCH_APP_WIDGET_ID, widgetInfo.first);
+ Prefs.putString(context, Prefs.Key.SEARCH_APP_WIDGET_PACKAGE,
+ widgetInfo.second.provider.getPackageName());
+ return widgetInfo.second;
+ }
+ }
+
+ // If we fall through here, then there is no resolved search widget, so clear the state
+ Prefs.remove(context, Prefs.Key.SEARCH_APP_WIDGET_ID);
+ Prefs.remove(context, Prefs.Key.SEARCH_APP_WIDGET_PACKAGE);
+ return null;
+ }
- // Find the first Recents widget from the same package as the global assist activity
+ /**
+ * Returns the first Recents widget from the same package as the global assist activity.
+ */
+ private AppWidgetProviderInfo resolveSearchAppWidget() {
List<AppWidgetProviderInfo> widgets = mAwm.getInstalledProviders(
AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX);
for (AppWidgetProviderInfo info : widgets) {
@@ -457,45 +593,21 @@ public class SystemServicesProxy {
/**
* Resolves and binds the search app widget that is to appear in the recents.
*/
- public Pair<Integer, AppWidgetProviderInfo> bindSearchAppWidget(AppWidgetHost host) {
+ private Pair<Integer, AppWidgetProviderInfo> bindSearchAppWidget(AppWidgetHost host,
+ AppWidgetProviderInfo resolvedSearchWidgetInfo) {
if (mAwm == null) return null;
if (mAssistComponent == null) return null;
- // Find the first Recents widget from the same package as the global assist activity
- AppWidgetProviderInfo searchWidgetInfo = resolveSearchAppWidget();
-
- // Return early if there is no search widget
- if (searchWidgetInfo == null) return null;
-
// Allocate a new widget id and try and bind the app widget (if that fails, then just skip)
int searchWidgetId = host.allocateAppWidgetId();
Bundle opts = new Bundle();
opts.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX);
- if (!mAwm.bindAppWidgetIdIfAllowed(searchWidgetId, searchWidgetInfo.provider, opts)) {
+ if (!mAwm.bindAppWidgetIdIfAllowed(searchWidgetId, resolvedSearchWidgetInfo.provider, opts)) {
host.deleteAppWidgetId(searchWidgetId);
return null;
}
- return new Pair<Integer, AppWidgetProviderInfo>(searchWidgetId, searchWidgetInfo);
- }
-
- /**
- * Returns the app widget info for the specified app widget id.
- */
- public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) {
- if (mAwm == null) return null;
-
- return mAwm.getAppWidgetInfo(appWidgetId);
- }
-
- /**
- * Destroys the specified app widget.
- */
- public void unbindSearchAppWidget(AppWidgetHost host, int appWidgetId) {
- if (mAwm == null) return;
-
- // Delete the app widget
- host.deleteAppWidgetId(appWidgetId);
+ return new Pair<>(searchWidgetId, resolvedSearchWidgetInfo);
}
/**
@@ -524,6 +636,13 @@ public class SystemServicesProxy {
}
/**
+ * Returns a system property.
+ */
+ public String getSystemProperty(String key) {
+ return SystemProperties.get(key);
+ }
+
+ /**
* Returns the window rect.
*/
public Rect getWindowRect() {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
index e1179fa..e810410 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
@@ -21,42 +21,12 @@ import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.view.View;
-import com.android.systemui.recents.RecentsConfiguration;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
import java.util.ArrayList;
/* Common code */
public class Utilities {
- // Reflection methods for altering shadows
- private static Method sPropertyMethod;
- static {
- try {
- Class<?> c = Class.forName("android.view.GLES20Canvas");
- sPropertyMethod = c.getDeclaredMethod("setProperty", String.class, String.class);
- if (!sPropertyMethod.isAccessible()) sPropertyMethod.setAccessible(true);
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- } catch (NoSuchMethodException e) {
- e.printStackTrace();
- }
- }
-
- /**
- * Calculates a consistent animation duration (ms) for all animations depending on the movement
- * of the object being animated.
- */
- public static int calculateTranslationAnimationDuration(int distancePx) {
- return calculateTranslationAnimationDuration(distancePx, 100);
- }
- public static int calculateTranslationAnimationDuration(int distancePx, int minDuration) {
- RecentsConfiguration config = RecentsConfiguration.getInstance();
- return Math.max(minDuration, (int) (1000f /* ms/s */ *
- (Math.abs(distancePx) / config.animationPxMovementPerSecond)));
- }
-
/** Scales a rect about its centroid */
public static void scaleRectAboutCenter(Rect r, float scale) {
if (scale != 1.0f) {
@@ -177,12 +147,6 @@ public class Utilities {
(1f - overlayAlpha) * Color.blue(overlayColor)));
}
- /** Sets some private shadow properties. */
- public static void setShadowProperty(String property, String value)
- throws IllegalAccessException, InvocationTargetException {
- sPropertyMethod.invoke(null, property, value);
- }
-
/**
* Cancels an animation ensuring that if it has listeners, onCancel and onEnd
* are not called.
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
index 0e1c01a..f40c58d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -20,9 +20,12 @@ import android.app.ActivityManager;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import android.util.Log;
+import android.util.SparseArray;
+import com.android.systemui.recents.Constants;
import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.misc.SystemServicesProxy;
@@ -34,11 +37,11 @@ import java.util.List;
/**
* This class stores the loading state as it goes through multiple stages of loading:
- * - preloadRawTasks() will load the raw set of recents tasks from the system
- * - preloadPlan() will construct a new task stack with all metadata and only icons and thumbnails
- * that are currently in the cache
- * - 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
+ * 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 {
static String TAG = "RecentsTaskLoadPlan";
@@ -60,7 +63,7 @@ public class RecentsTaskLoadPlan {
SystemServicesProxy mSystemServicesProxy;
List<ActivityManager.RecentTaskInfo> mRawTasks;
- TaskStack mStack;
+ SparseArray<TaskStack> mStacks = new SparseArray<>();
HashMap<Task.ComponentNameKey, ActivityInfoHandle> mActivityInfoCache =
new HashMap<Task.ComponentNameKey, ActivityInfoHandle>();
@@ -90,11 +93,14 @@ public class RecentsTaskLoadPlan {
synchronized void preloadPlan(RecentsTaskLoader loader, boolean isTopTaskHome) {
if (DEBUG) Log.d(TAG, "preloadPlan");
+ // This activity info cache will be used for both preloadPlan() and executePlan()
mActivityInfoCache.clear();
- mStack = new TaskStack();
+
+ // TODO (multi-display): Currently assume the primary display
+ Rect displayBounds = mSystemServicesProxy.getWindowRect();
Resources res = mContext.getResources();
- ArrayList<Task> loadedTasks = new ArrayList<Task>();
+ SparseArray<ArrayList<Task>> stacksTasks = new SparseArray<>();
if (mRawTasks == null) {
preloadRawTasks(isTopTaskHome);
}
@@ -103,8 +109,8 @@ public class RecentsTaskLoadPlan {
ActivityManager.RecentTaskInfo t = mRawTasks.get(i);
// Compose the task key
- Task.TaskKey taskKey = new Task.TaskKey(t.persistentId, t.baseIntent, t.userId,
- t.firstActiveTime, t.lastActiveTime);
+ Task.TaskKey taskKey = new Task.TaskKey(t.persistentId, t.stackId, t.baseIntent,
+ t.userId, t.firstActiveTime, t.lastActiveTime);
// Get an existing activity info handle if possible
Task.ComponentNameKey cnKey = taskKey.getComponentNameKey();
@@ -120,6 +126,8 @@ public class RecentsTaskLoadPlan {
// Load the label, icon, and color
String activityLabel = loader.getAndUpdateActivityLabel(taskKey, t.taskDescription,
mSystemServicesProxy, infoHandle);
+ String contentDescription = loader.getAndUpdateContentDescription(taskKey,
+ activityLabel, mSystemServicesProxy, res);
Drawable activityIcon = loader.getAndUpdateActivityIcon(taskKey, t.taskDescription,
mSystemServicesProxy, res, infoHandle, false);
int activityColor = loader.getActivityPrimaryColor(t.taskDescription, mConfig);
@@ -138,19 +146,48 @@ public class RecentsTaskLoadPlan {
// Add the task to the stack
Task task = new Task(taskKey, (t.id != RecentsTaskLoader.INVALID_TASK_ID),
- t.affiliatedTaskId, t.affiliatedTaskColor, activityLabel, activityIcon,
- activityColor, (i == (taskCount - 1)), mConfig.lockToAppEnabled, icon,
- iconFilename);
+ t.affiliatedTaskId, t.affiliatedTaskColor, activityLabel, contentDescription,
+ activityIcon, activityColor, (i == (taskCount - 1)), mConfig.lockToAppEnabled,
+ icon, iconFilename);
task.thumbnail = loader.getAndUpdateThumbnail(taskKey, mSystemServicesProxy, false);
if (DEBUG) Log.d(TAG, "\tthumbnail: " + taskKey + ", " + task.thumbnail);
- loadedTasks.add(task);
+
+ if (!mConfig.multiStackEnabled ||
+ Constants.DebugFlags.App.EnableMultiStackToSingleStack) {
+ int firstStackId = 0;
+ ArrayList<Task> stackTasks = stacksTasks.get(firstStackId);
+ if (stackTasks == null) {
+ stackTasks = new ArrayList<>();
+ stacksTasks.put(firstStackId, stackTasks);
+ }
+ stackTasks.add(task);
+ } else {
+ ArrayList<Task> stackTasks = stacksTasks.get(t.stackId);
+ if (stackTasks == null) {
+ stackTasks = new ArrayList<>();
+ stacksTasks.put(t.stackId, stackTasks);
+ }
+ stackTasks.add(task);
+ }
}
- mStack.setTasks(loadedTasks);
- mStack.createAffiliatedGroupings(mConfig);
- // Assertion
- if (mStack.getTaskCount() != mRawTasks.size()) {
- throw new RuntimeException("Loading failed");
+ // Initialize the stacks
+ SparseArray<ActivityManager.StackInfo> stackInfos = mSystemServicesProxy.getAllStackInfos();
+ mStacks.clear();
+ int stackCount = stacksTasks.size();
+ for (int i = 0; i < stackCount; i++) {
+ int stackId = stacksTasks.keyAt(i);
+ ActivityManager.StackInfo info = stackInfos.get(stackId);
+ ArrayList<Task> stackTasks = stacksTasks.valueAt(i);
+ TaskStack stack = new TaskStack(stackId);
+ if (Constants.DebugFlags.App.EnableMultiStackToSingleStack) {
+ stack.setBounds(displayBounds, displayBounds);
+ } else {
+ stack.setBounds(info.bounds, displayBounds);
+ }
+ stack.setTasks(stackTasks);
+ stack.createAffiliatedGroupings(mConfig);
+ mStacks.put(stackId, stack);
}
}
@@ -166,72 +203,93 @@ public class RecentsTaskLoadPlan {
Resources res = mContext.getResources();
// Iterate through each of the tasks and load them according to the load conditions.
- ArrayList<Task> tasks = mStack.getTasks();
- int taskCount = tasks.size();
- for (int i = 0; i < taskCount; i++) {
- ActivityManager.RecentTaskInfo t = mRawTasks.get(i);
- Task task = tasks.get(i);
- Task.TaskKey taskKey = task.key;
+ int stackCount = mStacks.size();
+ for (int j = 0; j < stackCount; j++) {
+ ArrayList<Task> tasks = mStacks.valueAt(j).getTasks();
+ int taskCount = tasks.size();
+ for (int i = 0; i < taskCount; i++) {
+ ActivityManager.RecentTaskInfo t = mRawTasks.get(i);
+ Task task = tasks.get(i);
+ Task.TaskKey taskKey = task.key;
- // Get an existing activity info handle if possible
- Task.ComponentNameKey cnKey = taskKey.getComponentNameKey();
- ActivityInfoHandle infoHandle;
- boolean hadCachedActivityInfo = false;
- if (mActivityInfoCache.containsKey(cnKey)) {
- infoHandle = mActivityInfoCache.get(cnKey);
- hadCachedActivityInfo = true;
- } else {
- infoHandle = new ActivityInfoHandle();
- }
+ // Get an existing activity info handle if possible
+ Task.ComponentNameKey cnKey = taskKey.getComponentNameKey();
+ ActivityInfoHandle infoHandle;
+ boolean hadCachedActivityInfo = false;
+ if (mActivityInfoCache.containsKey(cnKey)) {
+ infoHandle = mActivityInfoCache.get(cnKey);
+ hadCachedActivityInfo = true;
+ } else {
+ infoHandle = new ActivityInfoHandle();
+ }
- boolean isRunningTask = (task.key.id == opts.runningTaskId);
- boolean isVisibleTask = i >= (taskCount - opts.numVisibleTasks);
- boolean isVisibleThumbnail = i >= (taskCount - opts.numVisibleTaskThumbnails);
+ 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 requested, skip the running task
+ if (opts.onlyLoadPausedActivities && isRunningTask) {
+ continue;
+ }
- if (opts.loadIcons && (isRunningTask || isVisibleTask)) {
- if (task.activityIcon == null) {
- if (DEBUG) Log.d(TAG, "\tLoading icon: " + taskKey);
- task.activityIcon = loader.getAndUpdateActivityIcon(taskKey, t.taskDescription,
- mSystemServicesProxy, res, infoHandle, true);
+ if (opts.loadIcons && (isRunningTask || isVisibleTask)) {
+ if (task.activityIcon == null) {
+ if (DEBUG) Log.d(TAG, "\tLoading icon: " + taskKey);
+ task.activityIcon = loader.getAndUpdateActivityIcon(taskKey,
+ t.taskDescription, mSystemServicesProxy, res, infoHandle, true);
+ }
}
- }
- if (opts.loadThumbnails && (isRunningTask || isVisibleThumbnail)) {
- if (task.thumbnail == null || isRunningTask) {
- if (DEBUG) Log.d(TAG, "\tLoading thumbnail: " + taskKey);
- if (mConfig.svelteLevel <= RecentsConfiguration.SVELTE_LIMIT_CACHE) {
- task.thumbnail = loader.getAndUpdateThumbnail(taskKey, mSystemServicesProxy,
- true);
- } else if (mConfig.svelteLevel == RecentsConfiguration.SVELTE_DISABLE_CACHE) {
- loadQueue.addTask(task);
+ if (opts.loadThumbnails && (isRunningTask || isVisibleThumbnail)) {
+ if (task.thumbnail == null || isRunningTask) {
+ if (DEBUG) Log.d(TAG, "\tLoading thumbnail: " + taskKey);
+ if (mConfig.svelteLevel <= RecentsConfiguration.SVELTE_LIMIT_CACHE) {
+ task.thumbnail = loader.getAndUpdateThumbnail(taskKey,
+ mSystemServicesProxy, true);
+ } else if (mConfig.svelteLevel == RecentsConfiguration.SVELTE_DISABLE_CACHE) {
+ loadQueue.addTask(task);
+ }
}
}
- }
- // Update the activity info cache
- if (!hadCachedActivityInfo && infoHandle.info != null) {
- mActivityInfoCache.put(cnKey, infoHandle);
+ // Update the activity info cache
+ if (!hadCachedActivityInfo && infoHandle.info != null) {
+ mActivityInfoCache.put(cnKey, infoHandle);
+ }
}
}
}
/**
- * Composes and returns a TaskStack from the preloaded list of recent tasks.
+ * Returns all TaskStacks from the preloaded list of recent tasks.
*/
- public TaskStack getTaskStack() {
- return mStack;
+ public ArrayList<TaskStack> getAllTaskStacks() {
+ ArrayList<TaskStack> stacks = new ArrayList<TaskStack>();
+ int stackCount = mStacks.size();
+ for (int i = 0; i < stackCount; i++) {
+ stacks.add(mStacks.valueAt(i));
+ }
+ // Ensure that we have at least one stack
+ if (stacks.isEmpty()) {
+ stacks.add(new TaskStack());
+ }
+ return stacks;
}
/**
- * Composes and returns a SpaceNode from the preloaded list of recent tasks.
+ * Returns a specific TaskStack from the preloaded list of recent tasks.
*/
- public SpaceNode getSpaceNode() {
- SpaceNode node = new SpaceNode();
- node.setStack(mStack);
- return node;
+ public TaskStack getTaskStack(int stackId) {
+ return mStacks.get(stackId);
+ }
+
+ /** Returns whether there are any tasks in any stacks. */
+ public boolean hasTasks() {
+ int stackCount = mStacks.size();
+ for (int i = 0; i < stackCount; i++) {
+ if (mStacks.valueAt(i).getTaskCount() > 0) {
+ return true;
+ }
+ }
+ return false;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index ba2903a..b2aa2b6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -33,7 +33,6 @@ import com.android.systemui.recents.Constants;
import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import java.util.Collection;
import java.util.concurrent.ConcurrentLinkedQueue;
@@ -47,18 +46,6 @@ class TaskResourceLoadQueue {
ConcurrentLinkedQueue<Task> mQueue = new ConcurrentLinkedQueue<Task>();
/** Adds a new task to the load queue */
- void addTasks(Collection<Task> tasks) {
- for (Task t : tasks) {
- if (!mQueue.contains(t)) {
- mQueue.add(t);
- }
- }
- synchronized(this) {
- notifyAll();
- }
- }
-
- /** Adds a new task to the load queue */
void addTask(Task t) {
if (!mQueue.contains(t)) {
mQueue.add(t);
@@ -272,6 +259,7 @@ public class RecentsTaskLoader {
DrawableLruCache mApplicationIconCache;
BitmapLruCache mThumbnailCache;
StringLruCache mActivityLabelCache;
+ StringLruCache mContentDescriptionCache;
TaskResourceLoadQueue mLoadQueue;
TaskResourceLoader mLoader;
@@ -311,6 +299,7 @@ public class RecentsTaskLoader {
mApplicationIconCache = new DrawableLruCache(iconCacheSize);
mThumbnailCache = new BitmapLruCache(thumbnailCacheSize);
mActivityLabelCache = new StringLruCache(100);
+ mContentDescriptionCache = new StringLruCache(100);
mLoader = new TaskResourceLoader(mLoadQueue, mApplicationIconCache, mThumbnailCache,
mDefaultThumbnail, mDefaultApplicationIcon);
}
@@ -361,6 +350,24 @@ public class RecentsTaskLoader {
return label;
}
+ /** Returns the content description using as many cached values as we can. */
+ public String getAndUpdateContentDescription(Task.TaskKey taskKey, String activityLabel,
+ SystemServicesProxy ssp, Resources res) {
+ // Return the cached content description if it exists
+ String label = mContentDescriptionCache.getAndInvalidateIfModified(taskKey);
+ if (label != null) {
+ return label;
+ }
+ label = ssp.getContentDescription(taskKey.baseIntent, taskKey.userId, activityLabel, res);
+ if (label != null) {
+ mContentDescriptionCache.put(taskKey, label);
+ } else {
+ Log.w(TAG, "Missing content description for " + taskKey.baseIntent.getComponent()
+ + " u=" + taskKey.userId);
+ }
+ return label;
+ }
+
/** Returns the activity icon using as many cached values as we can. */
public Drawable getAndUpdateActivityIcon(Task.TaskKey taskKey,
ActivityManager.TaskDescription td, SystemServicesProxy ssp,
@@ -554,6 +561,7 @@ public class RecentsTaskLoader {
mApplicationIconCache.evictAll();
// The cache is small, only clear the label cache when we are critical
mActivityLabelCache.evictAll();
+ mContentDescriptionCache.evictAll();
break;
default:
break;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/SpaceNode.java b/packages/SystemUI/src/com/android/systemui/recents/model/SpaceNode.java
deleted file mode 100644
index 831698a..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/model/SpaceNode.java
+++ /dev/null
@@ -1,82 +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.graphics.Rect;
-
-import java.util.ArrayList;
-
-
-/**
- * The full recents space is partitioned using a BSP into various nodes that define where task
- * stacks should be placed.
- */
-public class SpaceNode {
- /* BSP node callbacks */
- public interface SpaceNodeCallbacks {
- /** Notifies when a node is added */
- public void onSpaceNodeAdded(SpaceNode node);
- /** Notifies when a node is measured */
- public void onSpaceNodeMeasured(SpaceNode node, Rect rect);
- }
-
- SpaceNode mStartNode;
- SpaceNode mEndNode;
-
- TaskStack mStack;
-
- public SpaceNode() {
- // Do nothing
- }
-
- /** Sets the current stack for this space node */
- public void setStack(TaskStack stack) {
- mStack = stack;
- }
-
- /** Returns the task stack (not null if this is a leaf) */
- TaskStack getStack() {
- return mStack;
- }
-
- /** Returns whether there are any tasks in any stacks below this node. */
- public boolean hasTasks() {
- return (mStack.getTaskCount() > 0) ||
- (mStartNode != null && mStartNode.hasTasks()) ||
- (mEndNode != null && mEndNode.hasTasks());
- }
-
- /** Returns whether this is a leaf node */
- boolean isLeafNode() {
- return (mStartNode == null) && (mEndNode == null);
- }
-
- /** Returns all the descendent task stacks */
- private void getStacksRec(ArrayList<TaskStack> stacks) {
- if (isLeafNode()) {
- stacks.add(mStack);
- } else {
- mStartNode.getStacksRec(stacks);
- mEndNode.getStacksRec(stacks);
- }
- }
- public ArrayList<TaskStack> getStacks() {
- ArrayList<TaskStack> stacks = new ArrayList<TaskStack>();
- getStacksRec(stacks);
- return stacks;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
index 55dfe45..c14adf4 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -36,6 +36,9 @@ public class Task {
public void onTaskDataLoaded();
/* Notifies when a task has been unbound */
public void onTaskDataUnloaded();
+
+ /* Notifies when a task's stack id has changed. */
+ public void onMultiStackDebugTaskStackIdChanged();
}
/** The ComponentNameKey represents the unique primary key for a component
@@ -68,14 +71,17 @@ public class Task {
public static class TaskKey {
final ComponentNameKey mComponentNameKey;
public final int id;
+ public int stackId;
public final Intent baseIntent;
public final int userId;
public long firstActiveTime;
public long lastActiveTime;
- public TaskKey(int id, Intent intent, int userId, long firstActiveTime, long lastActiveTime) {
+ public TaskKey(int id, int stackId, Intent intent, int userId, long firstActiveTime,
+ long lastActiveTime) {
mComponentNameKey = new ComponentNameKey(intent.getComponent(), userId);
this.id = id;
+ this.stackId = stackId;
this.baseIntent = intent;
this.userId = userId;
this.firstActiveTime = firstActiveTime;
@@ -92,18 +98,19 @@ public class Task {
if (!(o instanceof TaskKey)) {
return false;
}
- return id == ((TaskKey) o).id
- && userId == ((TaskKey) o).userId;
+ TaskKey otherKey = (TaskKey) o;
+ return id == otherKey.id && stackId == otherKey.stackId && userId == otherKey.userId;
}
@Override
public int hashCode() {
- return (id << 5) + userId;
+ return Objects.hash(id, stackId, userId);
}
@Override
public String toString() {
return "Task.Key: " + id + ", "
+ + "s: " + stackId + ", "
+ "u: " + userId + ", "
+ "lat: " + lastActiveTime + ", "
+ baseIntent.getComponent().getPackageName();
@@ -117,6 +124,7 @@ public class Task {
public boolean isLaunchTarget;
public Drawable applicationIcon;
public Drawable activityIcon;
+ public String contentDescription;
public String activityLabel;
public int colorPrimary;
public boolean useLightOnPrimaryColor;
@@ -133,8 +141,8 @@ public class Task {
}
public Task(TaskKey key, boolean isActive, int taskAffiliation, int taskAffiliationColor,
- String activityTitle, Drawable activityIcon, int colorPrimary,
- boolean lockToThisTask, boolean lockToTaskEnabled, Bitmap icon,
+ String activityTitle, String contentDescription, Drawable activityIcon,
+ int colorPrimary, boolean lockToThisTask, boolean lockToTaskEnabled, Bitmap icon,
String iconFilename) {
boolean isInAffiliationGroup = (taskAffiliation != key.id);
boolean hasAffiliationGroupColor = isInAffiliationGroup && (taskAffiliationColor != 0);
@@ -142,6 +150,7 @@ public class Task {
this.taskAffiliation = taskAffiliation;
this.taskAffiliationColor = taskAffiliationColor;
this.activityLabel = activityTitle;
+ this.contentDescription = contentDescription;
this.activityIcon = activityIcon;
this.colorPrimary = hasAffiliationGroupColor ? taskAffiliationColor : colorPrimary;
this.useLightOnPrimaryColor = Utilities.computeContrastBetweenColors(this.colorPrimary,
@@ -159,6 +168,7 @@ public class Task {
this.taskAffiliation = o.taskAffiliation;
this.taskAffiliationColor = o.taskAffiliationColor;
this.activityLabel = o.activityLabel;
+ this.contentDescription = o.contentDescription;
this.activityIcon = o.activityIcon;
this.colorPrimary = o.colorPrimary;
this.useLightOnPrimaryColor = o.useLightOnPrimaryColor;
@@ -180,6 +190,14 @@ public class Task {
this.group = group;
}
+ /** Updates the stack id of this task. */
+ public void setStackId(int stackId) {
+ key.stackId = stackId;
+ if (mCb != null) {
+ mCb.onMultiStackDebugTaskStackIdChanged();
+ }
+ }
+
/** Notifies the callback listeners that this task has been loaded */
public void notifyTaskDataLoaded(Bitmap thumbnail, Drawable applicationIcon) {
this.applicationIcon = applicationIcon;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
index 255d642..5aaea15 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -17,6 +17,7 @@
package com.android.systemui.recents.model;
import android.graphics.Color;
+import android.graphics.Rect;
import com.android.systemui.recents.Constants;
import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.misc.NamedCounter;
@@ -165,39 +166,46 @@ public class TaskStack {
public void onStackTaskAdded(TaskStack stack, Task t);
/* Notifies when a task has been removed from the stack */
public void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask);
+ /* Notifies when all task has been removed from the stack */
+ public void onStackAllTasksRemoved(TaskStack stack, ArrayList<Task> removedTasks);
/** Notifies when the stack was filtered */
public void onStackFiltered(TaskStack newStack, ArrayList<Task> curTasks, Task t);
/** Notifies when the stack was un-filtered */
public void onStackUnfiltered(TaskStack newStack, ArrayList<Task> curTasks);
}
- /** A pair of indices representing the group and task positions in the stack and group. */
- public static class GroupTaskIndex {
- public int groupIndex; // Index in the stack
- public int taskIndex; // Index in the group
-
- public GroupTaskIndex() {}
-
- public GroupTaskIndex(int gi, int ti) {
- groupIndex = gi;
- taskIndex = ti;
- }
- }
-
// The task offset to apply to a task id as a group affiliation
static final int IndividualTaskIdOffset = 1 << 16;
+ public final int id;
+ public final Rect stackBounds = new Rect();
+ public final Rect displayBounds = new Rect();
+
FilteredTaskList mTaskList = new FilteredTaskList();
TaskStackCallbacks mCb;
ArrayList<TaskGrouping> mGroups = new ArrayList<TaskGrouping>();
HashMap<Integer, TaskGrouping> mAffinitiesGroups = new HashMap<Integer, TaskGrouping>();
- /** Sets the callbacks for this task stack */
+ public TaskStack() {
+ this(0);
+ }
+
+ public TaskStack(int stackId) {
+ id = stackId;
+ }
+
+ /** Sets the callbacks for this task stack. */
public void setCallbacks(TaskStackCallbacks cb) {
mCb = cb;
}
+ /** Sets the bounds of this stack. */
+ public void setBounds(Rect stackBounds, Rect displayBounds) {
+ this.stackBounds.set(stackBounds);
+ this.displayBounds.set(displayBounds);
+ }
+
/** Resets this TaskStack. */
public void reset() {
mCb = null;
@@ -214,19 +222,24 @@ public class TaskStack {
}
}
+ /** Does the actual work associated with removing the task. */
+ void removeTaskImpl(Task t) {
+ // Remove the task from the list
+ mTaskList.remove(t);
+ // Remove it from the group as well, and if it is empty, remove the group
+ TaskGrouping group = t.group;
+ group.removeTask(t);
+ if (group.getTaskCount() == 0) {
+ removeGroup(group);
+ }
+ // Update the lock-to-app state
+ t.lockToThisTask = false;
+ }
+
/** Removes a task */
public void removeTask(Task t) {
if (mTaskList.contains(t)) {
- // Remove the task from the list
- mTaskList.remove(t);
- // Remove it from the group as well, and if it is empty, remove the group
- TaskGrouping group = t.group;
- group.removeTask(t);
- if (group.getTaskCount() == 0) {
- removeGroup(group);
- }
- // Update the lock-to-app state
- t.lockToThisTask = false;
+ removeTaskImpl(t);
Task newFrontMostTask = getFrontMostTask();
if (newFrontMostTask != null && newFrontMostTask.lockToTaskEnabled) {
newFrontMostTask.lockToThisTask = true;
@@ -238,20 +251,27 @@ public class TaskStack {
}
}
+ /** Removes all tasks */
+ public void removeAllTasks() {
+ ArrayList<Task> taskList = new ArrayList<Task>(mTaskList.getTasks());
+ int taskCount = taskList.size();
+ for (int i = taskCount - 1; i >= 0; i--) {
+ Task t = taskList.get(i);
+ removeTaskImpl(t);
+ }
+ if (mCb != null) {
+ // Notify that all tasks have been removed
+ mCb.onStackAllTasksRemoved(this, taskList);
+ }
+ }
+
/** Sets a few tasks in one go */
public void setTasks(List<Task> tasks) {
ArrayList<Task> taskList = mTaskList.getTasks();
int taskCount = taskList.size();
- for (int i = 0; i < taskCount; i++) {
+ for (int i = taskCount - 1; i >= 0; i--) {
Task t = taskList.get(i);
- // Remove the task from the list
- mTaskList.remove(t);
- // Remove it from the group as well, and if it is empty, remove the group
- TaskGrouping group = t.group;
- group.removeTask(t);
- if (group.getTaskCount() == 0) {
- removeGroup(group);
- }
+ removeTaskImpl(t);
if (mCb != null) {
// Notify that a task has been removed
mCb.onStackTaskRemoved(this, t, null);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FakeShadowDrawable.java b/packages/SystemUI/src/com/android/systemui/recents/views/FakeShadowDrawable.java
index 72f9001..509ad1b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/FakeShadowDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/FakeShadowDrawable.java
@@ -159,9 +159,9 @@ class FakeShadowDrawable extends Drawable {
}
@Override
- public void setColorFilter(ColorFilter cf) {
- mCornerShadowPaint.setColorFilter(cf);
- mEdgeShadowPaint.setColorFilter(cf);
+ public void setColorFilter(ColorFilter colorFilter) {
+ mCornerShadowPaint.setColorFilter(colorFilter);
+ mEdgeShadowPaint.setColorFilter(colorFilter);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FixedSizeImageView.java b/packages/SystemUI/src/com/android/systemui/recents/views/FixedSizeImageView.java
index 4b5c0bd..3f5d0a8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/FixedSizeImageView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/FixedSizeImageView.java
@@ -73,4 +73,9 @@ public class FixedSizeImageView extends ImageView {
mAllowRelayout = true;
mAllowInvalidate = true;
}
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
}
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 ee79242..6cb11b1 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -24,15 +24,22 @@ import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.net.Uri;
+import android.os.Bundle;
+import android.os.IRemoteCallback;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowInsets;
+import android.view.WindowManagerGlobal;
import android.widget.FrameLayout;
+import com.android.systemui.R;
import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.RecentsAppWidgetHostView;
import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.model.RecentsPackageMonitor;
@@ -41,6 +48,7 @@ import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
import java.util.ArrayList;
+import java.util.List;
/**
* This view is the the top level layout that contains TaskStacks (which are laid out according
@@ -49,6 +57,8 @@ import java.util.ArrayList;
public class RecentsView extends FrameLayout implements TaskStackView.TaskStackViewCallbacks,
RecentsPackageMonitor.PackageCallbacks {
+ private static final String TAG = "RecentsView";
+
/** The RecentsView callbacks */
public interface RecentsViewCallbacks {
public void onTaskViewClicked();
@@ -56,14 +66,18 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
public void onAllTaskViewsDismissed();
public void onExitToHomeAnimationTriggered();
public void onScreenPinningRequest();
+ public void onTaskResize(Task t);
+ public void runAfterPause(Runnable r);
}
RecentsConfiguration mConfig;
LayoutInflater mInflater;
DebugOverlayView mDebugOverlay;
+ RecentsViewLayoutAlgorithm mLayoutAlgorithm;
ArrayList<TaskStack> mStacks;
- View mSearchBar;
+ List<TaskStackView> mTaskStackViews = new ArrayList<>();
+ RecentsAppWidgetHostView mSearchBar;
RecentsViewCallbacks mCb;
public RecentsView(Context context) {
@@ -82,6 +96,7 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
super(context, attrs, defStyleAttr, defStyleRes);
mConfig = RecentsConfiguration.getInstance();
mInflater = LayoutInflater.from(context);
+ mLayoutAlgorithm = new RecentsViewLayoutAlgorithm(mConfig);
}
/** Sets the callbacks */
@@ -98,29 +113,18 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
public void setTaskStacks(ArrayList<TaskStack> stacks) {
int numStacks = stacks.size();
- // Make a list of the stack view children only
- ArrayList<TaskStackView> stackViews = new ArrayList<TaskStackView>();
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- if (child != mSearchBar) {
- stackViews.add((TaskStackView) child);
- }
- }
-
// Remove all/extra stack views
int numTaskStacksToKeep = 0; // Keep no tasks if we are recreating the layout
if (mConfig.launchedReuseTaskStackViews) {
- numTaskStacksToKeep = Math.min(childCount, numStacks);
+ numTaskStacksToKeep = Math.min(mTaskStackViews.size(), numStacks);
}
- for (int i = stackViews.size() - 1; i >= numTaskStacksToKeep; i--) {
- removeView(stackViews.get(i));
- stackViews.remove(i);
+ for (int i = mTaskStackViews.size() - 1; i >= numTaskStacksToKeep; i--) {
+ removeView(mTaskStackViews.remove(i));
}
// Update the stack views that we are keeping
for (int i = 0; i < numTaskStacksToKeep; i++) {
- TaskStackView tsv = stackViews.get(i);
+ TaskStackView tsv = mTaskStackViews.get(i);
// If onRecentsHidden is not triggered, we need to the stack view again here
tsv.reset();
tsv.setStack(stacks.get(i));
@@ -128,21 +132,19 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
// Add remaining/recreate stack views
mStacks = stacks;
- for (int i = stackViews.size(); i < numStacks; i++) {
+ for (int i = mTaskStackViews.size(); i < numStacks; i++) {
TaskStack stack = stacks.get(i);
TaskStackView stackView = new TaskStackView(getContext(), stack);
stackView.setCallbacks(this);
addView(stackView);
+ mTaskStackViews.add(stackView);
}
// Enable debug mode drawing on all the stacks if necessary
if (mConfig.debugModeEnabled) {
- for (int i = childCount - 1; i >= 0; i--) {
- View v = getChildAt(i);
- if (v != mSearchBar) {
- TaskStackView stackView = (TaskStackView) v;
- stackView.setDebugOverlay(mDebugOverlay);
- }
+ for (int i = mTaskStackViews.size() - 1; i >= 0; i--) {
+ TaskStackView stackView = mTaskStackViews.get(i);
+ stackView.setDebugOverlay(mDebugOverlay);
}
}
@@ -150,24 +152,75 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
requestLayout();
}
+ /** Gets the list of task views */
+ List<TaskStackView> getTaskStackViews() {
+ return mTaskStackViews;
+ }
+
+ /** Gets the next task in the stack - or if the last - the top task */
+ public Task getNextTaskOrTopTask(Task taskToSearch) {
+ Task returnTask = null;
+ boolean found = false;
+ List<TaskStackView> stackViews = getTaskStackViews();
+ int stackCount = stackViews.size();
+ for (int i = stackCount - 1; i >= 0; --i) {
+ TaskStack stack = stackViews.get(i).getStack();
+ ArrayList<Task> taskList = stack.getTasks();
+ // Iterate the stack views and try and find the focused task
+ for (int j = taskList.size() - 1; j >= 0; --j) {
+ Task task = taskList.get(j);
+ // Return the next task in the line.
+ if (found)
+ return task;
+ // Remember the first possible task as the top task.
+ if (returnTask == null)
+ returnTask = task;
+ if (task == taskToSearch)
+ found = true;
+ }
+ }
+ return returnTask;
+ }
+
/** Launches the focused task from the first stack if possible */
public boolean launchFocusedTask() {
// Get the first stack view
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- if (child != mSearchBar) {
- TaskStackView stackView = (TaskStackView) child;
- TaskStack stack = stackView.mStack;
- // Iterate the stack views and try and find the focused task
- int taskCount = stackView.getChildCount();
- for (int j = 0; j < taskCount; j++) {
- TaskView tv = (TaskView) stackView.getChildAt(j);
- Task task = tv.getTask();
- if (tv.isFocusedTask()) {
- onTaskViewClicked(stackView, tv, stack, task, false);
- return true;
- }
+ List<TaskStackView> stackViews = getTaskStackViews();
+ int stackCount = stackViews.size();
+ for (int i = 0; i < stackCount; i++) {
+ TaskStackView stackView = stackViews.get(i);
+ TaskStack stack = stackView.getStack();
+ // Iterate the stack views and try and find the focused task
+ List<TaskView> taskViews = stackView.getTaskViews();
+ int taskViewCount = taskViews.size();
+ for (int j = 0; j < taskViewCount; j++) {
+ TaskView tv = taskViews.get(j);
+ Task task = tv.getTask();
+ if (tv.isFocusedTask()) {
+ onTaskViewClicked(stackView, tv, stack, task, false);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /** Launches a given task. */
+ public boolean launchTask(Task task) {
+ // Get the first stack view
+ List<TaskStackView> stackViews = getTaskStackViews();
+ int stackCount = stackViews.size();
+ for (int i = 0; i < stackCount; i++) {
+ TaskStackView stackView = stackViews.get(i);
+ TaskStack stack = stackView.getStack();
+ // Iterate the stack views and try and find the given task.
+ List<TaskView> taskViews = stackView.getTaskViews();
+ int taskViewCount = taskViews.size();
+ for (int j = 0; j < taskViewCount; j++) {
+ TaskView tv = taskViews.get(j);
+ if (tv.getTask() == task) {
+ onTaskViewClicked(stackView, tv, stack, task, false);
+ return true;
}
}
}
@@ -177,24 +230,22 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
/** Launches the task that Recents was launched from, if possible */
public boolean launchPreviousTask() {
// Get the first stack view
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- if (child != mSearchBar) {
- TaskStackView stackView = (TaskStackView) child;
- TaskStack stack = stackView.mStack;
- ArrayList<Task> tasks = stack.getTasks();
-
- // Find the launch task in the stack
- if (!tasks.isEmpty()) {
- int taskCount = tasks.size();
- for (int j = 0; j < taskCount; j++) {
- if (tasks.get(j).isLaunchTarget) {
- Task task = tasks.get(j);
- TaskView tv = stackView.getChildViewForTask(task);
- onTaskViewClicked(stackView, tv, stack, task, false);
- return true;
- }
+ List<TaskStackView> stackViews = getTaskStackViews();
+ int stackCount = stackViews.size();
+ for (int i = 0; i < stackCount; i++) {
+ TaskStackView stackView = stackViews.get(i);
+ TaskStack stack = stackView.getStack();
+ ArrayList<Task> tasks = stack.getTasks();
+
+ // Find the launch task in the stack
+ if (!tasks.isEmpty()) {
+ int taskCount = tasks.size();
+ for (int j = 0; j < taskCount; j++) {
+ if (tasks.get(j).isLaunchTarget) {
+ Task task = tasks.get(j);
+ TaskView tv = stackView.getChildViewForTask(task);
+ onTaskViewClicked(stackView, tv, stack, task, false);
+ return true;
}
}
}
@@ -208,13 +259,11 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
// to ensure that it runs
ctx.postAnimationTrigger.increment();
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- if (child != mSearchBar) {
- TaskStackView stackView = (TaskStackView) child;
- stackView.startEnterRecentsAnimation(ctx);
- }
+ List<TaskStackView> stackViews = getTaskStackViews();
+ int stackCount = stackViews.size();
+ for (int i = 0; i < stackCount; i++) {
+ TaskStackView stackView = stackViews.get(i);
+ stackView.startEnterRecentsAnimation(ctx);
}
ctx.postAnimationTrigger.decrement();
}
@@ -224,13 +273,11 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
// We have to increment/decrement the post animation trigger in case there are no children
// to ensure that it runs
ctx.postAnimationTrigger.increment();
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- if (child != mSearchBar) {
- TaskStackView stackView = (TaskStackView) child;
- stackView.startExitToHomeAnimation(ctx);
- }
+ List<TaskStackView> stackViews = getTaskStackViews();
+ int stackCount = stackViews.size();
+ for (int i = 0; i < stackCount; i++) {
+ TaskStackView stackView = stackViews.get(i);
+ stackView.startExitToHomeAnimation(ctx);
}
ctx.postAnimationTrigger.decrement();
@@ -239,24 +286,21 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
}
/** Adds the search bar */
- public void setSearchBar(View searchBar) {
- // Create the search bar (and hide it if we have no recent tasks)
- if (Constants.DebugFlags.App.EnableSearchLayout) {
- // Remove the previous search bar if one exists
- if (mSearchBar != null && indexOfChild(mSearchBar) > -1) {
- removeView(mSearchBar);
- }
- // Add the new search bar
- if (searchBar != null) {
- mSearchBar = searchBar;
- addView(mSearchBar);
- }
+ public void setSearchBar(RecentsAppWidgetHostView searchBar) {
+ // Remove the previous search bar if one exists
+ if (mSearchBar != null && indexOfChild(mSearchBar) > -1) {
+ removeView(mSearchBar);
+ }
+ // Add the new search bar
+ if (searchBar != null) {
+ mSearchBar = searchBar;
+ addView(mSearchBar);
}
}
/** Returns whether there is currently a search bar */
- public boolean hasSearchBar() {
- return mSearchBar != null;
+ public boolean hasValidSearchBar() {
+ return mSearchBar != null && !mSearchBar.isReinflateRequired();
}
/** Sets the visibility of the search bar */
@@ -277,8 +321,8 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
int height = MeasureSpec.getSize(heightMeasureSpec);
// Get the search bar bounds and measure the search bar layout
+ Rect searchBarSpaceBounds = new Rect();
if (mSearchBar != null) {
- Rect searchBarSpaceBounds = new Rect();
mConfig.getSearchBarBounds(width, height, mConfig.systemInsets.top, searchBarSpaceBounds);
mSearchBar.measure(
MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.width(), MeasureSpec.EXACTLY),
@@ -286,19 +330,23 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
}
Rect taskStackBounds = new Rect();
- mConfig.getTaskStackBounds(width, height, mConfig.systemInsets.top,
- mConfig.systemInsets.right, taskStackBounds);
+ mConfig.getAvailableTaskStackBounds(width, height, mConfig.systemInsets.top,
+ mConfig.systemInsets.right, searchBarSpaceBounds, taskStackBounds);
- // Measure each TaskStackView with the full width and height of the window since the
+ // Measure each TaskStackView with the full width and height of the window since the
// transition view is a child of that stack view
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- if (child != mSearchBar && child.getVisibility() != GONE) {
- TaskStackView tsv = (TaskStackView) child;
- // Set the insets to be the top/left inset + search bounds
- tsv.setStackInsetRect(taskStackBounds);
- tsv.measure(widthMeasureSpec, heightMeasureSpec);
+ List<TaskStackView> stackViews = getTaskStackViews();
+ List<Rect> stackViewsBounds = mLayoutAlgorithm.computeStackRects(stackViews,
+ taskStackBounds);
+ int stackCount = stackViews.size();
+ for (int i = 0; i < stackCount; i++) {
+ TaskStackView stackView = stackViews.get(i);
+ if (stackView.getVisibility() != GONE) {
+ // We are going to measure the TaskStackView with the whole RecentsView dimensions,
+ // but the actual stack is going to be inset to the bounds calculated by the layout
+ // algorithm
+ stackView.setStackInsetRect(stackViewsBounds.get(i));
+ stackView.measure(widthMeasureSpec, heightMeasureSpec);
}
}
@@ -321,12 +369,13 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
// Layout each TaskStackView with the full width and height of the window since the
// transition view is a child of that stack view
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- if (child != mSearchBar && child.getVisibility() != GONE) {
- child.layout(left, top, left + child.getMeasuredWidth(),
- top + child.getMeasuredHeight());
+ List<TaskStackView> stackViews = getTaskStackViews();
+ int stackCount = stackViews.size();
+ for (int i = 0; i < stackCount; i++) {
+ TaskStackView stackView = stackViews.get(i);
+ if (stackView.getVisibility() != GONE) {
+ stackView.layout(left, top, left + stackView.getMeasuredWidth(),
+ top + stackView.getMeasuredHeight());
}
}
}
@@ -342,41 +391,29 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
/** Notifies each task view of the user interaction. */
public void onUserInteraction() {
// Get the first stack view
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- if (child != mSearchBar) {
- TaskStackView stackView = (TaskStackView) child;
- stackView.onUserInteraction();
- }
+ List<TaskStackView> stackViews = getTaskStackViews();
+ int stackCount = stackViews.size();
+ for (int i = 0; i < stackCount; i++) {
+ TaskStackView stackView = stackViews.get(i);
+ stackView.onUserInteraction();
}
}
/** Focuses the next task in the first stack view */
public void focusNextTask(boolean forward) {
// Get the first stack view
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- if (child != mSearchBar) {
- TaskStackView stackView = (TaskStackView) child;
- stackView.focusNextTask(forward, true);
- break;
- }
+ List<TaskStackView> stackViews = getTaskStackViews();
+ if (!stackViews.isEmpty()) {
+ stackViews.get(0).focusNextTask(forward, true);
}
}
/** Dismisses the focused task. */
public void dismissFocusedTask() {
// Get the first stack view
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- if (child != mSearchBar) {
- TaskStackView stackView = (TaskStackView) child;
- stackView.dismissFocusedTask();
- break;
- }
+ List<TaskStackView> stackViews = getTaskStackViews();
+ if (!stackViews.isEmpty()) {
+ stackViews.get(0).dismissFocusedTask();
}
}
@@ -398,11 +435,75 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
return false;
}
+ public void disableLayersForOneFrame() {
+ List<TaskStackView> stackViews = getTaskStackViews();
+ for (int i = 0; i < stackViews.size(); i++) {
+ stackViews.get(i).disableLayersForOneFrame();
+ }
+ }
+
+ private void postDrawHeaderThumbnailTransitionRunnable(final TaskView tv, final int offsetX,
+ final int offsetY, final TaskViewTransform transform,
+ final ActivityOptions.OnAnimationStartedListener animStartedListener) {
+ Runnable r = new Runnable() {
+ @Override
+ public void run() {
+ // Disable any focused state before we draw the header
+ if (tv.isFocusedTask()) {
+ tv.unsetFocusedTask();
+ }
+
+ float scale = tv.getScaleX();
+ int fromHeaderWidth = (int) (tv.mHeaderView.getMeasuredWidth() * scale);
+ int fromHeaderHeight = (int) (tv.mHeaderView.getMeasuredHeight() * scale);
+
+ Bitmap b = Bitmap.createBitmap(fromHeaderWidth, fromHeaderHeight,
+ Bitmap.Config.ARGB_8888);
+ if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) {
+ b.eraseColor(0xFFff0000);
+ } else {
+ Canvas c = new Canvas(b);
+ c.scale(tv.getScaleX(), tv.getScaleY());
+ tv.mHeaderView.draw(c);
+ c.setBitmap(null);
+ }
+ b = b.createAshmemBitmap();
+ int[] pts = new int[2];
+ tv.getLocationOnScreen(pts);
+ try {
+ WindowManagerGlobal.getWindowManagerService()
+ .overridePendingAppTransitionAspectScaledThumb(b,
+ pts[0] + offsetX,
+ pts[1] + offsetY,
+ transform.rect.width(),
+ transform.rect.height(),
+ new IRemoteCallback.Stub() {
+ @Override
+ public void sendResult(Bundle data)
+ throws RemoteException {
+ post(new Runnable() {
+ @Override
+ public void run() {
+ if (animStartedListener != null) {
+ animStartedListener.onAnimationStarted();
+ }
+ }
+ });
+ }
+ }, true);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error overriding app transition", e);
+ }
+ }
+ };
+ mCb.runAfterPause(r);
+ }
/**** TaskStackView.TaskStackCallbacks Implementation ****/
@Override
public void onTaskViewClicked(final TaskStackView stackView, final TaskView tv,
final TaskStack stack, final Task task, final boolean lockToTask) {
+
// Notify any callbacks of the launching of a new task
if (mCb != null) {
mCb.onTaskViewClicked();
@@ -433,30 +534,6 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
ActivityOptions opts = null;
if (task.thumbnail != null && task.thumbnail.getWidth() > 0 &&
task.thumbnail.getHeight() > 0) {
- Bitmap b;
- if (tv != null) {
- // Disable any focused state before we draw the header
- if (tv.isFocusedTask()) {
- tv.unsetFocusedTask();
- }
-
- float scale = tv.getScaleX();
- int fromHeaderWidth = (int) (tv.mHeaderView.getMeasuredWidth() * scale);
- int fromHeaderHeight = (int) (tv.mHeaderView.getMeasuredHeight() * scale);
- b = Bitmap.createBitmap(fromHeaderWidth, fromHeaderHeight,
- Bitmap.Config.ARGB_8888);
- if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) {
- b.eraseColor(0xFFff0000);
- } else {
- Canvas c = new Canvas(b);
- c.scale(tv.getScaleX(), tv.getScaleY());
- tv.mHeaderView.draw(c);
- c.setBitmap(null);
- }
- } else {
- // Notify the system to skip the thumbnail layer by using an ALPHA_8 bitmap
- b = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8);
- }
ActivityOptions.OnAnimationStartedListener animStartedListener = null;
if (lockToTask) {
animStartedListener = new ActivityOptions.OnAnimationStartedListener() {
@@ -475,9 +552,21 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
}
};
}
- opts = ActivityOptions.makeThumbnailAspectScaleUpAnimation(sourceView,
- b, offsetX, offsetY, transform.rect.width(), transform.rect.height(),
- sourceView.getHandler(), animStartedListener);
+ if (tv != null) {
+ postDrawHeaderThumbnailTransitionRunnable(tv, offsetX, offsetY, transform,
+ animStartedListener);
+ }
+ if (mConfig.multiStackEnabled) {
+ opts = ActivityOptions.makeCustomAnimation(sourceView.getContext(),
+ R.anim.recents_from_unknown_enter,
+ R.anim.recents_from_unknown_exit,
+ sourceView.getHandler(), animStartedListener);
+ } else {
+ opts = ActivityOptions.makeThumbnailAspectScaleUpAnimation(sourceView,
+ Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8).createAshmemBitmap(),
+ offsetX, offsetY, transform.rect.width(), transform.rect.height(),
+ sourceView.getHandler(), animStartedListener);
+ }
}
final ActivityOptions launchOpts = opts;
@@ -509,7 +598,7 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
if (tv == null) {
launchRunnable.run();
} else {
- if (!task.group.isFrontMostTask(task)) {
+ if (task.group != null && !task.group.isFrontMostTask(task)) {
// For affiliated tasks that are behind other tasks, we must animate the front cards
// out of view before starting the task transition
stackView.startLaunchTaskAnimation(tv, launchRunnable, lockToTask);
@@ -542,24 +631,29 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
loader.deleteTaskData(t, false);
// Remove the old task from activity manager
- RecentsTaskLoader.getInstance().getSystemServicesProxy().removeTask(t.key.id);
+ loader.getSystemServicesProxy().removeTask(t.key.id);
}
@Override
- public void onAllTaskViewsDismissed() {
+ public void onAllTaskViewsDismissed(ArrayList<Task> removedTasks) {
+ if (removedTasks != null) {
+ int taskCount = removedTasks.size();
+ for (int i = 0; i < taskCount; i++) {
+ onTaskViewDismissed(removedTasks.get(i));
+ }
+ }
+
mCb.onAllTaskViewsDismissed();
}
/** Final callback after Recents is finally hidden. */
public void onRecentsHidden() {
// Notify each task stack view
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- if (child != mSearchBar) {
- TaskStackView stackView = (TaskStackView) child;
- stackView.onRecentsHidden();
- }
+ List<TaskStackView> stackViews = getTaskStackViews();
+ int stackCount = stackViews.size();
+ for (int i = 0; i < stackCount; i++) {
+ TaskStackView stackView = stackViews.get(i);
+ stackView.onRecentsHidden();
}
}
@@ -591,18 +685,23 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
}
}
+ @Override
+ public void onTaskResize(Task t) {
+ if (mCb != null) {
+ mCb.onTaskResize(t);
+ }
+ }
+
/**** RecentsPackageMonitor.PackageCallbacks Implementation ****/
@Override
public void onPackagesChanged(RecentsPackageMonitor monitor, String packageName, int userId) {
// Propagate this event down to each task stack view
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- if (child != mSearchBar) {
- TaskStackView stackView = (TaskStackView) child;
- stackView.onPackagesChanged(monitor, packageName, userId);
- }
+ List<TaskStackView> stackViews = getTaskStackViews();
+ int stackCount = stackViews.size();
+ for (int i = 0; i < stackCount; i++) {
+ TaskStackView stackView = stackViews.get(i);
+ stackView.onPackagesChanged(monitor, packageName, userId);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewLayoutAlgorithm.java
new file mode 100644
index 0000000..eea273c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewLayoutAlgorithm.java
@@ -0,0 +1,59 @@
+/*
+ * 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.Rect;
+import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.model.TaskStack;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/* The layout logic for the RecentsView. */
+public class RecentsViewLayoutAlgorithm {
+
+ RecentsConfiguration mConfig;
+
+ public RecentsViewLayoutAlgorithm(RecentsConfiguration config) {
+ mConfig = config;
+ }
+
+ /** Return the relative coordinate given coordinates in another size. */
+ private int getRelativeCoordinate(int availableOffset, int availableSize, int otherCoord, int otherSize) {
+ float relPos = (float) otherCoord / otherSize;
+ return availableOffset + (int) (relPos * availableSize);
+ }
+
+ /**
+ * Computes and returns the bounds that each of the stack views should take up.
+ */
+ List<Rect> computeStackRects(List<TaskStackView> stackViews, Rect availableBounds) {
+ ArrayList<Rect> bounds = new ArrayList<Rect>(stackViews.size());
+ int stackViewsCount = stackViews.size();
+ for (int i = 0; i < stackViewsCount; i++) {
+ TaskStack stack = stackViews.get(i).getStack();
+ Rect sb = stack.stackBounds;
+ Rect db = stack.displayBounds;
+ Rect ab = availableBounds;
+ bounds.add(new Rect(getRelativeCoordinate(ab.left, ab.width(), sb.left, db.width()),
+ getRelativeCoordinate(ab.top, ab.height(), sb.top, db.height()),
+ getRelativeCoordinate(ab.left, ab.width(), sb.right, db.width()),
+ getRelativeCoordinate(ab.top, ab.height(), sb.bottom, db.height())));
+ }
+ return bounds;
+ }
+}
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 169683f..ebfc796 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -19,12 +19,15 @@ package com.android.systemui.recents.views;
import android.animation.ValueAnimator;
import android.content.ComponentName;
import android.content.Context;
+import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Rect;
+import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import com.android.systemui.R;
import com.android.systemui.recents.Constants;
@@ -36,11 +39,14 @@ import com.android.systemui.recents.model.RecentsPackageMonitor;
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.statusbar.DismissView;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.List;
/* The visual representation of a task stack view */
@@ -54,11 +60,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
boolean lockToTask);
public void onTaskViewAppInfoClicked(Task t);
public void onTaskViewDismissed(Task t);
- public void onAllTaskViewsDismissed();
+ public void onAllTaskViewsDismissed(ArrayList<Task> removedTasks);
public void onTaskStackFilterTriggered();
public void onTaskStackUnfilterTriggered();
- }
+ public void onTaskResize(Task t);
+ }
RecentsConfiguration mConfig;
TaskStack mStack;
@@ -72,9 +79,10 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
DozeTrigger mUIDozeTrigger;
DebugOverlayView mDebugOverlay;
Rect mTaskStackBounds = new Rect();
+ DismissView mDismissAllButton;
+ boolean mDismissAllButtonAnimating;
int mFocusedTaskIndex = -1;
int mPrevAccessibilityFocusedIndex = -1;
-
// Optimizations
int mStackViewsAnimationDuration;
boolean mStackViewsDirty = true;
@@ -89,7 +97,10 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
Rect mTmpRect = new Rect();
TaskViewTransform mTmpTransform = new TaskViewTransform();
HashMap<Task, TaskView> mTmpTaskViewMap = new HashMap<Task, TaskView>();
+ ArrayList<TaskView> mTaskViews = new ArrayList<TaskView>();
+ List<TaskView> mImmutableTaskViews = new ArrayList<TaskView>();
LayoutInflater mInflater;
+ boolean mLayersDisabled;
// A convenience update listener to request updating clipping of tasks
ValueAnimator.AnimatorUpdateListener mRequestUpdateClippingListener =
@@ -116,13 +127,15 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
@Override
public void run() {
// Show the task bar dismiss buttons
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- TaskView tv = (TaskView) getChildAt(i);
+ List<TaskView> taskViews = getTaskViews();
+ int taskViewCount = taskViews.size();
+ for (int i = 0; i < taskViewCount; i++) {
+ TaskView tv = taskViews.get(i);
tv.startNoUserInteractionAnimation();
}
}
});
+ setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
}
/** Sets the callbacks */
@@ -141,21 +154,44 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
requestLayout();
}
+ /** Returns the task stack. */
+ TaskStack getStack() {
+ return mStack;
+ }
+
/** Sets the debug overlay */
public void setDebugOverlay(DebugOverlayView overlay) {
mDebugOverlay = overlay;
}
+ /** Updates the list of task views */
+ void updateTaskViewsList() {
+ mTaskViews.clear();
+ int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View v = getChildAt(i);
+ if (v instanceof TaskView) {
+ mTaskViews.add((TaskView) v);
+ }
+ }
+ mImmutableTaskViews = Collections.unmodifiableList(mTaskViews);
+ }
+
+ /** Gets the list of task views */
+ List<TaskView> getTaskViews() {
+ return mImmutableTaskViews;
+ }
+
/** Resets this TaskStackView for reuse. */
void reset() {
// Reset the focused task
resetFocusedTask();
// Return all the views to the pool
- int childCount = getChildCount();
- for (int i = childCount - 1; i >= 0; i--) {
- TaskView tv = (TaskView) getChildAt(i);
- mViewPool.returnViewToPool(tv);
+ List<TaskView> taskViews = getTaskViews();
+ int taskViewCount = taskViews.size();
+ for (int i = taskViewCount - 1; i >= 0; i--) {
+ mViewPool.returnViewToPool(taskViews.get(i));
}
// Mark each task view for relayout
@@ -209,9 +245,10 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
/** Finds the child view given a specific task. */
public TaskView getChildViewForTask(Task t) {
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- TaskView tv = (TaskView) getChildAt(i);
+ List<TaskView> taskViews = getTaskViews();
+ int taskViewCount = taskViews.size();
+ for (int i = 0; i < taskViewCount; i++) {
+ TaskView tv = taskViews.get(i);
if (tv.getTask() == t) {
return tv;
}
@@ -299,17 +336,38 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
mDebugOverlay.setText("vis[" + visibleRange[1] + "-" + visibleRange[0] + "]");
}
+ // Inflate and add the dismiss button if necessary
+ if (Constants.DebugFlags.App.EnableDismissAll && mDismissAllButton == null) {
+ mDismissAllButton = (DismissView)
+ mInflater.inflate(R.layout.recents_dismiss_button, this, false);
+ mDismissAllButton.setOnButtonClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mStack.removeAllTasks();
+ }
+ });
+ addView(mDismissAllButton, 0);
+ }
+
// Return all the invisible children to the pool
mTmpTaskViewMap.clear();
- int childCount = getChildCount();
- for (int i = childCount - 1; i >= 0; i--) {
- TaskView tv = (TaskView) getChildAt(i);
+ List<TaskView> taskViews = getTaskViews();
+ int taskViewCount = taskViews.size();
+ boolean reaquireAccessibilityFocus = false;
+ for (int i = taskViewCount - 1; i >= 0; i--) {
+ TaskView tv = taskViews.get(i);
Task task = tv.getTask();
int taskIndex = mStack.indexOfTask(task);
if (visibleRange[1] <= taskIndex && taskIndex <= visibleRange[0]) {
mTmpTaskViewMap.put(task, tv);
} else {
mViewPool.returnViewToPool(tv);
+ reaquireAccessibilityFocus |= (i == mPrevAccessibilityFocusedIndex);
+
+ // Hide the dismiss button if the front most task is invisible
+ if (task == mStack.getFrontMostTask()) {
+ hideDismissAllButton(null);
+ }
}
}
@@ -322,7 +380,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
if (tv == null) {
tv = mViewPool.pickUpViewFromPool(task, task);
-
+ if (mLayersDisabled) {
+ tv.disableLayersForOneFrame();
+ }
if (mStackViewsAnimationDuration > 0) {
// For items in the list, put them in start animating them from the
// approriate ends of the list where they are expected to appear
@@ -333,6 +393,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
tv.updateViewPropertiesToTaskTransform(mTmpTransform, 0);
}
+
+ // If we show the front most task view then ensure that the dismiss button
+ // is visible too.
+ if (!mAwaitingFirstLayout && (task == mStack.getFrontMostTask())) {
+ showDismissAllButton();
+ }
}
// Animate the task into place
@@ -341,13 +407,17 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// Request accessibility focus on the next view if we removed the task
// that previously held accessibility focus
- childCount = getChildCount();
- if (childCount > 0 && ssp.isTouchExplorationEnabled()) {
- TaskView atv = (TaskView) getChildAt(childCount - 1);
- int indexOfTask = mStack.indexOfTask(atv.getTask());
- if (mPrevAccessibilityFocusedIndex != indexOfTask) {
- tv.requestAccessibilityFocus();
- mPrevAccessibilityFocusedIndex = indexOfTask;
+ if (reaquireAccessibilityFocus) {
+ taskViews = getTaskViews();
+ taskViewCount = taskViews.size();
+ if (taskViewCount > 0 && ssp.isTouchExplorationEnabled() &&
+ mPrevAccessibilityFocusedIndex != -1) {
+ TaskView atv = taskViews.get(taskViewCount - 1);
+ int indexOfTask = mStack.indexOfTask(atv.getTask());
+ if (mPrevAccessibilityFocusedIndex != indexOfTask) {
+ tv.requestAccessibilityFocus();
+ mPrevAccessibilityFocusedIndex = indexOfTask;
+ }
}
}
}
@@ -364,44 +434,43 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
/** Updates the clip for each of the task views. */
void clipTaskViews() {
// Update the clip on each task child
- if (Constants.DebugFlags.App.EnableTaskStackClipping) {
- int childCount = getChildCount();
- for (int i = 0; i < childCount - 1; i++) {
- TaskView tv = (TaskView) getChildAt(i);
- TaskView nextTv = null;
- TaskView tmpTv = null;
- int clipBottom = 0;
- if (tv.shouldClipViewInStack()) {
- // Find the next view to clip against
- int nextIndex = i;
- while (nextIndex < getChildCount()) {
- tmpTv = (TaskView) getChildAt(++nextIndex);
- if (tmpTv != null && tmpTv.shouldClipViewInStack()) {
- nextTv = tmpTv;
- break;
- }
+ List<TaskView> taskViews = getTaskViews();
+ int taskViewCount = taskViews.size();
+ for (int i = 0; i < taskViewCount - 1; i++) {
+ TaskView tv = taskViews.get(i);
+ TaskView nextTv = null;
+ TaskView tmpTv = null;
+ int clipBottom = 0;
+ if (tv.shouldClipViewInStack()) {
+ // Find the next view to clip against
+ int nextIndex = i;
+ while (nextIndex < (taskViewCount - 1)) {
+ tmpTv = taskViews.get(++nextIndex);
+ if (tmpTv != null && tmpTv.shouldClipViewInStack()) {
+ nextTv = tmpTv;
+ break;
}
+ }
- // Clip against the next view, this is just an approximation since we are
- // stacked and we can make assumptions about the visibility of the this
- // task relative to the ones in front of it.
- if (nextTv != null) {
- // Map the top edge of next task view into the local space of the current
- // task view to find the clip amount in local space
- mTmpCoord[0] = mTmpCoord[1] = 0;
- Utilities.mapCoordInDescendentToSelf(nextTv, this, mTmpCoord, false);
- Utilities.mapCoordInSelfToDescendent(tv, this, mTmpCoord, mTmpMatrix);
- clipBottom = (int) Math.floor(tv.getMeasuredHeight() - mTmpCoord[1]
- - nextTv.getPaddingTop() - 1);
- }
+ // Clip against the next view, this is just an approximation since we are
+ // stacked and we can make assumptions about the visibility of the this
+ // task relative to the ones in front of it.
+ if (nextTv != null) {
+ // Map the top edge of next task view into the local space of the current
+ // task view to find the clip amount in local space
+ mTmpCoord[0] = mTmpCoord[1] = 0;
+ Utilities.mapCoordInDescendentToSelf(nextTv, this, mTmpCoord, false);
+ Utilities.mapCoordInSelfToDescendent(tv, this, mTmpCoord, mTmpMatrix);
+ clipBottom = (int) Math.floor(tv.getMeasuredHeight() - mTmpCoord[1]
+ - nextTv.getPaddingTop() - 1);
}
- tv.getViewBounds().setClipBottom(clipBottom);
- }
- if (getChildCount() > 0) {
- // The front most task should never be clipped
- TaskView tv = (TaskView) getChildAt(getChildCount() - 1);
- tv.getViewBounds().setClipBottom(0);
}
+ tv.getViewBounds().setClipBottom(clipBottom);
+ }
+ if (taskViewCount > 0) {
+ // The front most task should never be clipped
+ TaskView tv = taskViews.get(taskViewCount - 1);
+ tv.getViewBounds().setClipBottom(0);
}
mStackViewsClipDirty = false;
}
@@ -435,25 +504,20 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
if (0 <= taskIndex && taskIndex < mStack.getTaskCount()) {
mFocusedTaskIndex = taskIndex;
+ mPrevAccessibilityFocusedIndex = taskIndex;
// Focus the view if possible, otherwise, focus the view after we scroll into position
- Task t = mStack.getTasks().get(taskIndex);
- TaskView tv = getChildViewForTask(t);
- Runnable postScrollRunnable = null;
- if (tv != null) {
- tv.setFocusedTask(animateFocusedState);
- } else {
- postScrollRunnable = new Runnable() {
- @Override
- public void run() {
- Task t = mStack.getTasks().get(mFocusedTaskIndex);
- TaskView tv = getChildViewForTask(t);
- if (tv != null) {
- tv.setFocusedTask(animateFocusedState);
- }
+ final Task t = mStack.getTasks().get(mFocusedTaskIndex);
+ Runnable postScrollRunnable = new Runnable() {
+ @Override
+ public void run() {
+ TaskView tv = getChildViewForTask(t);
+ if (tv != null) {
+ tv.setFocusedTask(animateFocusedState);
+ tv.requestAccessibilityFocus();
}
- };
- }
+ }
+ };
// Scroll the view into position (just center it in the curve)
if (scrollToNewPosition) {
@@ -473,24 +537,30 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
* Ensures that there is a task focused, if nothing is focused, then we will use the task
* at the center of the visible stack.
*/
- public boolean ensureFocusedTask() {
+ public boolean ensureFocusedTask(boolean findClosestToCenter) {
if (mFocusedTaskIndex < 0) {
- // If there is no task focused, then find the task that is closes to the center
- // of the screen and use that as the currently focused task
- int x = mLayoutAlgorithm.mStackVisibleRect.centerX();
- int y = mLayoutAlgorithm.mStackVisibleRect.centerY();
- int childCount = getChildCount();
- for (int i = childCount - 1; i >= 0; i--) {
- TaskView tv = (TaskView) getChildAt(i);
- tv.getHitRect(mTmpRect);
- if (mTmpRect.contains(x, y)) {
- mFocusedTaskIndex = mStack.indexOfTask(tv.getTask());
- break;
+ List<TaskView> taskViews = getTaskViews();
+ int taskViewCount = taskViews.size();
+ if (findClosestToCenter) {
+ // If there is no task focused, then find the task that is closes to the center
+ // of the screen and use that as the currently focused task
+ int x = mLayoutAlgorithm.mStackVisibleRect.centerX();
+ int y = mLayoutAlgorithm.mStackVisibleRect.centerY();
+ for (int i = taskViewCount - 1; i >= 0; i--) {
+ TaskView tv = taskViews.get(i);
+ tv.getHitRect(mTmpRect);
+ if (mTmpRect.contains(x, y)) {
+ mFocusedTaskIndex = mStack.indexOfTask(tv.getTask());
+ mPrevAccessibilityFocusedIndex = mFocusedTaskIndex;
+ break;
+ }
}
}
// If we can't find the center task, then use the front most index
- if (mFocusedTaskIndex < 0 && childCount > 0) {
- mFocusedTaskIndex = childCount - 1;
+ if (mFocusedTaskIndex < 0 && taskViewCount > 0) {
+ TaskView tv = taskViews.get(taskViewCount - 1);
+ mFocusedTaskIndex = mStack.indexOfTask(tv.getTask());
+ mPrevAccessibilityFocusedIndex = mFocusedTaskIndex;
}
}
return mFocusedTaskIndex >= 0;
@@ -538,15 +608,17 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
}
mFocusedTaskIndex = -1;
+ mPrevAccessibilityFocusedIndex = -1;
}
@Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event);
- int childCount = getChildCount();
- if (childCount > 0) {
- TaskView backMostTask = (TaskView) getChildAt(0);
- TaskView frontMostTask = (TaskView) getChildAt(childCount - 1);
+ List<TaskView> taskViews = getTaskViews();
+ int taskViewCount = taskViews.size();
+ if (taskViewCount > 0) {
+ TaskView backMostTask = taskViews.get(0);
+ TaskView frontMostTask = taskViews.get(taskViewCount - 1);
event.setFromIndex(mStack.indexOfTask(backMostTask.getTask()));
event.setToIndex(mStack.indexOfTask(frontMostTask.getTask()));
event.setContentDescription(frontMostTask.getTask().activityLabel);
@@ -557,6 +629,53 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
@Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ List<TaskView> taskViews = getTaskViews();
+ int taskViewCount = taskViews.size();
+ if (taskViewCount > 1 && mPrevAccessibilityFocusedIndex != -1) {
+ info.setScrollable(true);
+ if (mPrevAccessibilityFocusedIndex > 0) {
+ info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
+ }
+ if (mPrevAccessibilityFocusedIndex < mStack.getTaskCount() - 1) {
+ info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
+ }
+ }
+ }
+
+ @Override
+ public CharSequence getAccessibilityClassName() {
+ return TaskStackView.class.getName();
+ }
+
+ @Override
+ public boolean performAccessibilityAction(int action, Bundle arguments) {
+ if (super.performAccessibilityAction(action, arguments)) {
+ return true;
+ }
+ if (ensureFocusedTask(false)) {
+ switch (action) {
+ case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
+ if (mPrevAccessibilityFocusedIndex > 0) {
+ focusNextTask(true, false);
+ return true;
+ }
+ }
+ break;
+ case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
+ if (mPrevAccessibilityFocusedIndex < mStack.getTaskCount() - 1) {
+ focusNextTask(false, false);
+ return true;
+ }
+ }
+ break;
+ }
+ }
+ return false;
+ }
+
+ @Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mTouchHandler.onInterceptTouchEvent(ev);
}
@@ -571,12 +690,18 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
return mTouchHandler.onGenericMotionEvent(ev);
}
+ /** Returns the region that touch gestures can be started in. */
+ Rect getTouchableRegion() {
+ return mTaskStackBounds;
+ }
+
@Override
public void computeScroll() {
mStackScroller.computeScroll();
// Synchronize the views
synchronizeStackViewsWithModel();
clipTaskViews();
+ updateDismissButtonPosition();
// Notify accessibility
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED);
}
@@ -633,9 +758,10 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
// Measure each of the TaskViews
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- TaskView tv = (TaskView) getChildAt(i);
+ List<TaskView> taskViews = getTaskViews();
+ int taskViewCount = taskViews.size();
+ for (int i = 0; i < taskViewCount; i++) {
+ TaskView tv = taskViews.get(i);
if (tv.getBackground() != null) {
tv.getBackground().getPadding(mTmpRect);
} else {
@@ -650,6 +776,14 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
MeasureSpec.EXACTLY));
}
+ // Measure the dismiss button
+ if (mDismissAllButton != null) {
+ int taskRectWidth = mLayoutAlgorithm.mTaskRect.width();
+ mDismissAllButton.measure(
+ MeasureSpec.makeMeasureSpec(taskRectWidth, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(mConfig.dismissAllButtonSizePx, MeasureSpec.EXACTLY));
+ }
+
setMeasuredDimension(width, height);
}
@@ -661,9 +795,10 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
// Layout each of the children
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- TaskView tv = (TaskView) getChildAt(i);
+ List<TaskView> taskViews = getTaskViews();
+ int taskViewCount = taskViews.size();
+ for (int i = 0; i < taskViewCount; i++) {
+ TaskView tv = taskViews.get(i);
if (tv.getBackground() != null) {
tv.getBackground().getPadding(mTmpRect);
} else {
@@ -675,6 +810,15 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
mLayoutAlgorithm.mTaskRect.bottom + mTmpRect.bottom);
}
+ // Layout the dismiss button at the top of the screen, and just translate it accordingly
+ // when synchronizing the views with the model to attach it to the bottom of the front-most
+ // task view
+ if (mDismissAllButton != null) {
+ mDismissAllButton.layout(mLayoutAlgorithm.mTaskRect.left, 0,
+ mLayoutAlgorithm.mTaskRect.left + mDismissAllButton.getMeasuredWidth(),
+ mDismissAllButton.getMeasuredHeight());
+ }
+
if (mAwaitingFirstLayout) {
mAwaitingFirstLayout = false;
onFirstLayout();
@@ -688,9 +832,10 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// Find the launch target task
Task launchTargetTask = null;
- int childCount = getChildCount();
- for (int i = childCount - 1; i >= 0; i--) {
- TaskView tv = (TaskView) getChildAt(i);
+ 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 (task.isLaunchTarget) {
launchTargetTask = task;
@@ -699,8 +844,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
// Prepare the first view for its enter animation
- for (int i = childCount - 1; i >= 0; i--) {
- TaskView tv = (TaskView) getChildAt(i);
+ for (int i = taskViewCount - 1; i >= 0; i--) {
+ TaskView tv = taskViews.get(i);
Task task = tv.getTask();
boolean occludesLaunchTarget = (launchTargetTask != null) &&
launchTargetTask.group.isTaskAboveTask(task, launchTargetTask);
@@ -728,7 +873,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
// Start dozing
- mUIDozeTrigger.startDozing();
+ if (!mConfig.multiStackEnabled) {
+ mUIDozeTrigger.startDozing();
+ }
}
/** Requests this task stacks to start it's enter-recents animation */
@@ -743,9 +890,10 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
if (mStack.getTaskCount() > 0) {
// Find the launch target task
Task launchTargetTask = null;
- int childCount = getChildCount();
- for (int i = childCount - 1; i >= 0; i--) {
- TaskView tv = (TaskView) getChildAt(i);
+ 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 (task.isLaunchTarget) {
launchTargetTask = task;
@@ -754,12 +902,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
// Animate all the task views into view
- for (int i = childCount - 1; i >= 0; i--) {
- TaskView tv = (TaskView) getChildAt(i);
+ for (int i = taskViewCount - 1; i >= 0; i--) {
+ TaskView tv = taskViews.get(i);
Task task = tv.getTask();
ctx.currentTaskTransform = new TaskViewTransform();
ctx.currentStackViewIndex = i;
- ctx.currentStackViewCount = childCount;
+ ctx.currentStackViewCount = taskViewCount;
ctx.currentTaskRect = mLayoutAlgorithm.mTaskRect;
ctx.currentTaskOccludesLaunchTarget = (launchTargetTask != null) &&
launchTargetTask.group.isTaskAboveTask(task, launchTargetTask);
@@ -778,29 +926,35 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
SystemServicesProxy ssp = loader.getSystemServicesProxy();
- int childCount = getChildCount();
- if (childCount > 0) {
+ List<TaskView> taskViews = getTaskViews();
+ int taskViewCount = taskViews.size();
+ if (taskViewCount > 0) {
// Focus the first view if accessibility is enabled
if (ssp.isTouchExplorationEnabled()) {
- TaskView tv = ((TaskView) getChildAt(childCount - 1));
+ TaskView tv = taskViews.get(taskViewCount - 1);
tv.requestAccessibilityFocus();
mPrevAccessibilityFocusedIndex = mStack.indexOfTask(tv.getTask());
}
}
// Start the focus animation when alt-tabbing
- if (mConfig.launchedWithAltTab && !mConfig.launchedHasConfigurationChanged) {
- View tv = getChildAt(mFocusedTaskIndex);
+ ArrayList<Task> tasks = mStack.getTasks();
+ if (mConfig.launchedWithAltTab && !mConfig.launchedHasConfigurationChanged &&
+ 0 <= mFocusedTaskIndex && mFocusedTaskIndex < tasks.size()) {
+ TaskView tv = getChildViewForTask(tasks.get(mFocusedTaskIndex));
if (tv != null) {
- ((TaskView) tv).setFocusedTask(true);
+ tv.setFocusedTask(true);
}
}
+
+ // Show the dismiss button
+ showDismissAllButton();
}
});
}
}
- /** Requests this task stacks to start it's exit-recents animation. */
+ /** Requests this task stack to start it's exit-recents animation. */
public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) {
// Stop any scrolling
mStackScroller.stopScroller();
@@ -808,19 +962,44 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// Animate all the task views out of view
ctx.offscreenTranslationY = mLayoutAlgorithm.mViewRect.bottom -
(mLayoutAlgorithm.mTaskRect.top - mLayoutAlgorithm.mViewRect.top);
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- TaskView tv = (TaskView) getChildAt(i);
+ // Animate the dismiss-all button
+ hideDismissAllButton(null);
+
+ List<TaskView> taskViews = getTaskViews();
+ int taskViewCount = taskViews.size();
+ for (int i = 0; i < taskViewCount; i++) {
+ TaskView tv = taskViews.get(i);
tv.startExitToHomeAnimation(ctx);
}
}
+ /** Requests this task stack to start it's dismiss-all animation. */
+ public void startDismissAllAnimation(final Runnable postAnimationRunnable) {
+ // Clear the focused task
+ resetFocusedTask();
+ // Animate the dismiss-all button
+ hideDismissAllButton(new Runnable() {
+ @Override
+ public void run() {
+ List<TaskView> taskViews = getTaskViews();
+ int taskViewCount = taskViews.size();
+ int count = 0;
+ for (int i = taskViewCount - 1; i >= 0; i--) {
+ TaskView tv = taskViews.get(i);
+ tv.startDeleteTaskAnimation(i > 0 ? null : postAnimationRunnable, count * 50);
+ count++;
+ }
+ }
+ });
+ }
+
/** Animates a task view in this stack as it launches. */
public void startLaunchTaskAnimation(TaskView tv, Runnable r, boolean lockToTask) {
Task launchTargetTask = tv.getTask();
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- TaskView t = (TaskView) getChildAt(i);
+ List<TaskView> taskViews = getTaskViews();
+ int taskViewCount = taskViews.size();
+ for (int i = 0; i < taskViewCount; i++) {
+ TaskView t = taskViews.get(i);
if (t == tv) {
t.setClipViewInStack(false);
t.startLaunchTaskAnimation(r, true, true, lockToTask);
@@ -832,6 +1011,69 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
}
+ /** Shows the dismiss button */
+ void showDismissAllButton() {
+ if (mDismissAllButton == null) return;
+
+ if (mDismissAllButtonAnimating || mDismissAllButton.getVisibility() != View.VISIBLE ||
+ Float.compare(mDismissAllButton.getAlpha(), 0f) == 0) {
+ mDismissAllButtonAnimating = true;
+ mDismissAllButton.setVisibility(View.VISIBLE);
+ mDismissAllButton.showClearButton();
+ mDismissAllButton.findViewById(R.id.dismiss_text).setAlpha(1f);
+ mDismissAllButton.setAlpha(0f);
+ mDismissAllButton.animate()
+ .alpha(1f)
+ .setDuration(250)
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ mDismissAllButtonAnimating = false;
+ }
+ })
+ .start();
+ }
+ }
+
+ /** Hides the dismiss button */
+ void hideDismissAllButton(final Runnable postAnimRunnable) {
+ if (mDismissAllButton == null) return;
+
+ mDismissAllButtonAnimating = true;
+ mDismissAllButton.animate()
+ .alpha(0f)
+ .setDuration(200)
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ mDismissAllButtonAnimating = false;
+ mDismissAllButton.setVisibility(View.GONE);
+ if (postAnimRunnable != null) {
+ postAnimRunnable.run();
+ }
+ }
+ })
+ .start();
+ }
+
+ /** Updates the dismiss button position */
+ void updateDismissButtonPosition() {
+ if (mDismissAllButton == null) return;
+
+ // Update the position of the clear-all button to hang it off the first task view
+ if (mStack.getTaskCount() > 0) {
+ mTmpCoord[0] = mTmpCoord[1] = 0;
+ TaskView tv = getChildViewForTask(mStack.getFrontMostTask());
+ TaskViewTransform transform = mCurrentTaskTransforms.get(mStack.getTaskCount() - 1);
+ if (tv != null && transform.visible) {
+ Utilities.mapCoordInDescendentToSelf(tv, this, mTmpCoord, false);
+ mDismissAllButton.setTranslationY(mTmpCoord[1] + (tv.getScaleY() * tv.getHeight()));
+ mDismissAllButton.setTranslationX(-(mLayoutAlgorithm.mStackRect.width() -
+ transform.rect.width()) / 2f);
+ }
+ }
+ }
+
/** Final callback after Recents is finally hidden. */
void onRecentsHidden() {
reset();
@@ -847,6 +1089,20 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
mUIDozeTrigger.poke();
}
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ mLayersDisabled = false;
+ super.dispatchDraw(canvas);
+ }
+
+ public void disableLayersForOneFrame() {
+ mLayersDisabled = true;
+ List<TaskView> taskViews = getTaskViews();
+ for (int i = 0; i < taskViews.size(); i++) {
+ taskViews.get(i).disableLayersForOneFrame();
+ }
+ }
+
/**** TaskStackCallbacks Implementation ****/
@Override
@@ -908,12 +1164,30 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
shouldFinishActivity = (mStack.getTaskCount() == 0);
}
if (shouldFinishActivity) {
- mCb.onAllTaskViewsDismissed();
+ mCb.onAllTaskViewsDismissed(null);
}
+ } else {
+ // Fade the dismiss button back in
+ showDismissAllButton();
}
}
@Override
+ public void onStackAllTasksRemoved(TaskStack stack, final ArrayList<Task> removedTasks) {
+ // Announce for accessibility
+ String msg = getContext().getString(R.string.accessibility_recents_all_items_dismissed);
+ announceForAccessibility(msg);
+
+ startDismissAllAnimation(new Runnable() {
+ @Override
+ public void run() {
+ // Notify that all tasks have been removed
+ mCb.onAllTaskViewsDismissed(removedTasks);
+ }
+ });
+ }
+
+ @Override
public void onStackFiltered(TaskStack newStack, final ArrayList<Task> curTasks,
Task filteredTask) {
/*
@@ -998,6 +1272,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// Detach the view from the hierarchy
detachViewFromParent(tv);
+ // Update the task views list after removing the task view
+ updateTaskViewsList();
// Reset the view properties
tv.resetViewProperties();
@@ -1019,7 +1295,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
RecentsTaskLoader.getInstance().loadTaskData(task);
// If the doze trigger has already fired, then update the state for this task view
- if (mUIDozeTrigger.hasTriggered()) {
+ if (mConfig.multiStackEnabled || mUIDozeTrigger.hasTriggered()) {
tv.setNoUserInteractionState();
}
@@ -1032,11 +1308,14 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
int insertIndex = -1;
int taskIndex = mStack.indexOfTask(task);
if (taskIndex != -1) {
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- Task tvTask = ((TaskView) getChildAt(i)).getTask();
+
+ List<TaskView> taskViews = getTaskViews();
+ int taskViewCount = taskViews.size();
+ for (int i = 0; i < taskViewCount; i++) {
+ Task tvTask = taskViews.get(i).getTask();
if (taskIndex < mStack.indexOfTask(tvTask)) {
- insertIndex = i;
+ // Offset by 1 if we have a dismiss-all button
+ insertIndex = i + (Constants.DebugFlags.App.EnableDismissAll ? 1 : 0);
break;
}
}
@@ -1051,6 +1330,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
tv.requestLayout();
}
}
+ // Update the task views list after adding the new task view
+ updateTaskViewsList();
// Set the new state for this view, including the callbacks and view clipping
tv.setCallbacks(this);
@@ -1133,6 +1414,13 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
}
+ @Override
+ public void onTaskResize(TaskView tv) {
+ if (mCb != null) {
+ mCb.onTaskResize(tv.getTask());
+ }
+ }
+
/**** TaskStackViewScroller.TaskStackViewScrollerCallbacks ****/
@Override
@@ -1163,7 +1451,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
public void run() {
mStack.removeTask(t);
}
- });
+ }, 0);
} else {
// Otherwise, remove the task from the stack immediately
mStack.removeTask(t);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewFilterAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewFilterAlgorithm.java
index 9cd5ae4..614ca53 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewFilterAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewFilterAlgorithm.java
@@ -22,6 +22,7 @@ import com.android.systemui.recents.model.Task;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
/* The layout logic for a TaskStackView */
public class TaskStackViewFilterAlgorithm {
@@ -142,9 +143,10 @@ public class TaskStackViewFilterAlgorithm {
// the new stack) or to their final positions in the new stack
int offset = 0;
int movement = 0;
- int childCount = mStackView.getChildCount();
- for (int i = 0; i < childCount; i++) {
- TaskView tv = (TaskView) mStackView.getChildAt(i);
+ List<TaskView> taskViews = mStackView.getTaskViews();
+ int taskViewCount = taskViews.size();
+ for (int i = 0; i < taskViewCount; i++) {
+ TaskView tv = taskViews.get(i);
Task task = tv.getTask();
int taskIndex = tasks.indexOf(task);
TaskViewTransform toTransform;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
index 49b9129..f6df881 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
@@ -17,6 +17,7 @@
package com.android.systemui.recents.views;
import android.graphics.Rect;
+import com.android.systemui.recents.Constants;
import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.model.Task;
@@ -131,6 +132,11 @@ public class TaskStackViewLayoutAlgorithm {
float pNavBarOffset = pAtBottomOfStackRect -
screenYToCurveProgress(mStackVisibleRect.bottom - (mStackVisibleRect.bottom -
mStackRect.bottom));
+ float pDismissAllButtonOffset = 0f;
+ if (Constants.DebugFlags.App.EnableDismissAll) {
+ pDismissAllButtonOffset = pAtBottomOfStackRect -
+ screenYToCurveProgress(mStackVisibleRect.bottom - mConfig.dismissAllButtonSizePx);
+ }
// Update the task offsets
float pAtBackMostCardTop = 0.5f;
@@ -148,7 +154,8 @@ public class TaskStackViewLayoutAlgorithm {
}
}
- mMaxScrollP = pAtFrontMostCardTop - ((1f - pTaskHeightOffset - pNavBarOffset));
+ mMaxScrollP = pAtFrontMostCardTop + pDismissAllButtonOffset -
+ ((1f - pTaskHeightOffset - pNavBarOffset));
mMinScrollP = tasks.size() == 1 ? Math.max(mMaxScrollP, 0f) : 0f;
if (launchedWithAltTab && launchedFromHome) {
// Center the top most task, since that will be focused first
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 f7067be..fabc86d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
@@ -96,23 +96,13 @@ public class TaskStackViewScroller {
}
return false;
}
- /** Bounds the current scroll if necessary, but does not synchronize the stack view with the model. */
- public boolean boundScrollRaw() {
- float curScroll = getStackScroll();
- float newScroll = getBoundedStackScroll(curScroll);
- if (Float.compare(newScroll, curScroll) != 0) {
- setStackScrollRaw(newScroll);
- return true;
- }
- return false;
- }
/** Returns the bounded stack scroll */
float getBoundedStackScroll(float scroll) {
return Math.max(mLayoutAlgorithm.mMinScrollP, Math.min(mLayoutAlgorithm.mMaxScrollP, scroll));
}
- /** Returns the amount that the aboslute value of how much the scroll is out of bounds. */
+ /** Returns the amount that the absolute value of how much the scroll is out of bounds. */
float getScrollAmountOutOfBounds(float scroll) {
if (scroll < mLayoutAlgorithm.mMinScrollP) {
return Math.abs(scroll - mLayoutAlgorithm.mMinScrollP);
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 59e38f4..13bdbd2 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -24,8 +24,11 @@ import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewParent;
import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsConfiguration;
+import java.util.List;
+
/* Handles touch events for a TaskStackView. */
class TaskStackViewTouchHandler implements SwipeHelper.Callback {
static int INACTIVE_POINTER_ID = -1;
@@ -51,6 +54,8 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
int mScrollTouchSlop;
// The page touch slop is used to calculate when we start swiping
float mPagingTouchSlop;
+ // Used to calculate when a tap is outside a task view rectangle.
+ final int mWindowTouchSlop;
SwipeHelper mSwipeHelper;
boolean mInterceptedBySwipeHelper;
@@ -62,6 +67,7 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
mScrollTouchSlop = configuration.getScaledTouchSlop();
mPagingTouchSlop = configuration.getScaledPagingTouchSlop();
+ mWindowTouchSlop = configuration.getScaledWindowTouchSlop();
mSv = sv;
mScroller = scroller;
mConfig = config;
@@ -93,9 +99,10 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
/** Returns the view at the specified coordinates */
TaskView findViewAtPoint(int x, int y) {
- int childCount = mSv.getChildCount();
- for (int i = childCount - 1; i >= 0; i--) {
- TaskView tv = (TaskView) mSv.getChildAt(i);
+ List<TaskView> taskViews = mSv.getTaskViews();
+ int taskViewCount = taskViews.size();
+ for (int i = taskViewCount - 1; i >= 0; i--) {
+ TaskView tv = taskViews.get(i);
if (tv.getVisibility() == View.VISIBLE) {
if (mSv.isTransformedTouchPointInView(x, y, tv)) {
return tv;
@@ -115,11 +122,21 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
/** Touch preprocessing for handling below */
public boolean onInterceptTouchEvent(MotionEvent ev) {
// Return early if we have no children
- boolean hasChildren = (mSv.getChildCount() > 0);
- if (!hasChildren) {
+ boolean hasTaskViews = (mSv.getTaskViews().size() > 0);
+ if (!hasTaskViews) {
return false;
}
+ int action = ev.getAction();
+ if (mConfig.multiStackEnabled) {
+ // Check if we are within the bounds of the stack view contents
+ if ((action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
+ if (!mSv.getTouchableRegion().contains((int) ev.getX(), (int) ev.getY())) {
+ return false;
+ }
+ }
+ }
+
// Pass through to swipe helper if we are swiping
mInterceptedBySwipeHelper = mSwipeHelper.onInterceptTouchEvent(ev);
if (mInterceptedBySwipeHelper) {
@@ -128,7 +145,6 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
boolean wasScrolling = mScroller.isScrolling() ||
(mScroller.mScrollAnimator != null && mScroller.mScrollAnimator.isRunning());
- int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
// Save the touch down info
@@ -190,11 +206,21 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
/** Handles touch events once we have intercepted them */
public boolean onTouchEvent(MotionEvent ev) {
// Short circuit if we have no children
- boolean hasChildren = (mSv.getChildCount() > 0);
- if (!hasChildren) {
+ boolean hasTaskViews = (mSv.getTaskViews().size() > 0);
+ if (!hasTaskViews) {
return false;
}
+ int action = ev.getAction();
+ if (mConfig.multiStackEnabled) {
+ // Check if we are within the bounds of the stack view contents
+ if ((action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
+ if (!mSv.getTouchableRegion().contains((int) ev.getX(), (int) ev.getY())) {
+ return false;
+ }
+ }
+ }
+
// Pass through to swipe helper if we are swiping
if (mInterceptedBySwipeHelper && mSwipeHelper.onTouchEvent(ev)) {
return true;
@@ -203,7 +229,6 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
// Update the velocity tracker
initVelocityTrackerIfNotExists();
- int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
// Save the touch down info
@@ -279,7 +304,7 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
float overscrollRangePct = Math.abs((float) velocity / mMaximumVelocity);
int overscrollRange = (int) (Math.min(1f, overscrollRangePct) *
(Constants.Values.TaskStackView.TaskStackMaxOverscrollRange -
- Constants.Values.TaskStackView.TaskStackMinOverscrollRange));
+ Constants.Values.TaskStackView.TaskStackMinOverscrollRange));
mScroller.mScroller.fling(0,
mScroller.progressToScrollRange(mScroller.getStackScroll()),
0, velocity,
@@ -293,6 +318,9 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
} else if (mScroller.isScrollOutOfBounds()) {
// Animate the scroll back into bounds
mScroller.animateBoundScroll();
+ } else if (mActiveTaskView == null) {
+ // This tap didn't start on a task.
+ maybeHideRecentsFromBackgroundTap((int) ev.getX(), (int) ev.getY());
}
mActivePointerId = INACTIVE_POINTER_ID;
@@ -330,6 +358,34 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
return true;
}
+ /** Hides recents if the up event at (x, y) is a tap on the background area. */
+ void maybeHideRecentsFromBackgroundTap(int x, int y) {
+ // Ignore the up event if it's too far from its start position. The user might have been
+ // trying to scroll or swipe.
+ int dx = Math.abs(mInitialMotionX - x);
+ int dy = Math.abs(mInitialMotionY - y);
+ if (dx > mScrollTouchSlop || dy > mScrollTouchSlop) {
+ return;
+ }
+
+ // Shift the tap position toward the center of the task stack and check to see if it would
+ // have hit a view. The user might have tried to tap on a task and missed slightly.
+ int shiftedX = x;
+ if (x > mSv.getTouchableRegion().centerX()) {
+ shiftedX -= mWindowTouchSlop;
+ } else {
+ shiftedX += mWindowTouchSlop;
+ }
+ if (findViewAtPoint(shiftedX, y) != null) {
+ return;
+ }
+
+ // The user intentionally tapped on the background, which is like a tap on the "desktop".
+ // Hide recents and transition to the launcher.
+ Recents recents = Recents.getInstanceAndStartIfNeeded(mSv.getContext());
+ recents.hideRecents(false /* altTab */, true /* homeKey */);
+ }
+
/** Handles generic motion events */
public boolean onGenericMotionEvent(MotionEvent ev) {
if ((ev.getSource() & InputDevice.SOURCE_CLASS_POINTER) ==
@@ -340,11 +396,11 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
// Find the front most task and scroll the next task to the front
float vScroll = ev.getAxisValue(MotionEvent.AXIS_VSCROLL);
if (vScroll > 0) {
- if (mSv.ensureFocusedTask()) {
+ if (mSv.ensureFocusedTask(true)) {
mSv.focusNextTask(true, false);
}
} else {
- if (mSv.ensureFocusedTask()) {
+ if (mSv.ensureFocusedTask(true)) {
mSv.focusNextTask(false, false);
}
}
@@ -378,6 +434,8 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
+ // Fade out the dismiss button
+ mSv.hideDismissAllButton(null);
}
@Override
@@ -403,6 +461,8 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
tv.setClipViewInStack(true);
// Re-enable touch events from this task view
tv.setTouchEnabled(true);
+ // Restore the dismiss button
+ mSv.showDismissAllButton();
}
@Override
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 faa728d..5906ef1 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -22,6 +22,7 @@ import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.*;
import android.util.AttributeSet;
+import android.view.accessibility.AccessibilityManager;
import android.view.View;
import android.view.ViewOutlineProvider;
import android.view.animation.AccelerateInterpolator;
@@ -45,6 +46,8 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
public void onTaskViewDismissed(TaskView tv);
public void onTaskViewClipStateChanged(TaskView tv);
public void onTaskViewFocusChanged(TaskView tv, boolean focused);
+
+ public void onTaskResize(TaskView tv);
}
RecentsConfiguration mConfig;
@@ -364,7 +367,6 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
.setStartDelay(delay)
.setDuration(duration)
.setInterpolator(PhoneStatusBar.ALPHA_IN)
- .withLayer()
.start();
}
@@ -381,6 +383,11 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
ctx.postAnimationTrigger.increment();
}
+ /** Animates this task view away when dismissing all tasks. */
+ void startDismissAllAnimation() {
+ dismissTask();
+ }
+
/** Animates this task view as it exits recents */
void startLaunchTaskAnimation(final Runnable postAnimRunnable, boolean isLaunchingTask,
boolean occludesLaunchTarget, boolean lockToTask) {
@@ -408,7 +415,6 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
.setStartDelay(0)
.setDuration(mConfig.taskViewExitToAppDuration)
.setInterpolator(mConfig.fastOutLinearInInterpolator)
- .withLayer()
.start();
} else {
// Hide the dismiss button
@@ -428,25 +434,22 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
}
/** Animates the deletion of this task view */
- void startDeleteTaskAnimation(final Runnable r) {
+ void startDeleteTaskAnimation(final Runnable r, int delay) {
// Disabling clipping with the stack while the view is animating away
setClipViewInStack(false);
animate().translationX(mConfig.taskViewRemoveAnimTranslationXPx)
.alpha(0f)
- .setStartDelay(0)
+ .setStartDelay(delay)
.setUpdateListener(null)
.setInterpolator(mConfig.fastOutSlowInInterpolator)
.setDuration(mConfig.taskViewRemoveAnimDuration)
.withEndAction(new Runnable() {
@Override
public void run() {
- // We just throw this into a runnable because starting a view property
- // animation using layers can cause inconsisten results if we try and
- // update the layers while the animation is running. In some cases,
- // the runnabled passed in may start an animation which also uses layers
- // so we defer all this by posting this.
- r.run();
+ if (r != null) {
+ r.run();
+ }
// Re-enable clipping with the stack (we will reuse this view)
setClipViewInStack(true);
@@ -455,6 +458,11 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
.start();
}
+ /** Enables/disables handling touch on this task view. */
+ void setTouchEnabled(boolean enabled) {
+ setOnClickListener(enabled ? this : null);
+ }
+
/** Animates this task view if the user does not interact with the stack after a certain time. */
void startNoUserInteractionAnimation() {
mHeaderView.startNoUserInteractionAnimation();
@@ -481,7 +489,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
mCb.onTaskViewDismissed(tv);
}
}
- });
+ }, 0);
}
/**
@@ -641,6 +649,10 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
}
}
+ public void disableLayersForOneFrame() {
+ mHeaderView.disableLayersForOneFrame();
+ }
+
/**** TaskCallbacks Implementation ****/
/** Binds this task view to the task */
@@ -663,14 +675,17 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
mThumbnailView.rebindToTask(mTask);
mHeaderView.rebindToTask(mTask);
// Rebind any listeners
- mHeaderView.mApplicationIcon.setOnClickListener(this);
+ AccessibilityManager am = (AccessibilityManager) getContext().
+ getSystemService(Context.ACCESSIBILITY_SERVICE);
+ if (Constants.DebugFlags.App.EnableTaskFiltering || (am != null && am.isEnabled())) {
+ mHeaderView.mApplicationIcon.setOnClickListener(this);
+ }
mHeaderView.mDismissButton.setOnClickListener(this);
- mActionButtonView.setOnClickListener(this);
- if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) {
- if (mConfig.developerOptionsEnabled) {
- mHeaderView.mApplicationIcon.setOnLongClickListener(this);
- }
+ if (mConfig.multiStackEnabled) {
+ mHeaderView.mMoveTaskButton.setOnClickListener(this);
}
+ mActionButtonView.setOnClickListener(this);
+ mHeaderView.mApplicationIcon.setOnLongClickListener(this);
}
mTaskDataLoaded = true;
}
@@ -685,17 +700,18 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
// Unbind any listeners
mHeaderView.mApplicationIcon.setOnClickListener(null);
mHeaderView.mDismissButton.setOnClickListener(null);
- mActionButtonView.setOnClickListener(null);
- if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) {
- mHeaderView.mApplicationIcon.setOnLongClickListener(null);
+ if (mConfig.multiStackEnabled) {
+ mHeaderView.mMoveTaskButton.setOnClickListener(null);
}
+ mActionButtonView.setOnClickListener(null);
+ mHeaderView.mApplicationIcon.setOnLongClickListener(null);
}
mTaskDataLoaded = false;
}
- /** Enables/disables handling touch on this task view. */
- void setTouchEnabled(boolean enabled) {
- setOnClickListener(enabled ? this : null);
+ @Override
+ public void onMultiStackDebugTaskStackIdChanged() {
+ mHeaderView.rebindToTask(mTask);
}
/**** View.OnClickListener Implementation ****/
@@ -709,12 +725,26 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
postDelayed(new Runnable() {
@Override
public void run() {
- if (Constants.DebugFlags.App.EnableTaskFiltering && v == mHeaderView.mApplicationIcon) {
- if (mCb != null) {
- mCb.onTaskViewAppIconClicked(tv);
+ if (v == mHeaderView.mApplicationIcon) {
+ if (Constants.DebugFlags.App.EnableTaskFiltering) {
+ if (mCb != null) {
+ mCb.onTaskViewAppIconClicked(tv);
+ }
+ } else {
+ AccessibilityManager am = (AccessibilityManager) getContext().
+ getSystemService(Context.ACCESSIBILITY_SERVICE);
+ if (am != null && am.isEnabled()) {
+ if (mCb != null) {
+ mCb.onTaskViewAppInfoClicked(tv);
+ }
+ }
}
} else if (v == mHeaderView.mDismissButton) {
dismissTask();
+ } else if (v == mHeaderView.mMoveTaskButton) {
+ if (mCb != null) {
+ mCb.onTaskResize(tv);
+ }
}
}
}, 125);
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 05f6f40..6db4020 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
@@ -24,20 +24,19 @@ import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.ColorStateList;
-import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.RippleDrawable;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.PorterDuffXfermode;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.GradientDrawable;
-import android.graphics.drawable.RippleDrawable;
+import android.graphics.Rect;
import android.util.AttributeSet;
-import android.view.MotionEvent;
import android.view.View;
import android.view.ViewOutlineProvider;
import android.widget.FrameLayout;
@@ -46,7 +45,9 @@ import android.widget.TextView;
import com.android.systemui.R;
import com.android.systemui.recents.Constants;
import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.Utilities;
+import com.android.systemui.recents.model.RecentsTaskLoader;
import com.android.systemui.recents.model.Task;
@@ -54,8 +55,10 @@ import com.android.systemui.recents.model.Task;
public class TaskViewHeader extends FrameLayout {
RecentsConfiguration mConfig;
+ private SystemServicesProxy mSsp;
// Header views
+ ImageView mMoveTaskButton;
ImageView mDismissButton;
ImageView mApplicationIcon;
TextView mActivityDescription;
@@ -78,6 +81,8 @@ public class TaskViewHeader extends FrameLayout {
Paint mDimLayerPaint = new Paint();
PorterDuffColorFilter mDimColorFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_ATOP);
+ boolean mLayersDisabled;
+
public TaskViewHeader(Context context) {
this(context, null);
}
@@ -93,6 +98,7 @@ public class TaskViewHeader extends FrameLayout {
public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mConfig = RecentsConfiguration.getInstance();
+ mSsp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
setWillNotDraw(false);
setClipToOutline(true);
setOutlineProvider(new ViewOutlineProvider() {
@@ -103,11 +109,10 @@ public class TaskViewHeader extends FrameLayout {
});
// Load the dismiss resources
- Resources res = context.getResources();
- mLightDismissDrawable = res.getDrawable(R.drawable.recents_dismiss_light);
- mDarkDismissDrawable = res.getDrawable(R.drawable.recents_dismiss_dark);
+ mLightDismissDrawable = context.getDrawable(R.drawable.recents_dismiss_light);
+ mDarkDismissDrawable = context.getDrawable(R.drawable.recents_dismiss_dark);
mDismissContentDescription =
- res.getString(R.string.accessibility_recents_item_will_be_dismissed);
+ context.getString(R.string.accessibility_recents_item_will_be_dismissed);
// Configure the highlight paint
if (sHighlightPaint == null) {
@@ -121,19 +126,12 @@ public class TaskViewHeader extends FrameLayout {
}
@Override
- public boolean onTouchEvent(MotionEvent event) {
- // We ignore taps on the task bar except on the filter and dismiss buttons
- if (!Constants.DebugFlags.App.EnableTaskBarTouchEvents) return true;
-
- return super.onTouchEvent(event);
- }
-
- @Override
protected void onFinishInflate() {
// Initialize the icon and description views
mApplicationIcon = (ImageView) findViewById(R.id.application_icon);
mActivityDescription = (TextView) findViewById(R.id.activity_description);
mDismissButton = (ImageView) findViewById(R.id.dismiss_task);
+ mMoveTaskButton = (ImageView) findViewById(R.id.move_task);
// Hide the backgrounds if they are ripple drawables
if (!Constants.DebugFlags.App.EnableTaskFiltering) {
@@ -177,7 +175,9 @@ public class TaskViewHeader extends FrameLayout {
void setDimAlpha(int alpha) {
mDimColorFilter.setColor(Color.argb(alpha, 0, 0, 0));
mDimLayerPaint.setColorFilter(mDimColorFilter);
- setLayerType(LAYER_TYPE_HARDWARE, mDimLayerPaint);
+ if (!mLayersDisabled) {
+ setLayerType(LAYER_TYPE_HARDWARE, mDimLayerPaint);
+ }
}
/** Returns the secondary color for a primary color. */
@@ -195,10 +195,11 @@ public class TaskViewHeader extends FrameLayout {
} else if (t.applicationIcon != null) {
mApplicationIcon.setImageDrawable(t.applicationIcon);
}
- mApplicationIcon.setContentDescription(t.activityLabel);
if (!mActivityDescription.getText().toString().equals(t.activityLabel)) {
mActivityDescription.setText(t.activityLabel);
}
+ mActivityDescription.setContentDescription(t.contentDescription);
+
// Try and apply the system ui tint
int existingBgColor = (getBackground() instanceof ColorDrawable) ?
((ColorDrawable) getBackground()).getColor() : 0;
@@ -213,7 +214,44 @@ public class TaskViewHeader extends FrameLayout {
mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ?
mLightDismissDrawable : mDarkDismissDrawable);
mDismissButton.setContentDescription(String.format(mDismissContentDescription,
- t.activityLabel));
+ t.contentDescription));
+ mMoveTaskButton.setVisibility((mConfig.multiStackEnabled) ? View.VISIBLE : View.INVISIBLE);
+ if (mConfig.multiStackEnabled) {
+ updateResizeTaskBarIcon(t);
+ }
+ }
+
+ /** Updates the resize task bar button. */
+ void updateResizeTaskBarIcon(Task t) {
+ Rect display = mSsp.getWindowRect();
+ Rect taskRect = mSsp.getTaskBounds(t.key.stackId);
+ int resId = R.drawable.star;
+ if (display.equals(taskRect) || taskRect.isEmpty()) {
+ resId = R.drawable.vector_drawable_place_fullscreen;
+ } else {
+ boolean top = display.top == taskRect.top;
+ boolean bottom = display.bottom == taskRect.bottom;
+ boolean left = display.left == taskRect.left;
+ boolean right = display.right == taskRect.right;
+ if (top && bottom && left) {
+ resId = R.drawable.vector_drawable_place_left;
+ } else if (top && bottom && right) {
+ resId = R.drawable.vector_drawable_place_right;
+ } else if (top && left && right) {
+ resId = R.drawable.vector_drawable_place_top;
+ } else if (bottom && left && right) {
+ resId = R.drawable.vector_drawable_place_bottom;
+ } else if (top && right) {
+ resId = R.drawable.vector_drawable_place_top_right;
+ } else if (top && left) {
+ resId = R.drawable.vector_drawable_place_top_left;
+ } else if (bottom && right) {
+ resId = R.drawable.vector_drawable_place_bottom_right;
+ } else if (bottom && left) {
+ resId = R.drawable.vector_drawable_place_bottom_left;
+ }
+ }
+ mMoveTaskButton.setImageResource(resId);
}
/** Unbinds the bar view from the task */
@@ -230,7 +268,6 @@ public class TaskViewHeader extends FrameLayout {
.setStartDelay(0)
.setInterpolator(mConfig.fastOutSlowInInterpolator)
.setDuration(mConfig.taskViewExitToAppDuration)
- .withLayer()
.start();
}
}
@@ -245,7 +282,6 @@ public class TaskViewHeader extends FrameLayout {
.setStartDelay(0)
.setInterpolator(mConfig.fastOutLinearInInterpolator)
.setDuration(mConfig.taskViewEnterFromAppDuration)
- .withLayer()
.start();
}
}
@@ -272,6 +308,28 @@ public class TaskViewHeader extends FrameLayout {
return new int[] {};
}
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ super.dispatchDraw(canvas);
+ if (mLayersDisabled) {
+ mLayersDisabled = false;
+ postOnAnimation(new Runnable() {
+ @Override
+ public void run() {
+ mLayersDisabled = false;
+ setLayerType(LAYER_TYPE_HARDWARE, mDimLayerPaint);
+ }
+ });
+ }
+ }
+
+ public void disableLayersForOneFrame() {
+ mLayersDisabled = true;
+
+ // Disable layer for a frame so we can draw our first frame faster.
+ setLayerType(LAYER_TYPE_NONE, null);
+ }
+
/** Notifies the associated TaskView has been focused. */
void onTaskViewFocusChanged(boolean focused, boolean animateFocusedState) {
// If we are not animating the visible state, just return
@@ -284,23 +342,26 @@ public class TaskViewHeader extends FrameLayout {
}
if (focused) {
+ int currentColor = mBackgroundColor;
int secondaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark);
int[][] states = new int[][] {
+ new int[] {},
new int[] { android.R.attr.state_enabled },
new int[] { android.R.attr.state_pressed }
};
int[] newStates = new int[]{
+ 0,
android.R.attr.state_enabled,
android.R.attr.state_pressed
};
int[] colors = new int[] {
+ currentColor,
secondaryColor,
secondaryColor
};
mBackground.setColor(new ColorStateList(states, colors));
mBackground.setState(newStates);
// Pulse the background color
- int currentColor = mBackgroundColor;
int lightPrimaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark);
ValueAnimator backgroundColor = ValueAnimator.ofObject(new ArgbEvaluator(),
currentColor, lightPrimaryColor);
@@ -327,7 +388,7 @@ public class TaskViewHeader extends FrameLayout {
mFocusAnimator = new AnimatorSet();
mFocusAnimator.playTogether(backgroundColor, translation);
- mFocusAnimator.setStartDelay(750);
+ mFocusAnimator.setStartDelay(150);
mFocusAnimator.setDuration(750);
mFocusAnimator.start();
} else {
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 42c0f9f..a55e026 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
@@ -21,7 +21,6 @@ import android.graphics.Rect;
import android.view.View;
import android.view.ViewPropertyAnimator;
import android.view.animation.Interpolator;
-import com.android.systemui.recents.Constants;
/* The transform state for a task view */
@@ -133,6 +132,8 @@ public class TaskViewTransform {
/** Reset the transform on a view. */
public static void reset(View v) {
+ // Cancel any running animations
+ v.animate().cancel();
v.setTranslationX(0f);
v.setTranslationY(0f);
v.setTranslationZ(0f);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index d9fea47..9e3cf37 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -25,6 +25,7 @@ import android.app.Notification;
import android.app.Notification.BigPictureStyle;
import android.app.NotificationManager;
import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
@@ -59,6 +60,7 @@ import android.widget.ImageView;
import com.android.systemui.R;
import java.io.File;
+import java.io.FileOutputStream;
import java.io.OutputStream;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
@@ -122,7 +124,7 @@ class SaveImageInBackgroundTask extends AsyncTask<SaveImageInBackgroundData, Voi
// Prepare all the output metadata
mImageTime = System.currentTimeMillis();
- String imageDate = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date(mImageTime));
+ String imageDate = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date(mImageTime));
mImageFileName = String.format(SCREENSHOT_FILE_NAME_TEMPLATE, imageDate);
mScreenshotDir = new File(Environment.getExternalStoragePublicDirectory(
@@ -136,21 +138,31 @@ class SaveImageInBackgroundTask extends AsyncTask<SaveImageInBackgroundData, Voi
int previewWidth = data.previewWidth;
int previewHeight = data.previewheight;
- final int shortSide = mImageWidth < mImageHeight ? mImageWidth : mImageHeight;
- Bitmap preview = Bitmap.createBitmap(previewWidth, previewHeight, data.image.getConfig());
- Canvas c = new Canvas(preview);
+ Canvas c = new Canvas();
Paint paint = new Paint();
ColorMatrix desat = new ColorMatrix();
desat.setSaturation(0.25f);
paint.setColorFilter(new ColorMatrixColorFilter(desat));
Matrix matrix = new Matrix();
- matrix.postTranslate((previewWidth - mImageWidth) / 2,
- (previewHeight - mImageHeight) / 2);
+ 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(0x40FFFFFF);
+ c.drawColor(overlayColor);
c.setBitmap(null);
- Bitmap croppedIcon = Bitmap.createScaledBitmap(preview, iconSize, iconSize, true);
+ // 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);
// Show the intermediate notification
mTickerAddSpace = !mTickerAddSpace;
@@ -168,7 +180,7 @@ class SaveImageInBackgroundTask extends AsyncTask<SaveImageInBackgroundData, Voi
.setColor(r.getColor(com.android.internal.R.color.system_notification_accent_color));
mNotificationStyle = new Notification.BigPictureStyle()
- .bigPicture(preview);
+ .bigPicture(picture.createAshmemBitmap());
mNotificationBuilder.setStyle(mNotificationStyle);
// For "public" situations we want to show all the same info but
@@ -191,9 +203,9 @@ class SaveImageInBackgroundTask extends AsyncTask<SaveImageInBackgroundData, Voi
// On the tablet, the large icon makes the notification appear as if it is clickable (and
// on small devices, the large icon is not shown) so defer showing the large icon until
// we compose the final post-save notification below.
- mNotificationBuilder.setLargeIcon(croppedIcon);
+ mNotificationBuilder.setLargeIcon(icon.createAshmemBitmap());
// But we still don't set it for the expanded view, allowing the smallIcon to show here.
- mNotificationStyle.bigLargeIcon(null);
+ mNotificationStyle.bigLargeIcon((Bitmap) null);
}
@Override
@@ -221,6 +233,12 @@ class SaveImageInBackgroundTask extends AsyncTask<SaveImageInBackgroundData, Voi
// for DATE_TAKEN
long dateSeconds = mImageTime / 1000;
+ // Save
+ OutputStream out = new FileOutputStream(mImageFilePath);
+ image.compress(Bitmap.CompressFormat.PNG, 100, out);
+ out.flush();
+ out.close();
+
// Save the screenshot to the MediaStore
ContentValues values = new ContentValues();
ContentResolver resolver = context.getContentResolver();
@@ -233,8 +251,10 @@ class SaveImageInBackgroundTask extends AsyncTask<SaveImageInBackgroundData, Voi
values.put(MediaStore.Images.ImageColumns.MIME_TYPE, "image/png");
values.put(MediaStore.Images.ImageColumns.WIDTH, mImageWidth);
values.put(MediaStore.Images.ImageColumns.HEIGHT, mImageHeight);
+ values.put(MediaStore.Images.ImageColumns.SIZE, new File(mImageFilePath).length());
Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
+ // Create a share intent
String subjectDate = DateFormat.getDateTimeInstance().format(new Date(mImageTime));
String subject = String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate);
Intent sharingIntent = new Intent(Intent.ACTION_SEND);
@@ -242,24 +262,28 @@ class SaveImageInBackgroundTask extends AsyncTask<SaveImageInBackgroundData, Voi
sharingIntent.putExtra(Intent.EXTRA_STREAM, uri);
sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
- Intent chooserIntent = Intent.createChooser(sharingIntent, null);
+ // Create a share action for the notification
+ final PendingIntent callback = PendingIntent.getBroadcast(context, 0,
+ new Intent(context, GlobalScreenshot.TargetChosenReceiver.class)
+ .putExtra(GlobalScreenshot.CANCEL_ID, mNotificationId),
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
+ Intent chooserIntent = Intent.createChooser(sharingIntent, null,
+ callback.getIntentSender());
chooserIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK
| Intent.FLAG_ACTIVITY_NEW_TASK);
-
- mNotificationBuilder.addAction(R.drawable.ic_menu_share,
- r.getString(com.android.internal.R.string.share),
- PendingIntent.getActivity(context, 0, chooserIntent,
- PendingIntent.FLAG_CANCEL_CURRENT));
-
- OutputStream out = resolver.openOutputStream(uri);
- image.compress(Bitmap.CompressFormat.PNG, 100, out);
- out.flush();
- out.close();
-
- // update file size in the database
- values.clear();
- values.put(MediaStore.Images.ImageColumns.SIZE, new File(mImageFilePath).length());
- resolver.update(uri, values, null, null);
+ mNotificationBuilder.addAction(R.drawable.ic_screenshot_share,
+ r.getString(com.android.internal.R.string.share),
+ PendingIntent.getActivity(context, 0, chooserIntent,
+ PendingIntent.FLAG_CANCEL_CURRENT));
+
+ // Create a delete action for the notification
+ final PendingIntent deleteAction = PendingIntent.getBroadcast(context, 0,
+ new Intent(context, GlobalScreenshot.DeleteScreenshotReceiver.class)
+ .putExtra(GlobalScreenshot.CANCEL_ID, mNotificationId)
+ .putExtra(GlobalScreenshot.SCREENSHOT_URI_ID, uri.toString()),
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
+ mNotificationBuilder.addAction(R.drawable.ic_screenshot_delete,
+ r.getString(com.android.internal.R.string.delete), deleteAction);
params[0].imageUri = uri;
params[0].image = null;
@@ -333,6 +357,29 @@ class SaveImageInBackgroundTask extends AsyncTask<SaveImageInBackgroundData, Voi
}
/**
+ * An AsyncTask that deletes an image from the media store in the background.
+ */
+class DeleteImageInBackgroundTask extends AsyncTask<Uri, Void, Void> {
+ private static final String TAG = "DeleteImageInBackgroundTask";
+
+ private Context mContext;
+
+ DeleteImageInBackgroundTask(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ protected Void doInBackground(Uri... params) {
+ if (params.length != 1) return null;
+
+ Uri screenshotUri = params[0];
+ ContentResolver resolver = mContext.getContentResolver();
+ resolver.delete(screenshotUri, null, null);
+ return null;
+ }
+}
+
+/**
* TODO:
* - Performance when over gl surfaces? Ie. Gallery
* - what do we say in the Toast? Which icon do we get if the user uses another
@@ -341,7 +388,9 @@ class SaveImageInBackgroundTask extends AsyncTask<SaveImageInBackgroundData, Voi
class GlobalScreenshot {
private static final String TAG = "GlobalScreenshot";
- private static final int SCREENSHOT_NOTIFICATION_ID = 789;
+ static final String CANCEL_ID = "android:cancel_id";
+ static final String SCREENSHOT_URI_ID = "android:screenshot_uri_id";
+
private static final int SCREENSHOT_FLASH_TO_PEAK_DURATION = 130;
private static final int SCREENSHOT_DROP_IN_DURATION = 430;
private static final int SCREENSHOT_DROP_OUT_DELAY = 500;
@@ -464,7 +513,7 @@ class GlobalScreenshot {
mSaveInBgTask.cancel(false);
}
mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data, mNotificationManager,
- SCREENSHOT_NOTIFICATION_ID).execute(data);
+ R.id.notification_screenshot).execute(data);
}
/**
@@ -725,12 +774,52 @@ class GlobalScreenshot {
.setVisibility(Notification.VISIBILITY_PUBLIC) // ok to show outside lockscreen
.setCategory(Notification.CATEGORY_ERROR)
.setAutoCancel(true)
- .setColor(context.getResources().getColor(
+ .setColor(context.getColor(
com.android.internal.R.color.system_notification_accent_color));
Notification n =
new Notification.BigTextStyle(b)
.bigText(r.getString(R.string.screenshot_failed_text))
.build();
- nManager.notify(SCREENSHOT_NOTIFICATION_ID, n);
+ nManager.notify(R.id.notification_screenshot, n);
+ }
+
+ /**
+ * Removes the notification for a screenshot after a share target is chosen.
+ */
+ public static class TargetChosenReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (!intent.hasExtra(CANCEL_ID)) {
+ return;
+ }
+
+ // Clear the notification
+ final NotificationManager nm =
+ (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ final int id = intent.getIntExtra(CANCEL_ID, 0);
+ nm.cancel(id);
+ }
+ }
+
+ /**
+ * Removes the last screenshot.
+ */
+ public static class DeleteScreenshotReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (!intent.hasExtra(CANCEL_ID) || !intent.hasExtra(SCREENSHOT_URI_ID)) {
+ return;
+ }
+
+ // Clear the notification
+ final NotificationManager nm =
+ (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ final int id = intent.getIntExtra(CANCEL_ID, 0);
+ final Uri uri = Uri.parse(intent.getStringExtra(SCREENSHOT_URI_ID));
+ nm.cancel(id);
+
+ // And delete the image from the media store
+ new DeleteImageInBackgroundTask(context).execute(uri);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java
index 9fbcd7f..77c27fa 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java
@@ -30,6 +30,8 @@ import android.os.UserHandle;
import android.provider.Settings;
import android.widget.ImageView;
+import com.android.internal.logging.MetricsLogger;
+
import java.util.ArrayList;
public class BrightnessController implements ToggleSlider.Listener {
@@ -40,7 +42,7 @@ public class BrightnessController implements ToggleSlider.Listener {
* {@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 = 100;
+ private static final float BRIGHTNESS_ADJ_RESOLUTION = 2048;
private final int mMinimumBacklight;
private final int mMaximumBacklight;
@@ -195,12 +197,16 @@ public class BrightnessController implements ToggleSlider.Listener {
}
@Override
- public void onChanged(ToggleSlider view, boolean tracking, boolean automatic, int value) {
+ public void onChanged(ToggleSlider view, boolean tracking, boolean automatic, int value,
+ boolean stopTracking) {
updateIcon(mAutomatic);
if (mExternalChange) return;
if (!mAutomatic) {
final int val = value + mMinimumBacklight;
+ if (stopTracking) {
+ MetricsLogger.action(mContext, MetricsLogger.ACTION_BRIGHTNESS, val);
+ }
setBrightness(val);
if (!tracking) {
AsyncTask.execute(new Runnable() {
@@ -213,6 +219,9 @@ public class BrightnessController implements ToggleSlider.Listener {
}
} else {
final float adj = value / (BRIGHTNESS_ADJ_RESOLUTION / 2f) - 1;
+ if (stopTracking) {
+ MetricsLogger.action(mContext, MetricsLogger.ACTION_BRIGHTNESS_AUTO, value);
+ }
setBrightnessAdj(adj);
if (!tracking) {
AsyncTask.execute(new Runnable() {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/BrightnessDialog.java
index a1704ff..cef4d34 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/BrightnessDialog.java
@@ -17,17 +17,14 @@
package com.android.systemui.settings;
import android.app.Activity;
-import android.app.Dialog;
-import android.content.Context;
-import android.content.res.Resources;
import android.os.Bundle;
-import android.os.Handler;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ImageView;
+import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
/** A dialog that provides controls for adjusting the screen brightness. */
@@ -56,11 +53,13 @@ public class BrightnessDialog extends Activity {
protected void onStart() {
super.onStart();
mBrightnessController.registerCallbacks();
+ MetricsLogger.visible(this, MetricsLogger.BRIGHTNESS_DIALOG);
}
@Override
protected void onStop() {
super.onStop();
+ MetricsLogger.hidden(this, MetricsLogger.BRIGHTNESS_DIALOG);
mBrightnessController.unregisterCallbacks();
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/ToggleSeekBar.java b/packages/SystemUI/src/com/android/systemui/settings/ToggleSeekBar.java
index a0a5561..8829794 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/ToggleSeekBar.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/ToggleSeekBar.java
@@ -19,9 +19,13 @@ package com.android.systemui.settings;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
+import android.view.View;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.SeekBar;
public class ToggleSeekBar extends SeekBar {
+ private String mAccessibilityLabel;
+
public ToggleSeekBar(Context context) {
super(context);
}
@@ -42,4 +46,16 @@ public class ToggleSeekBar extends SeekBar {
return super.onTouchEvent(event);
}
+
+ public void setAccessibilityLabel(String label) {
+ mAccessibilityLabel = label;
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ if (mAccessibilityLabel != null) {
+ info.setText(mAccessibilityLabel);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/ToggleSlider.java b/packages/SystemUI/src/com/android/systemui/settings/ToggleSlider.java
index 35b574b..b263707 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/ToggleSlider.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/ToggleSlider.java
@@ -20,6 +20,7 @@ import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.util.AttributeSet;
+import android.view.MotionEvent;
import android.view.View;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
@@ -34,14 +35,15 @@ import com.android.systemui.statusbar.policy.BrightnessMirrorController;
public class ToggleSlider extends RelativeLayout {
public interface Listener {
public void onInit(ToggleSlider v);
- public void onChanged(ToggleSlider v, boolean tracking, boolean checked, int value);
+ public void onChanged(ToggleSlider v, boolean tracking, boolean checked, int value,
+ boolean stopTracking);
}
private Listener mListener;
private boolean mTracking;
private CompoundButton mToggle;
- private SeekBar mSlider;
+ private ToggleSeekBar mSlider;
private TextView mLabel;
private ToggleSlider mMirror;
@@ -67,12 +69,14 @@ public class ToggleSlider extends RelativeLayout {
mToggle = (CompoundButton) findViewById(R.id.toggle);
mToggle.setOnCheckedChangeListener(mCheckListener);
- mSlider = (SeekBar) findViewById(R.id.slider);
+ mSlider = (ToggleSeekBar) findViewById(R.id.slider);
mSlider.setOnSeekBarChangeListener(mSeekListener);
mLabel = (TextView) findViewById(R.id.label);
mLabel.setText(a.getString(R.styleable.ToggleSlider_text));
+ mSlider.setAccessibilityLabel(getContentDescription().toString());
+
a.recycle();
}
@@ -123,6 +127,16 @@ public class ToggleSlider extends RelativeLayout {
}
}
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ if (mMirror != null) {
+ MotionEvent copy = ev.copy();
+ mMirror.dispatchTouchEvent(copy);
+ copy.recycle();
+ }
+ return super.dispatchTouchEvent(ev);
+ }
+
private final OnCheckedChangeListener mCheckListener = new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton toggle, boolean checked) {
@@ -130,7 +144,7 @@ public class ToggleSlider extends RelativeLayout {
if (mListener != null) {
mListener.onChanged(
- ToggleSlider.this, mTracking, checked, mSlider.getProgress());
+ ToggleSlider.this, mTracking, checked, mSlider.getProgress(), false);
}
if (mMirror != null) {
@@ -144,11 +158,7 @@ public class ToggleSlider extends RelativeLayout {
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (mListener != null) {
mListener.onChanged(
- ToggleSlider.this, mTracking, mToggle.isChecked(), progress);
- }
-
- if (mMirror != null) {
- mMirror.setValue(progress);
+ ToggleSlider.this, mTracking, mToggle.isChecked(), progress, false);
}
}
@@ -157,16 +167,12 @@ public class ToggleSlider extends RelativeLayout {
mTracking = true;
if (mListener != null) {
- mListener.onChanged(
- ToggleSlider.this, mTracking, mToggle.isChecked(), mSlider.getProgress());
+ mListener.onChanged(ToggleSlider.this, mTracking, mToggle.isChecked(),
+ mSlider.getProgress(), false);
}
mToggle.setChecked(false);
- if (mMirror != null) {
- mMirror.mSlider.setPressed(true);
- }
-
if (mMirrorController != null) {
mMirrorController.showMirror();
mMirrorController.setLocation((View) getParent());
@@ -178,12 +184,8 @@ public class ToggleSlider extends RelativeLayout {
mTracking = false;
if (mListener != null) {
- mListener.onChanged(
- ToggleSlider.this, mTracking, mToggle.isChecked(), mSlider.getProgress());
- }
-
- if (mMirror != null) {
- mMirror.mSlider.setPressed(false);
+ mListener.onChanged(ToggleSlider.this, mTracking, mToggle.isChecked(),
+ mSlider.getProgress(), true);
}
if (mMirrorController != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index 465a141..7cde44c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -21,15 +21,8 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.BitmapShader;
import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffColorFilter;
import android.graphics.RectF;
-import android.graphics.Shader;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
@@ -94,13 +87,12 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
= new PathInterpolator(0, 0, 0.5f, 1);
private final int mTintedRippleColor;
private final int mLowPriorityRippleColor;
- private final int mNormalRippleColor;
+ protected final int mNormalRippleColor;
private boolean mDimmed;
private boolean mDark;
private int mBgTint = 0;
- private final int mRoundedRectCornerRadius;
/**
* Flag to indicate that the notification has been touched once and the second touch will
@@ -115,7 +107,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
private OnActivatedListener mOnActivatedListener;
private final Interpolator mLinearOutSlowInInterpolator;
- private final Interpolator mFastOutSlowInInterpolator;
+ protected final Interpolator mFastOutSlowInInterpolator;
private final Interpolator mSlowOutFastInInterpolator;
private final Interpolator mSlowOutLinearInInterpolator;
private final Interpolator mLinearInterpolator;
@@ -126,10 +118,8 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
private NotificationBackgroundView mBackgroundDimmed;
private ObjectAnimator mBackgroundAnimator;
private RectF mAppearAnimationRect = new RectF();
- private PorterDuffColorFilter mAppearAnimationFilter;
private float mAnimationTranslationY;
private boolean mDrawingAppearAnimation;
- private Paint mAppearPaint = new Paint();
private ValueAnimator mAppearAnimator;
private float mAppearAnimationFraction = -1.0f;
private float mAppearAnimationTranslation;
@@ -151,18 +141,15 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
mLinearInterpolator = new LinearInterpolator();
setClipChildren(false);
setClipToPadding(false);
- mAppearAnimationFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_ATOP);
- mRoundedRectCornerRadius = getResources().getDimensionPixelSize(
- R.dimen.notification_material_rounded_rect_radius);
- mLegacyColor = getResources().getColor(R.color.notification_legacy_background_color);
- mNormalColor = getResources().getColor(R.color.notification_material_background_color);
- mLowPriorityColor = getResources().getColor(
+ mLegacyColor = context.getColor(R.color.notification_legacy_background_color);
+ mNormalColor = context.getColor(R.color.notification_material_background_color);
+ mLowPriorityColor = context.getColor(
R.color.notification_material_background_low_priority_color);
- mTintedRippleColor = context.getResources().getColor(
+ mTintedRippleColor = context.getColor(
R.color.notification_ripple_tinted_color);
- mLowPriorityRippleColor = context.getResources().getColor(
+ mLowPriorityRippleColor = context.getColor(
R.color.notification_ripple_color_low_priority);
- mNormalRippleColor = context.getResources().getColor(
+ mNormalRippleColor = context.getColor(
R.color.notification_ripple_untinted_color);
}
@@ -388,7 +375,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
}
private void updateBackgroundTint() {
- int color = getBackgroundColor();
+ int color = getBgColor();
int rippleColor = getRippleColor();
if (color == mNormalColor) {
// We don't need to tint a normal notification
@@ -540,9 +527,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
private void startAppearAnimation(boolean isAppearing, float translationDirection, long delay,
long duration, final Runnable onFinishedRunnable) {
- if (mAppearAnimator != null) {
- mAppearAnimator.cancel();
- }
+ cancelAppearAnimation();
mAnimationTranslationY = translationDirection * getActualHeight();
if (mAppearAnimationFraction == -1.0f) {
// not initialized yet, we start anew
@@ -613,6 +598,17 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
mAppearAnimator.start();
}
+ private void cancelAppearAnimation() {
+ if (mAppearAnimator != null) {
+ mAppearAnimator.cancel();
+ }
+ }
+
+ public void cancelAppearDrawing() {
+ cancelAppearAnimation();
+ enableAppearDrawing(false);
+ }
+
private void updateAppearRect() {
float inverseFraction = (1.0f - mAppearAnimationFraction);
float translationFraction = mCurrentAppearInterpolator.getInterpolation(inverseFraction);
@@ -652,21 +648,27 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
}
private void updateAppearAnimationAlpha() {
- int backgroundColor = getBackgroundColor();
- if (backgroundColor != -1) {
- float contentAlphaProgress = mAppearAnimationFraction;
- contentAlphaProgress = contentAlphaProgress / (1.0f - ALPHA_ANIMATION_END);
- contentAlphaProgress = Math.min(1.0f, contentAlphaProgress);
- contentAlphaProgress = mCurrentAlphaInterpolator.getInterpolation(contentAlphaProgress);
- int sourceColor = Color.argb((int) (255 * (1.0f - contentAlphaProgress)),
- Color.red(backgroundColor), Color.green(backgroundColor),
- Color.blue(backgroundColor));
- mAppearAnimationFilter.setColor(sourceColor);
- mAppearPaint.setColorFilter(mAppearAnimationFilter);
+ float contentAlphaProgress = mAppearAnimationFraction;
+ contentAlphaProgress = contentAlphaProgress / (1.0f - ALPHA_ANIMATION_END);
+ contentAlphaProgress = Math.min(1.0f, contentAlphaProgress);
+ contentAlphaProgress = mCurrentAlphaInterpolator.getInterpolation(contentAlphaProgress);
+ setContentAlpha(contentAlphaProgress);
+ }
+
+ private void setContentAlpha(float contentAlpha) {
+ int layerType = contentAlpha == 0.0f || contentAlpha == 1.0f ? LAYER_TYPE_NONE
+ : LAYER_TYPE_HARDWARE;
+ View contentView = getContentView();
+ int currentLayerType = contentView.getLayerType();
+ if (currentLayerType != layerType) {
+ contentView.setLayerType(layerType, null);
}
+ contentView.setAlpha(contentAlpha);
}
- private int getBackgroundColor() {
+ protected abstract View getContentView();
+
+ private int getBgColor() {
if (mBgTint != 0) {
return mBgTint;
} else if (mShowingLegacyBackground) {
@@ -678,7 +680,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
}
}
- private int getRippleColor() {
+ protected int getRippleColor() {
if (mBgTint != 0) {
return mTintedRippleColor;
} else if (mShowingLegacyBackground) {
@@ -699,41 +701,24 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
*/
private void enableAppearDrawing(boolean enable) {
if (enable != mDrawingAppearAnimation) {
- if (enable) {
- if (getWidth() == 0 || getActualHeight() == 0) {
- // TODO: This should not happen, but it can during expansion. Needs
- // investigation
- return;
- }
- Bitmap bitmap = Bitmap.createBitmap(getWidth(), getActualHeight(),
- Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(bitmap);
- draw(canvas);
- mAppearPaint.setShader(new BitmapShader(bitmap, Shader.TileMode.CLAMP,
- Shader.TileMode.CLAMP));
- } else {
- mAppearPaint.setShader(null);
- }
mDrawingAppearAnimation = enable;
+ if (!enable) {
+ setContentAlpha(1.0f);
+ }
invalidate();
}
}
@Override
protected void dispatchDraw(Canvas canvas) {
- if (!mDrawingAppearAnimation) {
- super.dispatchDraw(canvas);
- } else {
- drawAppearRect(canvas);
+ if (mDrawingAppearAnimation) {
+ canvas.save();
+ canvas.translate(0, mAppearAnimationTranslation);
+ }
+ super.dispatchDraw(canvas);
+ if (mDrawingAppearAnimation) {
+ canvas.restore();
}
- }
-
- private void drawAppearRect(Canvas canvas) {
- canvas.save();
- canvas.translate(0, mAppearAnimationTranslation);
- canvas.drawRoundRect(mAppearAnimationRect, mRoundedRectCornerRadius,
- mRoundedRectCornerRadius, mAppearPaint);
- canvas.restore();
}
public void setOnActivatedListener(OnActivatedListener onActivatedListener) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedButton.java
new file mode 100644
index 0000000..87c12c2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedButton.java
@@ -0,0 +1,48 @@
+/*
+ * 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.statusbar;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.Button;
+
+/**
+ * A Button which doesn't have overlapping drawing commands
+ */
+public class AlphaOptimizedButton extends Button {
+ public AlphaOptimizedButton(Context context) {
+ super(context);
+ }
+
+ public AlphaOptimizedButton(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public AlphaOptimizedButton(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public AlphaOptimizedButton(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedFrameLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedFrameLayout.java
index a835c0e..359272e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedFrameLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedFrameLayout.java
@@ -19,7 +19,6 @@ package com.android.systemui.statusbar;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.FrameLayout;
-import android.widget.LinearLayout;
/**
* A frame layout which does not have overlapping renderings commands and therefore does not need a
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedImageView.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedImageView.java
index 094161d..700ea34 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedImageView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedImageView.java
@@ -17,35 +17,49 @@
package com.android.systemui.statusbar;
import android.content.Context;
+import android.content.res.TypedArray;
import android.util.AttributeSet;
-import android.view.View;
import android.widget.ImageView;
+import com.android.systemui.R;
+
/**
- * An ImageView which does not have overlapping rendering commands and therefore does not need a
- * layer when alpha is changed.
+ * An ImageView which supports an attribute specifying whether it has overlapping rendering
+ * commands and therefore does not need a layer when alpha is changed.
*/
-public class AlphaOptimizedImageView extends ImageView
-{
+public class AlphaOptimizedImageView extends ImageView {
+ private final boolean mHasOverlappingRendering;
+
public AlphaOptimizedImageView(Context context) {
- super(context);
+ this(context, null /* attrs */);
}
public AlphaOptimizedImageView(Context context, AttributeSet attrs) {
- super(context, attrs);
+ this(context, attrs, 0 /* defStyleAttr */);
}
public AlphaOptimizedImageView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
+ this(context, attrs, defStyleAttr, 0 /* defStyleRes */);
}
public AlphaOptimizedImageView(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
+
+ TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
+ R.styleable.AlphaOptimizedImageView, 0, 0);
+
+ try {
+ // Default to true, which is what View.java defaults to
+ mHasOverlappingRendering = a.getBoolean(
+ R.styleable.AlphaOptimizedImageView_hasOverlappingRendering, true);
+ } finally {
+ a.recycle();
+ }
}
@Override
public boolean hasOverlappingRendering() {
- return false;
+ return mHasOverlappingRendering;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AnimatedImageView.java b/packages/SystemUI/src/com/android/systemui/statusbar/AnimatedImageView.java
index 9839fe9..90f7c08 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/AnimatedImageView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AnimatedImageView.java
@@ -25,10 +25,15 @@ import android.widget.ImageView;
import android.widget.RemoteViews.RemoteView;
@RemoteView
-public class AnimatedImageView extends ImageView {
+public class AnimatedImageView extends AlphaOptimizedImageView {
AnimationDrawable mAnim;
boolean mAttached;
+ // Tracks the last image that was set, so that we don't refresh the image if it is exactly
+ // the same as the previous one. If this is a resid, we track that. If it's a drawable, we
+ // track the hashcode of the drawable.
+ int mDrawableId;
+
public AnimatedImageView(Context context) {
super(context);
}
@@ -43,7 +48,7 @@ public class AnimatedImageView extends ImageView {
mAnim.stop();
}
if (drawable instanceof AnimationDrawable) {
- mAnim = (AnimationDrawable)drawable;
+ mAnim = (AnimationDrawable) drawable;
if (isShown()) {
mAnim.start();
}
@@ -54,6 +59,13 @@ public class AnimatedImageView extends ImageView {
@Override
public void setImageDrawable(Drawable drawable) {
+ if (drawable != null) {
+ if (mDrawableId == drawable.hashCode()) return;
+
+ mDrawableId = drawable.hashCode();
+ } else {
+ mDrawableId = 0;
+ }
super.setImageDrawable(drawable);
updateAnim();
}
@@ -61,6 +73,9 @@ public class AnimatedImageView extends ImageView {
@Override
@android.view.RemotableViewMethod
public void setImageResource(int resid) {
+ if (mDrawableId == resid) return;
+
+ mDrawableId = resid;
super.setImageResource(resid);
updateAnim();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 8a03a2b..6ad0ef9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -41,6 +41,7 @@ import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Handler;
@@ -49,6 +50,7 @@ import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -63,39 +65,42 @@ import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.view.Display;
+import android.view.Gravity;
import android.view.IWindowManager;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
-import android.view.ViewGroup.LayoutParams;
import android.view.ViewParent;
-import android.view.ViewStub;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityManager;
import android.view.animation.AnimationUtils;
import android.widget.DateTimeView;
import android.widget.ImageView;
-import android.widget.LinearLayout;
import android.widget.RemoteViews;
import android.widget.TextView;
+import android.widget.Toast;
+import com.android.internal.logging.MetricsLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.statusbar.StatusBarIconList;
import com.android.internal.util.NotificationColorUtil;
import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.R;
import com.android.systemui.RecentsComponent;
-import com.android.systemui.SearchPanelView;
import com.android.systemui.SwipeHelper;
import com.android.systemui.SystemUI;
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.recents.Recents;
import com.android.systemui.statusbar.NotificationData.Entry;
import com.android.systemui.statusbar.phone.NavigationBarView;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.policy.HeadsUpNotificationView;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.PreviewInflater;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
@@ -116,6 +121,9 @@ public abstract class BaseStatusBar extends SystemUI implements
// STOPSHIP disable once we resolve b/18102199
private static final boolean NOTIFICATION_CLICK_DEBUG = true;
+ public static final boolean ENABLE_CHILD_NOTIFICATIONS = Build.IS_DEBUGGABLE
+ && SystemProperties.getBoolean("debug.child_notifs", 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;
@@ -123,11 +131,6 @@ public abstract class BaseStatusBar extends SystemUI implements
protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023;
protected static final int MSG_SHOW_NEXT_AFFILIATED_TASK = 1024;
protected static final int MSG_SHOW_PREV_AFFILIATED_TASK = 1025;
- protected static final int MSG_CLOSE_SEARCH_PANEL = 1027;
- protected static final int MSG_SHOW_HEADS_UP = 1028;
- protected static final int MSG_HIDE_HEADS_UP = 1029;
- protected static final int MSG_ESCALATE_HEADS_UP = 1030;
- protected static final int MSG_DECAY_HEADS_UP = 1031;
protected static final boolean ENABLE_HEADS_UP = true;
// scores above this threshold should be displayed in heads up mode.
@@ -137,10 +140,6 @@ public abstract class BaseStatusBar extends SystemUI implements
// Should match the value in PhoneWindowManager
public static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
- public static final int EXPANDED_LEAVE_ALONE = -10000;
- public static final int EXPANDED_FULL_OPEN = -10001;
-
- private static final int HIDDEN_NOTIFICATION_ID = 10000;
private static final String BANNER_ACTION_CANCEL =
"com.android.systemui.statusbar.banner_action_cancel";
private static final String BANNER_ACTION_SETUP =
@@ -154,12 +153,10 @@ public abstract class BaseStatusBar extends SystemUI implements
protected NotificationData mNotificationData;
protected NotificationStackScrollLayout mStackScroller;
- // for heads up notifications
- protected HeadsUpNotificationView mHeadsUpNotificationView;
- protected int mHeadsUpNotificationDecay;
+ protected NotificationGroupManager mGroupManager = new NotificationGroupManager();
- // Search panel
- protected SearchPanelView mSearchPanelView;
+ // for heads up notifications
+ protected HeadsUpManager mHeadsUpManager;
protected int mCurrentUserId = 0;
final protected SparseArray<UserInfo> mCurrentProfiles = new SparseArray<UserInfo>();
@@ -241,6 +238,10 @@ public abstract class BaseStatusBar extends SystemUI implements
protected DismissView mDismissView;
protected EmptyShadeView mEmptyShadeView;
+ private NotificationClicker mNotificationClicker = new NotificationClicker();
+
+ protected AssistManager mAssistManager;
+
@Override // NotificationData.Environment
public boolean isDeviceProvisioned() {
return mDeviceProvisioned;
@@ -302,6 +303,7 @@ public abstract class BaseStatusBar extends SystemUI implements
try {
ActivityManagerNative.getDefault()
.keyguardWaitingForActivityDrawn();
+ ActivityManagerNative.getDefault().resumeAppSwitches();
} catch (RemoteException e) {
}
}
@@ -314,7 +316,9 @@ public abstract class BaseStatusBar extends SystemUI implements
animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
true /* force */);
visibilityChanged(false);
+ mAssistManager.hideAssist();
}
+
// Wait for activity start.
return handled;
}
@@ -379,15 +383,36 @@ public abstract class BaseStatusBar extends SystemUI implements
userSwitched(mCurrentUserId);
} else if (Intent.ACTION_USER_ADDED.equals(action)) {
updateCurrentProfilesCache();
- } else if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(
- action)) {
- mUsersAllowingPrivateNotifications.clear();
- updateLockscreenNotificationSetting();
- updateNotifications();
+ } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
+ List<ActivityManager.RecentTaskInfo> recentTask = null;
+ try {
+ recentTask = ActivityManagerNative.getDefault().getRecentTasks(1,
+ ActivityManager.RECENT_WITH_EXCLUDED
+ | ActivityManager.RECENT_INCLUDE_PROFILES,
+ mCurrentUserId);
+ } 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)) {
NotificationManager noMan = (NotificationManager)
mContext.getSystemService(Context.NOTIFICATION_SERVICE);
- noMan.cancel(HIDDEN_NOTIFICATION_ID);
+ noMan.cancel(R.id.notification_hidden);
Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0);
@@ -403,6 +428,19 @@ public abstract class BaseStatusBar extends SystemUI implements
}
};
+ private final BroadcastReceiver mAllUsersReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(action) &&
+ isCurrentProfile(getSendingUserId())) {
+ mUsersAllowingPrivateNotifications.clear();
+ updateLockscreenNotificationSetting();
+ updateNotifications();
+ }
+ }
+ };
+
private final NotificationListenerService mNotificationListener =
new NotificationListenerService() {
@Override
@@ -414,7 +452,7 @@ public abstract class BaseStatusBar extends SystemUI implements
@Override
public void run() {
for (StatusBarNotification sbn : notifications) {
- addNotification(sbn, currentRanking);
+ addNotification(sbn, currentRanking, null /* oldEntry */);
}
}
});
@@ -424,61 +462,68 @@ public abstract class BaseStatusBar extends SystemUI implements
public void onNotificationPosted(final StatusBarNotification sbn,
final RankingMap rankingMap) {
if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn);
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- Notification n = sbn.getNotification();
- boolean isUpdate = mNotificationData.get(sbn.getKey()) != null
- || isHeadsUp(sbn.getKey());
-
- // 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 (n.isGroupChild() &&
- mNotificationData.isGroupWithSummary(sbn.getGroupKey())) {
- if (DEBUG) {
- Log.d(TAG, "Ignoring group child due to existing summary: " + sbn);
- }
+ if (sbn != null) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
- // Remove existing notification to avoid stale data.
+ String key = sbn.getKey();
+ 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);
+ }
+
+ // Remove existing notification to avoid stale data.
+ if (isUpdate) {
+ removeNotification(key, rankingMap);
+ } else {
+ mNotificationData.updateRanking(rankingMap);
+ }
+ return;
+ }
if (isUpdate) {
- removeNotification(sbn.getKey(), rankingMap);
+ updateNotification(sbn, rankingMap);
} else {
- mNotificationData.updateRanking(rankingMap);
+ addNotification(sbn, rankingMap, null /* oldEntry */);
}
- return;
}
- if (isUpdate) {
- updateNotification(sbn, rankingMap);
- } else {
- addNotification(sbn, rankingMap);
- }
- }
- });
+ });
+ }
}
@Override
- public void onNotificationRemoved(final StatusBarNotification sbn,
+ public void onNotificationRemoved(StatusBarNotification sbn,
final RankingMap rankingMap) {
if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn);
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- removeNotification(sbn.getKey(), rankingMap);
- }
- });
+ if (sbn != null) {
+ final String key = sbn.getKey();
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ removeNotification(key, rankingMap);
+ }
+ });
+ }
}
@Override
public void onNotificationRankingUpdate(final RankingMap rankingMap) {
if (DEBUG) Log.d(TAG, "onRankingUpdate");
+ if (rankingMap != null) {
mHandler.post(new Runnable() {
@Override
public void run() {
updateNotificationRanking(rankingMap);
}
});
- }
+ } }
};
@@ -511,7 +556,6 @@ public abstract class BaseStatusBar extends SystemUI implements
ServiceManager.checkService(DreamService.DREAM_SERVICE));
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
- mSettingsObserver.onChange(false); // set up
mContext.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), true,
mSettingsObserver);
@@ -532,7 +576,7 @@ public abstract class BaseStatusBar extends SystemUI implements
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
- mRecents = getComponent(RecentsComponent.class);
+ mRecents = getComponent(Recents.class);
mRecents.setCallback(this);
final Configuration currentConfig = mContext.getResources().getConfiguration();
@@ -561,7 +605,8 @@ public abstract class BaseStatusBar extends SystemUI implements
createAndAddWindows();
- disable(switches[0], false /* animate */);
+ mSettingsObserver.onChange(false); // set up
+ disable(switches[0], switches[6], false /* animate */);
setSystemUiVisibility(switches[1], 0xffffffff);
topAppWindowChanged(switches[2] != 0);
// StatusBarManagerService has a back up of IME token and it's restored here.
@@ -605,11 +650,16 @@ public abstract class BaseStatusBar extends SystemUI implements
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_USER_SWITCHED);
filter.addAction(Intent.ACTION_USER_ADDED);
+ filter.addAction(Intent.ACTION_USER_PRESENT);
filter.addAction(BANNER_ACTION_CANCEL);
filter.addAction(BANNER_ACTION_SETUP);
- filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
mContext.registerReceiver(mBroadcastReceiver, filter);
+ IntentFilter allUsersFilter = new IntentFilter();
+ allUsersFilter.addAction(
+ DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
+ mContext.registerReceiverAsUser(mAllUsersReceiver, UserHandle.ALL, allUsersFilter,
+ null, null);
updateCurrentProfilesCache();
}
@@ -618,7 +668,7 @@ public abstract class BaseStatusBar extends SystemUI implements
Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 1)) {
Log.d(TAG, "user hasn't seen notification about hidden notifications");
final LockPatternUtils lockPatternUtils = new LockPatternUtils(mContext);
- if (!lockPatternUtils.isSecure()) {
+ if (!lockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())) {
Log.d(TAG, "insecure lockscreen, skipping notification");
Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0);
@@ -647,7 +697,7 @@ public abstract class BaseStatusBar extends SystemUI implements
.setContentText(mContext.getString(R.string.hidden_notifications_text))
.setPriority(Notification.PRIORITY_HIGH)
.setOngoing(true)
- .setColor(res.getColor(colorRes))
+ .setColor(mContext.getColor(colorRes))
.setContentIntent(setupIntent)
.addAction(R.drawable.ic_close,
mContext.getString(R.string.hidden_notifications_cancel),
@@ -658,7 +708,7 @@ public abstract class BaseStatusBar extends SystemUI implements
NotificationManager noMan =
(NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
- noMan.notify(HIDDEN_NOTIFICATION_ID, note.build());
+ noMan.notify(R.id.notification_hidden, note.build());
}
}
@@ -666,15 +716,7 @@ public abstract class BaseStatusBar extends SystemUI implements
setHeadsUpUser(newUserId);
}
- private void setHeadsUpUser(int newUserId) {
- if (mHeadsUpNotificationView != null) {
- mHeadsUpNotificationView.setUser(newUserId);
- }
- }
-
- public boolean isHeadsUp(String key) {
- return mHeadsUpNotificationView != null && mHeadsUpNotificationView.isShowing(key);
- }
+ protected abstract void setHeadsUpUser(int newUserId);
@Override // NotificationData.Environment
public boolean isNotificationForCurrentProfiles(StatusBarNotification n) {
@@ -687,6 +729,14 @@ public abstract class BaseStatusBar extends SystemUI implements
return isCurrentProfile(notificationUserId);
}
+ protected void setNotificationShown(StatusBarNotification n) {
+ mNotificationListener.setNotificationsShown(new String[] { n.getKey() });
+ }
+
+ protected void setNotificationsShown(String[] keys) {
+ mNotificationListener.setNotificationsShown(keys);
+ }
+
protected boolean isCurrentProfile(int userId) {
synchronized (mCurrentProfiles) {
return userId == UserHandle.USER_ALL || mCurrentProfiles.get(userId) != null;
@@ -698,6 +748,11 @@ public abstract class BaseStatusBar extends SystemUI implements
return null;
}
+ @Override
+ public NotificationGroupManager getGroupManager() {
+ return mGroupManager;
+ }
+
/**
* Takes the necessary steps to prepare the status bar for starting an activity, then starts it.
* @param action A dismiss action that is called if it's safe to start the activity.
@@ -727,8 +782,7 @@ public abstract class BaseStatusBar extends SystemUI implements
protected View updateNotificationVetoButton(View row, StatusBarNotification n) {
View vetoButton = row.findViewById(R.id.veto);
- if (n.isClearable() || (mHeadsUpNotificationView.getEntry() != null
- && mHeadsUpNotificationView.getEntry().row == row)) {
+ if (n.isClearable()) {
final String _pkg = n.getPackageName();
final String _tag = n.getTag();
final int _id = n.getId();
@@ -758,7 +812,8 @@ public abstract class BaseStatusBar extends SystemUI implements
protected void applyColorsAndBackgrounds(StatusBarNotification sbn,
NotificationData.Entry entry) {
- if (entry.expanded.getId() != com.android.internal.R.id.status_bar_latest_event_content) {
+ if (entry.getContentView().getId()
+ != com.android.internal.R.id.status_bar_latest_event_content) {
// Using custom RemoteViews
if (entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD
&& entry.targetSdk < Build.VERSION_CODES.LOLLIPOP) {
@@ -770,25 +825,22 @@ public abstract class BaseStatusBar extends SystemUI implements
final int color = sbn.getNotification().color;
if (isMediaNotification(entry)) {
entry.row.setTintColor(color == Notification.COLOR_DEFAULT
- ? mContext.getResources().getColor(
+ ? mContext.getColor(
R.color.notification_material_background_media_default_color)
: color);
}
}
if (entry.icon != null) {
- if (entry.targetSdk >= Build.VERSION_CODES.LOLLIPOP) {
- entry.icon.setColorFilter(mContext.getResources().getColor(android.R.color.white));
- } else {
- entry.icon.setColorFilter(null);
- }
+ entry.icon.setTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
}
}
public boolean isMediaNotification(NotificationData.Entry entry) {
// TODO: confirm that there's a valid media key
- return entry.expandedBig != null &&
- entry.expandedBig.findViewById(com.android.internal.R.id.media_actions) != null;
+ return entry.getExpandedContentView() != null &&
+ entry.getExpandedContentView()
+ .findViewById(com.android.internal.R.id.media_actions) != null;
}
// The gear button in the guts that links to the app's own notification settings
@@ -834,16 +886,13 @@ public abstract class BaseStatusBar extends SystemUI implements
}, false /* afterKeyguardGone */);
}
- private void inflateGuts(ExpandableNotificationRow row) {
- ViewStub stub = (ViewStub) row.findViewById(R.id.notification_guts_stub);
- if (stub != null) {
- stub.inflate();
- }
+ private void bindGuts(ExpandableNotificationRow row) {
+ row.inflateGuts();
final StatusBarNotification sbn = row.getStatusBarNotification();
PackageManager pmUser = getPackageManagerForUser(
sbn.getUser().getIdentifier());
row.setTag(sbn.getPackageName());
- final View guts = row.findViewById(R.id.notification_guts);
+ final View guts = row.getGuts();
final String pkg = sbn.getPackageName();
String appname = pkg;
Drawable pkgicon = null;
@@ -871,6 +920,7 @@ public abstract class BaseStatusBar extends SystemUI implements
final int appUidF = appUid;
settingsButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
+ MetricsLogger.action(mContext, MetricsLogger.ACTION_NOTE_INFO);
startAppNotificationSettingsActivity(pkg, appUidF);
}
});
@@ -891,6 +941,7 @@ public abstract class BaseStatusBar extends SystemUI implements
.setClassName(pkg, infos.get(0).activityInfo.name);
appSettingsButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
+ MetricsLogger.action(mContext, MetricsLogger.ACTION_APP_NOTE_SETTINGS);
startAppOwnNotificationSettingsActivity(appSettingsLaunchIntent,
sbn.getId(),
sbn.getTag(),
@@ -921,11 +972,11 @@ public abstract class BaseStatusBar extends SystemUI implements
return false;
}
- inflateGuts((ExpandableNotificationRow) v);
+ ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+ bindGuts(row);
// Assume we are a status_bar_notification_row
- final NotificationGuts guts = (NotificationGuts) v.findViewById(
- R.id.notification_guts);
+ final NotificationGuts guts = row.getGuts();
if (guts == null) {
// This view has no guts. Examples are the more card or the dismiss all view
return false;
@@ -937,6 +988,7 @@ public abstract class BaseStatusBar extends SystemUI implements
return false;
}
+ MetricsLogger.action(mContext, MetricsLogger.ACTION_NOTE_CONTROLS);
guts.setVisibility(View.VISIBLE);
final double horz = Math.max(guts.getWidth() - x, x);
final double vert = Math.max(guts.getActualHeight() - y, y);
@@ -978,9 +1030,6 @@ public abstract class BaseStatusBar extends SystemUI implements
}
}
- public void onHeadsUpDismissed() {
- }
-
@Override
public void showRecentApps(boolean triggeredFromAltTab) {
int msg = MSG_SHOW_RECENT_APPS;
@@ -998,9 +1047,7 @@ public abstract class BaseStatusBar extends SystemUI implements
@Override
public void toggleRecentApps() {
- int msg = MSG_TOGGLE_RECENTS_APPS;
- mHandler.removeMessages(msg);
- mHandler.sendEmptyMessage(msg);
+ toggleRecents();
}
@Override
@@ -1031,50 +1078,6 @@ public abstract class BaseStatusBar extends SystemUI implements
mHandler.sendEmptyMessage(msg);
}
- @Override
- public void showSearchPanel() {
- if (mSearchPanelView != null && mSearchPanelView.isAssistantAvailable()) {
- mSearchPanelView.show(true, true);
- }
- }
-
- @Override
- public void hideSearchPanel() {
- int msg = MSG_CLOSE_SEARCH_PANEL;
- mHandler.removeMessages(msg);
- mHandler.sendEmptyMessage(msg);
- }
-
- protected abstract WindowManager.LayoutParams getSearchLayoutParams(
- LayoutParams layoutParams);
-
- protected void updateSearchPanel() {
- // Search Panel
- boolean visible = false;
- if (mSearchPanelView != null) {
- visible = mSearchPanelView.isShowing();
- mWindowManager.removeView(mSearchPanelView);
- }
-
- // Provide SearchPanel with a temporary parent to allow layout params to work.
- LinearLayout tmpRoot = new LinearLayout(mContext);
- mSearchPanelView = (SearchPanelView) LayoutInflater.from(mContext).inflate(
- R.layout.status_bar_search_panel, tmpRoot, false);
- mSearchPanelView.setOnTouchListener(
- new TouchOutsideListener(MSG_CLOSE_SEARCH_PANEL, mSearchPanelView));
- mSearchPanelView.setVisibility(View.GONE);
- boolean vertical = mNavigationBarView != null && mNavigationBarView.isVertical();
- mSearchPanelView.setHorizontal(vertical);
-
- WindowManager.LayoutParams lp = getSearchLayoutParams(mSearchPanelView.getLayoutParams());
-
- mWindowManager.addView(mSearchPanelView, lp);
- mSearchPanelView.setBar(this);
- if (visible) {
- mSearchPanelView.show(true, false);
- }
- }
-
protected H createHandler() {
return new H();
}
@@ -1161,13 +1164,10 @@ public abstract class BaseStatusBar extends SystemUI implements
// Do nothing
}
- public abstract void resetHeadsUpDecayTimer();
-
- public abstract void scheduleHeadsUpOpen();
-
- public abstract void scheduleHeadsUpClose();
-
- public abstract void scheduleHeadsUpEscalation();
+ /**
+ * If there is an active heads-up notification and it has a fullscreen intent, fire it now.
+ */
+ public abstract void maybeEscalateHeadsUp();
/**
* Save the current "public" (locked and secure) state of the lockscreen.
@@ -1190,14 +1190,15 @@ public abstract class BaseStatusBar extends SystemUI implements
}
if (mUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
- final boolean allowed = 0 != Settings.Secure.getIntForUser(
+ final boolean allowedByUser = 0 != Settings.Secure.getIntForUser(
mContext.getContentResolver(),
Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, userHandle);
final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures(null /* admin */,
userHandle);
final boolean allowedByDpm = (dpmFlags
& DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS) == 0;
- mUsersAllowingPrivateNotifications.append(userHandle, allowed && allowedByDpm);
+ final boolean allowed = allowedByUser && allowedByDpm;
+ mUsersAllowingPrivateNotifications.append(userHandle, allowed);
return allowed;
}
@@ -1250,50 +1251,14 @@ public abstract class BaseStatusBar extends SystemUI implements
case MSG_SHOW_PREV_AFFILIATED_TASK:
showRecentsPreviousAffiliatedTask();
break;
- case MSG_CLOSE_SEARCH_PANEL:
- if (DEBUG) Log.d(TAG, "closing search panel");
- if (mSearchPanelView != null && mSearchPanelView.isShowing()) {
- mSearchPanelView.show(false, true);
- }
- break;
- }
- }
- }
-
- public class TouchOutsideListener implements View.OnTouchListener {
- private int mMsg;
- private StatusBarPanel mPanel;
-
- public TouchOutsideListener(int msg, StatusBarPanel panel) {
- mMsg = msg;
- mPanel = panel;
- }
-
- public boolean onTouch(View v, MotionEvent ev) {
- final int action = ev.getAction();
- if (action == MotionEvent.ACTION_OUTSIDE
- || (action == MotionEvent.ACTION_DOWN
- && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) {
- mHandler.removeMessages(mMsg);
- mHandler.sendEmptyMessage(mMsg);
- return true;
}
- return false;
}
}
protected void workAroundBadLayerDrawableOpacity(View v) {
}
- private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) {
- return inflateViews(entry, parent, false);
- }
-
- protected boolean inflateViewsForHeadsUp(NotificationData.Entry entry, ViewGroup parent) {
- return inflateViews(entry, parent, true);
- }
-
- private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent, boolean isHeadsUp) {
+ protected boolean inflateViews(Entry entry, ViewGroup parent) {
PackageManager pmUser = getPackageManagerForUser(
entry.notification.getUser().getIdentifier());
@@ -1301,12 +1266,7 @@ public abstract class BaseStatusBar extends SystemUI implements
final StatusBarNotification sbn = entry.notification;
RemoteViews contentView = sbn.getNotification().contentView;
RemoteViews bigContentView = sbn.getNotification().bigContentView;
-
- if (isHeadsUp) {
- maxHeight =
- mContext.getResources().getDimensionPixelSize(R.dimen.notification_mid_height);
- bigContentView = sbn.getNotification().headsUpContentView;
- }
+ RemoteViews headsUpContentView = sbn.getNotification().headsUpContentView;
if (contentView == null) {
return false;
@@ -1342,6 +1302,7 @@ public abstract class BaseStatusBar extends SystemUI implements
row = (ExpandableNotificationRow) inflater.inflate(R.layout.status_bar_notification_row,
parent, false);
row.setExpansionLogger(this, entry.notification.getKey());
+ row.setGroupManager(mGroupManager);
}
workAroundBadLayerDrawableOpacity(row);
@@ -1352,30 +1313,26 @@ public abstract class BaseStatusBar extends SystemUI implements
// NB: the large icon is now handled entirely by the template
// bind the click event to the content area
- NotificationContentView expanded =
- (NotificationContentView) row.findViewById(R.id.expanded);
- NotificationContentView expandedPublic =
- (NotificationContentView) row.findViewById(R.id.expandedPublic);
+ NotificationContentView contentContainer = row.getPrivateLayout();
+ NotificationContentView contentContainerPublic = row.getPublicLayout();
row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
- PendingIntent contentIntent = sbn.getNotification().contentIntent;
- if (contentIntent != null) {
- final View.OnClickListener listener = makeClicker(contentIntent, sbn.getKey(),
- isHeadsUp);
- row.setOnClickListener(listener);
- } else {
- row.setOnClickListener(null);
- }
+ mNotificationClicker.register(row, sbn);
// set up the adaptive layout
View contentViewLocal = null;
View bigContentViewLocal = null;
+ View headsUpContentViewLocal = null;
try {
- contentViewLocal = contentView.apply(mContext, expanded,
+ contentViewLocal = contentView.apply(mContext, contentContainer,
mOnClickHandler);
if (bigContentView != null) {
- bigContentViewLocal = bigContentView.apply(mContext, expanded,
+ bigContentViewLocal = bigContentView.apply(mContext, contentContainer,
+ mOnClickHandler);
+ }
+ if (headsUpContentView != null) {
+ headsUpContentViewLocal = headsUpContentView.apply(mContext, contentContainer,
mOnClickHandler);
}
}
@@ -1387,23 +1344,27 @@ public abstract class BaseStatusBar extends SystemUI implements
if (contentViewLocal != null) {
contentViewLocal.setIsRootNamespace(true);
- expanded.setContractedChild(contentViewLocal);
+ contentContainer.setContractedChild(contentViewLocal);
}
if (bigContentViewLocal != null) {
bigContentViewLocal.setIsRootNamespace(true);
- expanded.setExpandedChild(bigContentViewLocal);
+ contentContainer.setExpandedChild(bigContentViewLocal);
+ }
+ if (headsUpContentViewLocal != null) {
+ headsUpContentViewLocal.setIsRootNamespace(true);
+ contentContainer.setHeadsUpChild(headsUpContentViewLocal);
}
// now the public version
View publicViewLocal = null;
if (publicNotification != null) {
try {
- publicViewLocal = publicNotification.contentView.apply(mContext, expandedPublic,
- mOnClickHandler);
+ publicViewLocal = publicNotification.contentView.apply(mContext,
+ contentContainerPublic, mOnClickHandler);
if (publicViewLocal != null) {
publicViewLocal.setIsRootNamespace(true);
- expandedPublic.setContractedChild(publicViewLocal);
+ contentContainerPublic.setContractedChild(publicViewLocal);
}
}
catch (RuntimeException e) {
@@ -1425,9 +1386,8 @@ public abstract class BaseStatusBar extends SystemUI implements
// Add a basic notification template
publicViewLocal = LayoutInflater.from(mContext).inflate(
R.layout.notification_public_default,
- expandedPublic, false);
+ contentContainerPublic, false);
publicViewLocal.setIsRootNamespace(true);
- expandedPublic.setContractedChild(publicViewLocal);
final TextView title = (TextView) publicViewLocal.findViewById(R.id.title);
try {
@@ -1441,9 +1401,10 @@ public abstract class BaseStatusBar extends SystemUI implements
final ImageView profileBadge = (ImageView) publicViewLocal.findViewById(
R.id.profile_badge_line3);
- final StatusBarIcon ic = new StatusBarIcon(entry.notification.getPackageName(),
+ final StatusBarIcon ic = new StatusBarIcon(
entry.notification.getUser(),
- entry.notification.getNotification().icon,
+ entry.notification.getPackageName(),
+ entry.notification.getNotification().getSmallIcon(),
entry.notification.getNotification().iconLevel,
entry.notification.getNotification().number,
entry.notification.getNotification().tickerText);
@@ -1493,6 +1454,7 @@ public abstract class BaseStatusBar extends SystemUI implements
mContext.getResources().getConfiguration().fontScale);
title.setPadding(0, topPadding, 0, 0);
+ contentContainerPublic.setContractedChild(publicViewLocal);
entry.autoRedacted = true;
}
@@ -1506,9 +1468,7 @@ public abstract class BaseStatusBar extends SystemUI implements
entry.row = row;
entry.row.setHeightRange(mRowMinHeight, maxHeight);
entry.row.setOnActivatedListener(this);
- entry.expanded = contentViewLocal;
- entry.expandedPublic = publicViewLocal;
- entry.setBigContentView(bigContentViewLocal);
+ entry.row.setExpandable(bigContentViewLocal != null);
applyColorsAndBackgrounds(sbn, entry);
@@ -1520,41 +1480,42 @@ public abstract class BaseStatusBar extends SystemUI implements
}
row.setUserLocked(userLocked);
row.setStatusBarNotification(entry.notification);
+
return true;
}
- public NotificationClicker makeClicker(PendingIntent intent, String notificationKey,
- boolean forHun) {
- return new NotificationClicker(intent, notificationKey, forHun);
- }
+ private final class NotificationClicker implements View.OnClickListener {
+ 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;
+ }
- protected class NotificationClicker implements View.OnClickListener {
- private PendingIntent mIntent;
- private final String mNotificationKey;
- private boolean mIsHeadsUp;
+ final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+ final StatusBarNotification sbn = row.getStatusBarNotification();
+ if (sbn == null) {
+ Log.e(TAG, "NotificationClicker called on an unclickable notification,");
+ return;
+ }
- public NotificationClicker(PendingIntent intent, String notificationKey, boolean forHun) {
- mIntent = intent;
- mNotificationKey = notificationKey;
- mIsHeadsUp = forHun;
- }
+ final PendingIntent intent = sbn.getNotification().contentIntent;
+ final String notificationKey = sbn.getKey();
- public void onClick(final View v) {
if (NOTIFICATION_CLICK_DEBUG) {
- Log.d(TAG, "Clicked on content of " + mNotificationKey);
+ Log.d(TAG, "Clicked on content of " + notificationKey);
}
final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing();
- final boolean afterKeyguardGone = mIntent.isActivity()
- && PreviewInflater.wouldLaunchResolverActivity(mContext, mIntent.getIntent(),
+ final boolean afterKeyguardGone = intent.isActivity()
+ && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
mCurrentUserId);
dismissKeyguardThenExecute(new OnDismissAction() {
public boolean onDismiss() {
- if (mIsHeadsUp) {
+ if (mHeadsUpManager != null && mHeadsUpManager.isHeadsUp(notificationKey)) {
// Release the HUN notification to the shade.
//
// In most cases, when FLAG_AUTO_CANCEL is set, the notification will
// become canceled shortly by NoMan, but we can't assume that.
- mHeadsUpNotificationView.releaseAndClose();
+ mHeadsUpManager.releaseImmediately(notificationKey);
}
new Thread() {
@Override
@@ -1573,9 +1534,9 @@ public abstract class BaseStatusBar extends SystemUI implements
} catch (RemoteException e) {
}
- if (mIntent != null) {
+ if (intent != null) {
try {
- mIntent.send();
+ intent.send();
} catch (PendingIntent.CanceledException e) {
// the stack trace isn't very helpful here.
// Just log the exception message.
@@ -1583,14 +1544,15 @@ public abstract class BaseStatusBar extends SystemUI implements
// TODO: Dismiss Keyguard.
}
- if (mIntent.isActivity()) {
+ if (intent.isActivity()) {
+ mAssistManager.hideAssist();
overrideActivityPendingAppTransition(keyguardShowing
&& !afterKeyguardGone);
}
}
try {
- mBarService.onNotificationClick(mNotificationKey);
+ mBarService.onNotificationClick(notificationKey);
} catch (RemoteException ex) {
// system process is dead if we're here.
}
@@ -1599,18 +1561,30 @@ public abstract class BaseStatusBar extends SystemUI implements
// close the shade if it was open
animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
- true /* force */);
+ true /* force */, true /* delayed */);
visibilityChanged(false);
- return mIntent != null && mIntent.isActivity();
+ return true;
}
}, afterKeyguardGone);
}
+
+ public void register(ExpandableNotificationRow row, StatusBarNotification sbn) {
+ final PendingIntent contentIntent = sbn.getNotification().contentIntent;
+ if (contentIntent != null) {
+ row.setOnClickListener(this);
+ } else {
+ row.setOnClickListener(null);
+ }
+ }
}
public void animateCollapsePanels(int flags, boolean force) {
}
+ public void animateCollapsePanels(int flags, boolean force, boolean delayed) {
+ }
+
public void overrideActivityPendingAppTransition(boolean keyguardShowing) {
if (keyguardShowing) {
try {
@@ -1642,19 +1616,20 @@ public abstract class BaseStatusBar extends SystemUI implements
/**
* The LEDs are turned off when the notification panel is shown, even just a little bit.
- * This was added last-minute and is inconsistent with the way the rest of the notifications
- * are handled, because the notification isn't really cancelled. The lights are just
- * turned off. If any other notifications happen, the lights will turn back on. Steve says
- * this is what he wants. (see bug 1131461)
*/
protected void handleVisibleToUserChanged(boolean visibleToUser) {
try {
if (visibleToUser) {
- // Only stop blinking, vibrating, ringing when the user went into the shade
- // manually (SHADE or SHADE_LOCKED).
- boolean clearNotificationEffects =
- (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED);
- mBarService.onPanelRevealed(clearNotificationEffects);
+ boolean clearNotificationEffects = !isPanelFullyCollapsed() &&
+ (mShowLockscreenNotifications ||
+ (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED));
+ int notificationLoad = mNotificationData.getActiveNotifications().size();
+ if (mHeadsUpManager.hasPinnedHeadsUp() && isPanelFullyCollapsed()) {
+ notificationLoad = 1;
+ } else {
+ MetricsLogger.histogram(mContext, "note_load", notificationLoad);
+ }
+ mBarService.onPanelRevealed(clearNotificationEffects, notificationLoad);
} else {
mBarService.onPanelHidden();
}
@@ -1664,6 +1639,19 @@ public abstract class BaseStatusBar extends SystemUI implements
}
/**
+ * Clear Buzz/Beep/Blink.
+ */
+ public void clearNotificationEffects() {
+ try {
+ mBarService.clearNotificationEffects();
+ } catch (RemoteException e) {
+ // Won't fail unless the world has ended.
+ }
+ }
+
+ protected abstract boolean isPanelFullyCollapsed();
+
+ /**
* Cancel this notification and tell the StatusBarManagerService / NotificationManagerService
* about the failure.
*
@@ -1693,29 +1681,39 @@ public abstract class BaseStatusBar extends SystemUI implements
if (DEBUG) {
Log.d(TAG, "createNotificationViews(notification=" + sbn);
}
+ final StatusBarIconView iconView = createIcon(sbn);
+ if (iconView == null) {
+ return null;
+ }
+
+ // Construct the expanded view.
+ NotificationData.Entry entry = new NotificationData.Entry(sbn, iconView);
+ if (!inflateViews(entry, mStackScroller)) {
+ handleNotificationError(sbn, "Couldn't expand RemoteViews for: " + sbn);
+ return null;
+ }
+ return entry;
+ }
+
+ protected StatusBarIconView createIcon(StatusBarNotification sbn) {
// Construct the icon.
Notification n = sbn.getNotification();
final StatusBarIconView iconView = new StatusBarIconView(mContext,
sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), n);
iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
- final StatusBarIcon ic = new StatusBarIcon(sbn.getPackageName(),
+ final StatusBarIcon ic = new StatusBarIcon(
sbn.getUser(),
- n.icon,
- n.iconLevel,
- n.number,
- n.tickerText);
+ sbn.getPackageName(),
+ n.getSmallIcon(),
+ n.iconLevel,
+ n.number,
+ n.tickerText);
if (!iconView.set(ic)) {
handleNotificationError(sbn, "Couldn't create icon: " + ic);
return null;
}
- // Construct the expanded view.
- NotificationData.Entry entry = new NotificationData.Entry(sbn, iconView);
- if (!inflateViews(entry, mStackScroller)) {
- handleNotificationError(sbn, "Couldn't expand RemoteViews for: " + sbn);
- return null;
- }
- return entry;
+ return iconView;
}
protected void addNotificationViews(Entry entry, RankingMap ranking) {
@@ -1755,35 +1753,35 @@ public abstract class BaseStatusBar extends SystemUI implements
entry.row.setSystemExpanded(top);
}
}
+ boolean isInvisibleChild = !mGroupManager.isVisible(entry.notification);
boolean showOnKeyguard = shouldShowOnKeyguard(entry.notification);
if ((isLockscreenPublicMode() && !mShowLockscreenNotifications) ||
(onKeyguard && (visibleNotifications >= maxKeyguardNotifications
- || !showOnKeyguard))) {
+ || !showOnKeyguard || isInvisibleChild))) {
entry.row.setVisibility(View.GONE);
- if (onKeyguard && showOnKeyguard) {
+ if (onKeyguard && showOnKeyguard && !isInvisibleChild) {
mKeyguardIconOverflowContainer.getIconsView().addNotification(entry);
}
} else {
boolean wasGone = entry.row.getVisibility() == View.GONE;
entry.row.setVisibility(View.VISIBLE);
- if (wasGone) {
- // notify the scroller of a child addition
- mStackScroller.generateAddAnimation(entry.row, true /* fromMoreCard */);
+ if (!isInvisibleChild) {
+ if (wasGone) {
+ // notify the scroller of a child addition
+ mStackScroller.generateAddAnimation(entry.row, true /* fromMoreCard */);
+ }
+ visibleNotifications++;
}
- visibleNotifications++;
}
}
- if (onKeyguard && mKeyguardIconOverflowContainer.getIconsView().getChildCount() > 0) {
- mKeyguardIconOverflowContainer.setVisibility(View.VISIBLE);
- } else {
- mKeyguardIconOverflowContainer.setVisibility(View.GONE);
- }
+ mStackScroller.updateOverflowContainerVisibility(onKeyguard
+ && mKeyguardIconOverflowContainer.getIconsView().getChildCount() > 0);
+ mStackScroller.changeViewPosition(mDismissView, mStackScroller.getChildCount() - 1);
+ mStackScroller.changeViewPosition(mEmptyShadeView, mStackScroller.getChildCount() - 2);
mStackScroller.changeViewPosition(mKeyguardIconOverflowContainer,
mStackScroller.getChildCount() - 3);
- mStackScroller.changeViewPosition(mEmptyShadeView, mStackScroller.getChildCount() - 2);
- mStackScroller.changeViewPosition(mDismissView, mStackScroller.getChildCount() - 1);
}
private boolean shouldShowOnKeyguard(StatusBarNotification sbn) {
@@ -1813,15 +1811,12 @@ public abstract class BaseStatusBar extends SystemUI implements
setShowLockscreenNotifications(show && allowedByDpm);
}
- protected abstract void haltTicker();
protected abstract void setAreThereNotifications();
protected abstract void updateNotifications();
- protected abstract void tick(StatusBarNotification n, boolean firstTime);
- protected abstract void updateExpandedViewPos(int expandedPosition);
- protected abstract boolean shouldDisableNavbarGestures();
+ public abstract boolean shouldDisableNavbarGestures();
public abstract void addNotification(StatusBarNotification notification,
- RankingMap ranking);
+ RankingMap ranking, Entry oldEntry);
protected abstract void updateNotificationRanking(RankingMap ranking);
public abstract void removeNotification(String key, RankingMap ranking);
@@ -1829,26 +1824,111 @@ public abstract class BaseStatusBar extends SystemUI implements
if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")");
final String key = notification.getKey();
- boolean wasHeadsUp = isHeadsUp(key);
- Entry oldEntry;
- if (wasHeadsUp) {
- oldEntry = mHeadsUpNotificationView.getEntry();
- } else {
- oldEntry = mNotificationData.get(key);
- }
- if (oldEntry == null) {
+ Entry entry = mNotificationData.get(key);
+ if (entry == null) {
return;
}
- final StatusBarNotification oldNotification = oldEntry.notification;
+ Notification n = notification.getNotification();
+ if (DEBUG) {
+ logUpdate(entry, n);
+ }
+ boolean applyInPlace = shouldApplyInPlace(entry, n);
+ boolean shouldInterrupt = shouldInterrupt(entry);
+ boolean alertAgain = alertAgain(entry, n);
+
+ entry.notification = notification;
+ mGroupManager.onEntryUpdated(entry, entry.notification);
+
+ boolean updateSuccessful = false;
+ if (applyInPlace) {
+ if (DEBUG) Log.d(TAG, "reusing notification for key: " + key);
+ try {
+ if (entry.icon != null) {
+ // Update the icon
+ final StatusBarIcon ic = new StatusBarIcon(
+ notification.getUser(),
+ notification.getPackageName(),
+ n.getSmallIcon(),
+ n.iconLevel,
+ n.number,
+ n.tickerText);
+ entry.icon.setNotification(n);
+ if (!entry.icon.set(ic)) {
+ handleNotificationError(notification, "Couldn't update icon: " + ic);
+ return;
+ }
+ }
+ updateNotificationViews(entry, notification);
+ updateSuccessful = true;
+ }
+ catch (RuntimeException e) {
+ // It failed to apply cleanly.
+ Log.w(TAG, "Couldn't reapply views for package " + n.contentView.getPackage(), e);
+ }
+ }
+ if (!updateSuccessful) {
+ if (DEBUG) Log.d(TAG, "not reusing notification for key: " + key);
+ final StatusBarIcon ic = new StatusBarIcon(
+ notification.getUser(),
+ notification.getPackageName(),
+ n.getSmallIcon(),
+ n.iconLevel,
+ n.number,
+ n.tickerText);
+ entry.icon.setNotification(n);
+ entry.icon.set(ic);
+ inflateViews(entry, mStackScroller);
+ }
+ updateHeadsUp(key, entry, shouldInterrupt, alertAgain);
+ mNotificationData.updateRanking(ranking);
+ updateNotifications();
+
+ // Update the veto button accordingly (and as a result, whether this row is
+ // swipe-dismissable)
+ updateNotificationVetoButton(entry.row, notification);
+
+ if (DEBUG) {
+ // Is this for you?
+ boolean isForCurrentUser = isNotificationForCurrentProfiles(notification);
+ Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
+ }
+
+ setAreThereNotifications();
+ }
+
+ protected abstract void updateHeadsUp(String key, Entry entry, boolean shouldInterrupt,
+ boolean alertAgain);
+ private void logUpdate(Entry oldEntry, Notification n) {
+ StatusBarNotification oldNotification = oldEntry.notification;
+ Log.d(TAG, "old notification: when=" + oldNotification.getNotification().when
+ + " ongoing=" + oldNotification.isOngoing()
+ + " expanded=" + oldEntry.getContentView()
+ + " contentView=" + oldNotification.getNotification().contentView
+ + " bigContentView=" + oldNotification.getNotification().bigContentView
+ + " publicView=" + oldNotification.getNotification().publicVersion
+ + " rowParent=" + oldEntry.row.getParent());
+ Log.d(TAG, "new notification: when=" + n.when
+ + " ongoing=" + oldNotification.isOngoing()
+ + " contentView=" + n.contentView
+ + " bigContentView=" + n.bigContentView
+ + " publicView=" + n.publicVersion);
+ }
+
+ /**
+ * @return whether we can just reapply the RemoteViews from a notification in-place when it is
+ * updated
+ */
+ private boolean shouldApplyInPlace(Entry entry, Notification n) {
+ StatusBarNotification oldNotification = entry.notification;
// XXX: modify when we do something more intelligent with the two content views
final RemoteViews oldContentView = oldNotification.getNotification().contentView;
- Notification n = notification.getNotification();
final RemoteViews contentView = n.contentView;
final RemoteViews oldBigContentView = oldNotification.getNotification().bigContentView;
final RemoteViews bigContentView = n.bigContentView;
- final RemoteViews oldHeadsUpContentView = oldNotification.getNotification().headsUpContentView;
+ final RemoteViews oldHeadsUpContentView
+ = oldNotification.getNotification().headsUpContentView;
final RemoteViews headsUpContentView = n.headsUpContentView;
final Notification oldPublicNotification = oldNotification.getNotification().publicVersion;
final RemoteViews oldPublicContentView = oldPublicNotification != null
@@ -1856,34 +1936,15 @@ public abstract class BaseStatusBar extends SystemUI implements
final Notification publicNotification = n.publicVersion;
final RemoteViews publicContentView = publicNotification != null
? publicNotification.contentView : null;
-
- if (DEBUG) {
- Log.d(TAG, "old notification: when=" + oldNotification.getNotification().when
- + " ongoing=" + oldNotification.isOngoing()
- + " expanded=" + oldEntry.expanded
- + " contentView=" + oldContentView
- + " bigContentView=" + oldBigContentView
- + " publicView=" + oldPublicContentView
- + " rowParent=" + oldEntry.row.getParent());
- Log.d(TAG, "new notification: when=" + n.when
- + " ongoing=" + oldNotification.isOngoing()
- + " contentView=" + contentView
- + " bigContentView=" + bigContentView
- + " publicView=" + publicContentView);
- }
-
- // Can we just reapply the RemoteViews in place?
-
- // 1U is never null
- boolean contentsUnchanged = oldEntry.expanded != null
+ boolean contentsUnchanged = entry.getContentView() != null
&& contentView.getPackage() != null
&& oldContentView.getPackage() != null
&& oldContentView.getPackage().equals(contentView.getPackage())
&& oldContentView.getLayoutId() == contentView.getLayoutId();
// large view may be null
boolean bigContentsUnchanged =
- (oldEntry.getBigContentView() == null && bigContentView == null)
- || ((oldEntry.getBigContentView() != null && bigContentView != null)
+ (entry.getExpandedContentView() == null && bigContentView == null)
+ || ((entry.getExpandedContentView() != null && bigContentView != null)
&& bigContentView.getPackage() != null
&& oldBigContentView.getPackage() != null
&& oldBigContentView.getPackage().equals(bigContentView.getPackage())
@@ -1902,172 +1963,41 @@ public abstract class BaseStatusBar extends SystemUI implements
&& oldPublicContentView.getPackage() != null
&& oldPublicContentView.getPackage().equals(publicContentView.getPackage())
&& oldPublicContentView.getLayoutId() == publicContentView.getLayoutId());
- boolean updateTicker = n.tickerText != null
- && !TextUtils.equals(n.tickerText,
- oldEntry.notification.getNotification().tickerText);
-
- final boolean shouldInterrupt = shouldInterrupt(notification);
- final boolean alertAgain = alertAgain(oldEntry, n);
- boolean updateSuccessful = false;
- if (contentsUnchanged && bigContentsUnchanged && headsUpContentsUnchanged
- && publicUnchanged) {
- if (DEBUG) Log.d(TAG, "reusing notification for key: " + key);
- oldEntry.notification = notification;
- try {
- if (oldEntry.icon != null) {
- // Update the icon
- final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(),
- notification.getUser(),
- n.icon,
- n.iconLevel,
- n.number,
- n.tickerText);
- oldEntry.icon.setNotification(n);
- if (!oldEntry.icon.set(ic)) {
- handleNotificationError(notification, "Couldn't update icon: " + ic);
- return;
- }
- }
-
- if (wasHeadsUp) {
- if (shouldInterrupt) {
- updateHeadsUpViews(oldEntry, notification);
- if (alertAgain) {
- resetHeadsUpDecayTimer();
- }
- } else {
- // we updated the notification above, so release to build a new shade entry
- mHeadsUpNotificationView.releaseAndClose();
- return;
- }
- } else {
- if (shouldInterrupt && alertAgain) {
- removeNotificationViews(key, ranking);
- addNotification(notification, ranking); //this will pop the headsup
- } else {
- updateNotificationViews(oldEntry, notification);
- }
- }
- mNotificationData.updateRanking(ranking);
- updateNotifications();
- updateSuccessful = true;
- }
- catch (RuntimeException e) {
- // It failed to add cleanly. Log, and remove the view from the panel.
- Log.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e);
- }
- }
- if (!updateSuccessful) {
- if (DEBUG) Log.d(TAG, "not reusing notification for key: " + key);
- if (wasHeadsUp) {
- if (shouldInterrupt) {
- if (DEBUG) Log.d(TAG, "rebuilding heads up for key: " + key);
- Entry newEntry = new Entry(notification, null);
- ViewGroup holder = mHeadsUpNotificationView.getHolder();
- if (inflateViewsForHeadsUp(newEntry, holder)) {
- mHeadsUpNotificationView.showNotification(newEntry);
- if (alertAgain) {
- resetHeadsUpDecayTimer();
- }
- } else {
- Log.w(TAG, "Couldn't create new updated headsup for package "
- + contentView.getPackage());
- }
- } else {
- if (DEBUG) Log.d(TAG, "releasing heads up for key: " + key);
- oldEntry.notification = notification;
- mHeadsUpNotificationView.releaseAndClose();
- return;
- }
- } else {
- if (shouldInterrupt && alertAgain) {
- if (DEBUG) Log.d(TAG, "reposting to invoke heads up for key: " + key);
- removeNotificationViews(key, ranking);
- addNotification(notification, ranking); //this will pop the headsup
- } else {
- if (DEBUG) Log.d(TAG, "rebuilding update in place for key: " + key);
- oldEntry.notification = notification;
- final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(),
- notification.getUser(),
- n.icon,
- n.iconLevel,
- n.number,
- n.tickerText);
- oldEntry.icon.setNotification(n);
- oldEntry.icon.set(ic);
- inflateViews(oldEntry, mStackScroller, wasHeadsUp);
- mNotificationData.updateRanking(ranking);
- updateNotifications();
- }
- }
- }
-
- // Update the veto button accordingly (and as a result, whether this row is
- // swipe-dismissable)
- updateNotificationVetoButton(oldEntry.row, notification);
-
- // Is this for you?
- boolean isForCurrentUser = isNotificationForCurrentProfiles(notification);
- if (DEBUG) Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
-
- // Restart the ticker if it's still running
- if (updateTicker && isForCurrentUser) {
- haltTicker();
- tick(notification, false);
- }
-
- // Recalculate the position of the sliding windows and the titles.
- setAreThereNotifications();
- updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
+ return contentsUnchanged && bigContentsUnchanged && headsUpContentsUnchanged
+ && publicUnchanged;
}
- private void updateNotificationViews(NotificationData.Entry entry,
- StatusBarNotification notification) {
- updateNotificationViews(entry, notification, false);
- }
-
- private void updateHeadsUpViews(NotificationData.Entry entry,
- StatusBarNotification notification) {
- updateNotificationViews(entry, notification, true);
- }
-
- private void updateNotificationViews(NotificationData.Entry entry,
- StatusBarNotification notification, boolean isHeadsUp) {
+ private void updateNotificationViews(Entry entry, StatusBarNotification notification) {
final RemoteViews contentView = notification.getNotification().contentView;
- final RemoteViews bigContentView = isHeadsUp
- ? notification.getNotification().headsUpContentView
- : notification.getNotification().bigContentView;
+ final RemoteViews bigContentView = notification.getNotification().bigContentView;
+ final RemoteViews headsUpContentView = notification.getNotification().headsUpContentView;
final Notification publicVersion = notification.getNotification().publicVersion;
final RemoteViews publicContentView = publicVersion != null ? publicVersion.contentView
: null;
// Reapply the RemoteViews
- contentView.reapply(mContext, entry.expanded, mOnClickHandler);
- if (bigContentView != null && entry.getBigContentView() != null) {
- bigContentView.reapply(mContext, entry.getBigContentView(),
+ contentView.reapply(mContext, entry.getContentView(), mOnClickHandler);
+ if (bigContentView != null && entry.getExpandedContentView() != null) {
+ bigContentView.reapply(mContext, entry.getExpandedContentView(),
mOnClickHandler);
}
+ View headsUpChild = entry.getHeadsUpContentView();
+ if (headsUpContentView != null && headsUpChild != null) {
+ headsUpContentView.reapply(mContext, headsUpChild, mOnClickHandler);
+ }
if (publicContentView != null && entry.getPublicContentView() != null) {
publicContentView.reapply(mContext, entry.getPublicContentView(), mOnClickHandler);
}
// update the contentIntent
- final PendingIntent contentIntent = notification.getNotification().contentIntent;
- if (contentIntent != null) {
- final View.OnClickListener listener = makeClicker(contentIntent, notification.getKey(),
- isHeadsUp);
- entry.row.setOnClickListener(listener);
- } else {
- entry.row.setOnClickListener(null);
- }
+ mNotificationClicker.register(entry.row, notification);
+
entry.row.setStatusBarNotification(notification);
entry.row.notifyContentUpdated();
entry.row.resetHeight();
}
- protected void notifyHeadsUpScreenOn(boolean screenOn) {
- if (!screenOn) {
- scheduleHeadsUpEscalation();
- }
+ protected void notifyHeadsUpScreenOff() {
+ maybeEscalateHeadsUp();
}
private boolean alertAgain(Entry oldEntry, Notification newNotification) {
@@ -2075,7 +2005,8 @@ public abstract class BaseStatusBar extends SystemUI implements
|| (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0;
}
- protected boolean shouldInterrupt(StatusBarNotification sbn) {
+ protected boolean shouldInterrupt(Entry entry) {
+ StatusBarNotification sbn = entry.notification;
if (mNotificationData.shouldFilterOut(sbn)) {
if (DEBUG) {
Log.d(TAG, "Skipping HUN check for " + sbn.getKey() + " since it's filtered out.");
@@ -2083,7 +2014,7 @@ public abstract class BaseStatusBar extends SystemUI implements
return false;
}
- if (mHeadsUpNotificationView.isSnoozed(sbn.getPackageName())) {
+ if (isSnoozedPackage(sbn)) {
return false;
}
@@ -2100,10 +2031,12 @@ public abstract class BaseStatusBar extends SystemUI implements
Notification.HEADS_UP_ALLOWED) != Notification.HEADS_UP_NEVER;
boolean accessibilityForcesLaunch = isFullscreen
&& mAccessibilityManager.isTouchExplorationEnabled();
+ boolean justLaunchedFullScreenIntent = entry.hasJustLaunchedFullScreenIntent();
boolean interrupt = (isFullscreen || (isHighPriority && (isNoisy || hasTicker)))
&& isAllowed
&& !accessibilityForcesLaunch
+ && !justLaunchedFullScreenIntent
&& mPowerManager.isScreenOn()
&& (!mStatusBarKeyguardViewManager.isShowing()
|| mStatusBarKeyguardViewManager.isOccluded())
@@ -2117,6 +2050,8 @@ public abstract class BaseStatusBar extends SystemUI implements
return interrupt;
}
+ protected abstract boolean isSnoozedPackage(StatusBarNotification sbn);
+
public void setInteracting(int barWindow, boolean interacting) {
// hook for subclasses
}
@@ -2133,9 +2068,6 @@ public abstract class BaseStatusBar extends SystemUI implements
}
public void destroy() {
- if (mSearchPanelView != null) {
- mWindowManager.removeViewImmediate(mSearchPanelView);
- }
mContext.unregisterReceiver(mBroadcastReceiver);
try {
mNotificationListener.unregisterAsSystemService();
@@ -2186,4 +2118,11 @@ public abstract class BaseStatusBar extends SystemUI implements
}
return mStatusBarKeyguardViewManager.isSecure();
}
+
+ @Override
+ public void showAssistDisclosure() {
+ if (mAssistManager != null) {
+ mAssistManager.showDisclosure();
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 0b1b883..0deff08 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
+import android.util.Pair;
import com.android.internal.statusbar.IStatusBar;
import com.android.internal.statusbar.StatusBarIcon;
@@ -57,6 +58,10 @@ public class CommandQueue extends IStatusBar.Stub {
private static final int MSG_NOTIFICATION_LIGHT_OFF = 16 << MSG_SHIFT;
private static final int MSG_NOTIFICATION_LIGHT_PULSE = 17 << MSG_SHIFT;
private static final int MSG_SHOW_SCREEN_PIN_REQUEST = 18 << MSG_SHIFT;
+ private static final int MSG_APP_TRANSITION_PENDING = 19 << MSG_SHIFT;
+ private static final int MSG_APP_TRANSITION_CANCELLED = 20 << MSG_SHIFT;
+ private static final int MSG_APP_TRANSITION_STARTING = 21 << MSG_SHIFT;
+ private static final int MSG_ASSIST_DISCLOSURE = 22 << MSG_SHIFT;
public static final int FLAG_EXCLUDE_NONE = 0;
public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -79,7 +84,7 @@ public class CommandQueue extends IStatusBar.Stub {
public void updateIcon(String slot, int index, int viewIndex,
StatusBarIcon old, StatusBarIcon icon);
public void removeIcon(String slot, int index, int viewIndex);
- public void disable(int state, boolean animate);
+ public void disable(int state1, int state2, boolean animate);
public void animateExpandNotificationsPanel();
public void animateCollapsePanels(int flags);
public void animateExpandSettingsPanel();
@@ -92,13 +97,15 @@ public class CommandQueue extends IStatusBar.Stub {
public void toggleRecentApps();
public void preloadRecentApps();
public void cancelPreloadRecentApps();
- public void showSearchPanel();
- public void hideSearchPanel();
public void setWindowState(int window, int state);
public void buzzBeepBlinked();
public void notificationLightOff();
public void notificationLightPulse(int argb, int onMillis, int offMillis);
public void showScreenPinningRequest();
+ public void appTransitionPending();
+ public void appTransitionCancelled();
+ public void appTransitionStarting(long startTime, long duration);
+ public void showAssistDisclosure();
}
public CommandQueue(Callbacks callbacks, StatusBarIconList list) {
@@ -122,10 +129,10 @@ public class CommandQueue extends IStatusBar.Stub {
}
}
- public void disable(int state) {
+ public void disable(int state1, int state2) {
synchronized (mList) {
mHandler.removeMessages(MSG_DISABLE);
- mHandler.obtainMessage(MSG_DISABLE, state, 0, null).sendToTarget();
+ mHandler.obtainMessage(MSG_DISABLE, state1, state2, null).sendToTarget();
}
}
@@ -152,7 +159,8 @@ public class CommandQueue extends IStatusBar.Stub {
public void setSystemUiVisibility(int vis, int mask) {
synchronized (mList) {
- mHandler.removeMessages(MSG_SET_SYSTEMUI_VISIBILITY);
+ // Don't coalesce these, since it might have one time flags set such as
+ // STATUS_BAR_UNHIDE which might get lost.
mHandler.obtainMessage(MSG_SET_SYSTEMUI_VISIBILITY, vis, mask, null).sendToTarget();
}
}
@@ -246,6 +254,35 @@ public class CommandQueue extends IStatusBar.Stub {
}
}
+ public void appTransitionPending() {
+ synchronized (mList) {
+ mHandler.removeMessages(MSG_APP_TRANSITION_PENDING);
+ mHandler.sendEmptyMessage(MSG_APP_TRANSITION_PENDING);
+ }
+ }
+
+ public void appTransitionCancelled() {
+ synchronized (mList) {
+ mHandler.removeMessages(MSG_APP_TRANSITION_PENDING);
+ mHandler.sendEmptyMessage(MSG_APP_TRANSITION_PENDING);
+ }
+ }
+
+ public void appTransitionStarting(long startTime, long duration) {
+ synchronized (mList) {
+ mHandler.removeMessages(MSG_APP_TRANSITION_STARTING);
+ mHandler.obtainMessage(MSG_APP_TRANSITION_STARTING, Pair.create(startTime, duration))
+ .sendToTarget();
+ }
+ }
+
+ public void showAssistDisclosure() {
+ synchronized (mList) {
+ mHandler.removeMessages(MSG_ASSIST_DISCLOSURE);
+ mHandler.obtainMessage(MSG_ASSIST_DISCLOSURE).sendToTarget();
+ }
+ }
+
private final class H extends Handler {
public void handleMessage(Message msg) {
final int what = msg.what & MSG_MASK;
@@ -277,7 +314,7 @@ public class CommandQueue extends IStatusBar.Stub {
break;
}
case MSG_DISABLE:
- mCallbacks.disable(msg.arg1, true /* animate */);
+ mCallbacks.disable(msg.arg1, msg.arg2, true /* animate */);
break;
case MSG_EXPAND_NOTIFICATIONS:
mCallbacks.animateExpandNotificationsPanel();
@@ -328,6 +365,19 @@ public class CommandQueue extends IStatusBar.Stub {
case MSG_SHOW_SCREEN_PIN_REQUEST:
mCallbacks.showScreenPinningRequest();
break;
+ case MSG_APP_TRANSITION_PENDING:
+ mCallbacks.appTransitionPending();
+ break;
+ case MSG_APP_TRANSITION_CANCELLED:
+ mCallbacks.appTransitionCancelled();
+ break;
+ case MSG_APP_TRANSITION_STARTING:
+ Pair<Long, Long> data = (Pair<Long, Long>) msg.obj;
+ mCallbacks.appTransitionStarting(data.first, data.second);
+ break;
+ case MSG_ASSIST_DISCLOSURE:
+ mCallbacks.showAssistDisclosure();
+ break;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DelegateViewHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/DelegateViewHelper.java
deleted file mode 100644
index 7ae6764..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/DelegateViewHelper.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.StatusBarManager;
-import android.content.res.Resources;
-import android.graphics.RectF;
-import android.view.MotionEvent;
-import android.view.View;
-import com.android.systemui.R;
-
-public class DelegateViewHelper {
- private View mDelegateView;
- private View mSourceView;
- private BaseStatusBar mBar;
- private int[] mTempPoint = new int[2];
- private float[] mDownPoint = new float[2];
- private float mTriggerThreshhold;
- private boolean mPanelShowing;
-
- RectF mInitialTouch = new RectF();
- private boolean mStarted;
- private boolean mSwapXY = false;
- private boolean mDisabled;
-
- public DelegateViewHelper(View sourceView) {
- setSourceView(sourceView);
- }
-
- public void setDelegateView(View view) {
- mDelegateView = view;
- }
-
- public void setBar(BaseStatusBar phoneStatusBar) {
- mBar = phoneStatusBar;
- }
-
- public boolean onInterceptTouchEvent(MotionEvent event) {
- if (mSourceView == null || mDelegateView == null || mBar.shouldDisableNavbarGestures()) {
- return false;
- }
-
- mSourceView.getLocationOnScreen(mTempPoint);
- final float sourceX = mTempPoint[0];
- final float sourceY = mTempPoint[1];
-
- final int action = event.getAction();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- mPanelShowing = mDelegateView.getVisibility() == View.VISIBLE;
- mDownPoint[0] = event.getX();
- mDownPoint[1] = event.getY();
- mStarted = mInitialTouch.contains(mDownPoint[0] + sourceX, mDownPoint[1] + sourceY);
- break;
- }
-
- if (!mStarted) {
- return false;
- }
-
- if (!mDisabled && !mPanelShowing && action == MotionEvent.ACTION_MOVE) {
- final int historySize = event.getHistorySize();
- for (int k = 0; k < historySize + 1; k++) {
- float x = k < historySize ? event.getHistoricalX(k) : event.getX();
- float y = k < historySize ? event.getHistoricalY(k) : event.getY();
- final float distance = mSwapXY ? (mDownPoint[0] - x) : (mDownPoint[1] - y);
- if (distance > mTriggerThreshhold) {
- mBar.showSearchPanel();
- mPanelShowing = true;
- break;
- }
- }
- }
-
- if (action == MotionEvent.ACTION_DOWN) {
- mBar.setInteracting(StatusBarManager.WINDOW_NAVIGATION_BAR, true);
- } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
- mBar.setInteracting(StatusBarManager.WINDOW_NAVIGATION_BAR, false);
- }
-
- mDelegateView.getLocationOnScreen(mTempPoint);
- final float delegateX = mTempPoint[0];
- final float delegateY = mTempPoint[1];
-
- float deltaX = sourceX - delegateX;
- float deltaY = sourceY - delegateY;
- event.offsetLocation(deltaX, deltaY);
- mDelegateView.dispatchTouchEvent(event);
- event.offsetLocation(-deltaX, -deltaY);
- return mPanelShowing;
- }
-
- public void setSourceView(View view) {
- mSourceView = view;
- if (mSourceView != null) {
- Resources r = mSourceView.getContext().getResources();
- mTriggerThreshhold = r.getDimensionPixelSize(R.dimen.navigation_bar_min_swipe_distance);
- }
- }
-
- /**
- * Selects the initial touch region based on a list of views. This is meant to be called by
- * a container widget on children over which the initial touch should be detected. Note this
- * will compute a minimum bound that contains all specified views.
- *
- * @param views
- */
- public void setInitialTouchRegion(View ... views) {
- RectF bounds = new RectF();
- int p[] = new int[2];
- for (int i = 0; i < views.length; i++) {
- View view = views[i];
- if (view == null) continue;
- view.getLocationOnScreen(p);
- if (i == 0) {
- bounds.set(p[0], p[1], p[0] + view.getWidth(), p[1] + view.getHeight());
- } else {
- bounds.union(p[0], p[1], p[0] + view.getWidth(), p[1] + view.getHeight());
- }
- }
- mInitialTouch.set(bounds);
- }
-
- /**
- * When rotation is set to NO_SENSOR, then this allows swapping x/y for gesture detection
- * @param swap
- */
- public void setSwapXY(boolean swap) {
- mSwapXY = swap;
- }
-
- public void setDisabled(boolean disabled) {
- mDisabled = disabled;
- }
-} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DismissViewButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/DismissViewButton.java
index f2a5673..a323684 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/DismissViewButton.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/DismissViewButton.java
@@ -21,12 +21,9 @@ import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
-import android.os.SystemClock;
import android.util.AttributeSet;
-import android.view.Choreographer;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewRootImpl;
import android.widget.Button;
import com.android.systemui.R;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
@@ -51,15 +48,14 @@ public class DismissViewButton extends Button {
public DismissViewButton(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- mAnimatedDismissDrawable = (AnimatedVectorDrawable) getContext().getResources().getDrawable(
+ mAnimatedDismissDrawable = (AnimatedVectorDrawable) getContext().getDrawable(
R.drawable.dismiss_all_shape_animation).mutate();
mAnimatedDismissDrawable.setCallback(this);
mAnimatedDismissDrawable.setBounds(0,
0,
mAnimatedDismissDrawable.getIntrinsicWidth(),
mAnimatedDismissDrawable.getIntrinsicHeight());
- mStaticDismissDrawable = getContext().getResources().getDrawable(
- R.drawable.dismiss_all_shape);
+ mStaticDismissDrawable = getContext().getDrawable(R.drawable.dismiss_all_shape);
mStaticDismissDrawable.setBounds(0,
0,
mStaticDismissDrawable.getIntrinsicWidth(),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
index c9f0260..15a092c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
@@ -162,20 +162,20 @@ public class DragDownHelper implements Gefingerpoken {
? RUBBERBAND_FACTOR_EXPANDABLE
: RUBBERBAND_FACTOR_STATIC;
float rubberband = heightDelta * rubberbandFactor;
- if (expandable && (rubberband + child.getMinHeight()) > child.getMaxHeight()) {
- float overshoot = (rubberband + child.getMinHeight()) - child.getMaxHeight();
+ if (expandable && (rubberband + child.getMinHeight()) > child.getMaxContentHeight()) {
+ float overshoot = (rubberband + child.getMinHeight()) - child.getMaxContentHeight();
overshoot *= (1 - RUBBERBAND_FACTOR_STATIC);
rubberband -= overshoot;
}
- child.setActualHeight((int) (child.getMinHeight() + rubberband));
+ child.setContentHeight((int) (child.getMinHeight() + rubberband));
}
private void cancelExpansion(final ExpandableView child) {
- if (child.getActualHeight() == child.getMinHeight()) {
+ if (child.getContentHeight() == child.getMinHeight()) {
return;
}
- ObjectAnimator anim = ObjectAnimator.ofInt(child, "actualHeight",
- child.getActualHeight(), child.getMinHeight());
+ ObjectAnimator anim = ObjectAnimator.ofInt(child, "contentHeight",
+ child.getContentHeight(), child.getMinHeight());
anim.setInterpolator(mInterpolator);
anim.setDuration(SPRING_BACK_ANIMATION_LENGTH_MS);
anim.addListener(new AnimatorListenerAdapter() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java b/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java
index 0825aa3..5db0699 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java
@@ -20,11 +20,9 @@ import android.content.Context;
import android.content.res.Configuration;
import android.util.AttributeSet;
import android.view.View;
-import android.view.animation.Interpolator;
import android.widget.TextView;
import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
public class EmptyShadeView extends StackScrollerDecorView {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 8ad8406..b88e5ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -16,9 +16,13 @@
package com.android.systemui.statusbar;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.AnimationDrawable;
+import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.service.notification.StatusBarNotification;
import android.util.AttributeSet;
@@ -26,12 +30,25 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.ViewStub;
import android.view.accessibility.AccessibilityEvent;
+import android.view.animation.LinearInterpolator;
import android.widget.ImageView;
+
import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.phone.PhoneStatusBar;
+import com.android.systemui.statusbar.stack.NotificationChildrenContainer;
+import com.android.systemui.statusbar.stack.StackScrollState;
+import com.android.systemui.statusbar.stack.StackStateAnimator;
+import com.android.systemui.statusbar.stack.StackViewState;
+
+import java.util.List;
public class ExpandableNotificationRow extends ActivatableNotificationView {
+
+ private static final int DEFAULT_DIVIDER_ALPHA = 0x29;
+ private static final int COLORED_DIVIDER_ALPHA = 0x7B;
+ private final LinearInterpolator mLinearInterpolator = new LinearInterpolator();
private int mRowMinHeight;
- private int mRowMaxHeight;
/** Does this row contain layouts that can adapt to row expansion */
private boolean mExpandable;
@@ -45,7 +62,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
private boolean mShowingPublic;
private boolean mSensitive;
private boolean mShowingPublicInitialized;
- private boolean mShowingPublicForIntrinsicHeight;
+ private boolean mHideSensitiveForIntrinsicHeight;
/**
* Is this notification expanded by the system. The expansion state can be overridden by the
@@ -61,15 +78,45 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
private NotificationContentView mPublicLayout;
private NotificationContentView mPrivateLayout;
private int mMaxExpandHeight;
+ private int mHeadsUpHeight;
private View mVetoButton;
private boolean mClearable;
private ExpansionLogger mLogger;
private String mLoggingKey;
private boolean mWasReset;
private NotificationGuts mGuts;
-
private StatusBarNotification mStatusBarNotification;
private boolean mIsHeadsUp;
+ private View mExpandButton;
+ private View mExpandButtonDivider;
+ private ViewStub mExpandButtonStub;
+ private ViewStub mChildrenContainerStub;
+ private NotificationGroupManager mGroupManager;
+ private View mExpandButtonContainer;
+ private boolean mChildrenExpanded;
+ private NotificationChildrenContainer mChildrenContainer;
+ private ValueAnimator mChildExpandAnimator;
+ private float mChildrenExpandProgress;
+ private float mExpandButtonStart;
+ private ViewStub mGutsStub;
+ private boolean mHasExpandAction;
+ private boolean mIsSystemChildExpanded;
+ private boolean mIsPinned;
+ private OnClickListener mExpandClickListener = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mGroupManager.setGroupExpanded(mStatusBarNotification,
+ !mChildrenExpanded);
+ }
+ };
+
+ public NotificationContentView getPrivateLayout() {
+ return mPrivateLayout;
+ }
+
+ public NotificationContentView getPublicLayout() {
+ return mPublicLayout;
+ }
public void setIconAnimationRunning(boolean running) {
setIconAnimationRunning(running, mPublicLayout);
@@ -80,8 +127,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
if (layout != null) {
View contractedChild = layout.getContractedChild();
View expandedChild = layout.getExpandedChild();
+ View headsUpChild = layout.getHeadsUpChild();
setIconAnimationRunningForChild(running, contractedChild);
setIconAnimationRunningForChild(running, expandedChild);
+ setIconAnimationRunningForChild(running, headsUpChild);
}
}
@@ -119,14 +168,137 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
public void setStatusBarNotification(StatusBarNotification statusBarNotification) {
mStatusBarNotification = statusBarNotification;
updateVetoButton();
+ updateExpandButton();
}
public StatusBarNotification getStatusBarNotification() {
return mStatusBarNotification;
}
+ public boolean isHeadsUp() {
+ return mIsHeadsUp;
+ }
+
public void setHeadsUp(boolean isHeadsUp) {
+ int intrinsicBefore = getIntrinsicHeight();
mIsHeadsUp = isHeadsUp;
+ mPrivateLayout.setHeadsUp(isHeadsUp);
+ if (intrinsicBefore != getIntrinsicHeight()) {
+ notifyHeightChanged(false /* needsAnimation */);
+ }
+ }
+
+ public void setGroupManager(NotificationGroupManager groupManager) {
+ mGroupManager = groupManager;
+ }
+
+ public void addChildNotification(ExpandableNotificationRow row) {
+ addChildNotification(row, -1);
+ }
+
+ /**
+ * Add a child notification to this view.
+ *
+ * @param row the row to add
+ * @param childIndex the index to add it at, if -1 it will be added at the end
+ */
+ public void addChildNotification(ExpandableNotificationRow row, int childIndex) {
+ if (mChildrenContainer == null) {
+ mChildrenContainerStub.inflate();
+ }
+ mChildrenContainer.addNotification(row, childIndex);
+ }
+
+ public void removeChildNotification(ExpandableNotificationRow row) {
+ if (mChildrenContainer != null) {
+ mChildrenContainer.removeNotification(row);
+ }
+ }
+
+ @Override
+ public boolean areChildrenExpanded() {
+ return mChildrenExpanded;
+ }
+
+ public List<ExpandableNotificationRow> getNotificationChildren() {
+ return mChildrenContainer == null ? null : mChildrenContainer.getNotificationChildren();
+ }
+
+ /**
+ * Apply the order given in the list to the children.
+ *
+ * @param childOrder the new list order
+ * @return whether the list order has changed
+ */
+ public boolean applyChildOrder(List<ExpandableNotificationRow> childOrder) {
+ return mChildrenContainer != null && mChildrenContainer.applyChildOrder(childOrder);
+ }
+
+ public void getChildrenStates(StackScrollState resultState) {
+ if (mChildrenExpanded) {
+ StackViewState parentState = resultState.getViewStateForView(this);
+ mChildrenContainer.getState(resultState, parentState);
+ }
+ }
+
+ public void applyChildrenState(StackScrollState state) {
+ if (mChildrenExpanded) {
+ mChildrenContainer.applyState(state);
+ }
+ }
+
+ public void prepareExpansionChanged(StackScrollState state) {
+ if (mChildrenExpanded) {
+ mChildrenContainer.prepareExpansionChanged(state);
+ }
+ }
+
+ public void startChildAnimation(StackScrollState finalState,
+ StackStateAnimator stateAnimator, boolean withDelays, long delay, long duration) {
+ if (mChildrenExpanded) {
+ mChildrenContainer.startAnimationToState(finalState, stateAnimator, withDelays, delay,
+ duration);
+ }
+ }
+
+ public ExpandableNotificationRow getViewAtPosition(float y) {
+ if (!mChildrenExpanded) {
+ return this;
+ } else {
+ ExpandableNotificationRow view = mChildrenContainer.getViewAtPosition(y);
+ return view == null ? this : view;
+ }
+ }
+
+ public NotificationGuts getGuts() {
+ return mGuts;
+ }
+
+ protected int calculateContentHeightFromActualHeight(int actualHeight) {
+ int realActualHeight = actualHeight;
+ if (hasBottomDecor()) {
+ realActualHeight -= getBottomDecorHeight();
+ }
+ realActualHeight = Math.max(getMinHeight(), realActualHeight);
+ return realActualHeight;
+ }
+
+ /**
+ * Set this notification to be pinned to the top if {@link #isHeadsUp()} is true. By doing this
+ * the notification will be rendered on top of the screen.
+ *
+ * @param pinned whether it is pinned
+ */
+ public void setPinned(boolean pinned) {
+ mIsPinned = pinned;
+ }
+
+ public boolean isPinned() {
+ return mIsPinned;
+ }
+
+ public int getHeadsUpHeight() {
+ return mHeadsUpHeight;
}
public interface ExpansionLogger {
@@ -145,7 +317,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
super.reset();
mRowMinHeight = 0;
final boolean wasExpanded = isExpanded();
- mRowMaxHeight = 0;
+ mMaxViewHeight = 0;
mExpandable = false;
mHasUserChangedExpansion = false;
mUserLocked = false;
@@ -165,6 +337,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
resetActualHeight();
}
mMaxExpandHeight = 0;
+ mHeadsUpHeight = 0;
mWasReset = true;
onHeightReset();
requestLayout();
@@ -180,21 +353,97 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
super.onFinishInflate();
mPublicLayout = (NotificationContentView) findViewById(R.id.expandedPublic);
mPrivateLayout = (NotificationContentView) findViewById(R.id.expanded);
- ViewStub gutsStub = (ViewStub) findViewById(R.id.notification_guts_stub);
- gutsStub.setOnInflateListener(new ViewStub.OnInflateListener() {
+ mGutsStub = (ViewStub) findViewById(R.id.notification_guts_stub);
+ mGutsStub.setOnInflateListener(new ViewStub.OnInflateListener() {
@Override
public void onInflate(ViewStub stub, View inflated) {
mGuts = (NotificationGuts) inflated;
mGuts.setClipTopAmount(getClipTopAmount());
mGuts.setActualHeight(getActualHeight());
+ mGutsStub = null;
+ }
+ });
+ mExpandButtonStub = (ViewStub) findViewById(R.id.more_button_stub);
+ mExpandButtonStub.setOnInflateListener(new ViewStub.OnInflateListener() {
+
+ @Override
+ public void onInflate(ViewStub stub, View inflated) {
+ mExpandButtonContainer = inflated;
+ mExpandButton = inflated.findViewById(R.id.notification_expand_button);
+ mExpandButtonDivider = inflated.findViewById(R.id.notification_expand_divider);
+ mExpandButtonContainer.setOnClickListener(mExpandClickListener);
+ }
+ });
+ mChildrenContainerStub = (ViewStub) findViewById(R.id.child_container_stub);
+ mChildrenContainerStub.setOnInflateListener(new ViewStub.OnInflateListener() {
+
+ @Override
+ public void onInflate(ViewStub stub, View inflated) {
+ mChildrenContainer = (NotificationChildrenContainer) inflated;
+ mChildrenContainer.setCollapseClickListener(mExpandClickListener);
+ updateChildrenVisibility(false);
}
});
mVetoButton = findViewById(R.id.veto);
}
+ public void inflateGuts() {
+ if (mGuts == null) {
+ mGutsStub.inflate();
+ }
+ }
+
+ private void updateChildrenVisibility(boolean animated) {
+ if (mChildrenContainer == null) {
+ return;
+ }
+ if (mChildExpandAnimator != null) {
+ mChildExpandAnimator.cancel();
+ }
+ float targetProgress = mChildrenExpanded ? 1.0f : 0.0f;
+ if (animated) {
+ if (mChildrenExpanded) {
+ mChildrenContainer.setVisibility(VISIBLE);
+ }
+ mExpandButtonStart = mExpandButtonContainer.getTranslationY();
+ mChildExpandAnimator = ValueAnimator.ofFloat(mChildrenExpandProgress, targetProgress);
+ mChildExpandAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ setChildrenExpandProgress((float) animation.getAnimatedValue());
+ }
+ });
+ mChildExpandAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mChildExpandAnimator = null;
+ if (!mChildrenExpanded) {
+ mChildrenContainer.setVisibility(INVISIBLE);
+ }
+ }
+ });
+ mChildExpandAnimator.setInterpolator(mLinearInterpolator);
+ mChildExpandAnimator.setDuration(
+ StackStateAnimator.ANIMATION_DURATION_EXPAND_CLICKED);
+ mChildExpandAnimator.start();
+ } else {
+ setChildrenExpandProgress(targetProgress);
+ mChildrenContainer.setVisibility(mChildrenExpanded ? VISIBLE : INVISIBLE);
+ }
+ }
+
+ private void setChildrenExpandProgress(float progress) {
+ mChildrenExpandProgress = progress;
+ updateExpandButtonAppearance();
+ NotificationContentView showingLayout = getShowingLayout();
+ float alpha = 1.0f - mChildrenExpandProgress;
+ alpha = PhoneStatusBar.ALPHA_OUT.getInterpolation(alpha);
+ showingLayout.setAlpha(alpha);
+ }
+
@Override
- public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
- if (super.onRequestSendAccessibilityEvent(child, event)) {
+ public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) {
+ if (super.onRequestSendAccessibilityEventInternal(child, event)) {
// Add a record for the entire layout since its content is somehow small.
// The event comes from a leaf view that is interacted with.
AccessibilityEvent record = AccessibilityEvent.obtain();
@@ -217,7 +466,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
public void setHeightRange(int rowMinHeight, int rowMaxHeight) {
mRowMinHeight = rowMinHeight;
- mRowMaxHeight = rowMaxHeight;
+ mMaxViewHeight = rowMaxHeight;
}
public boolean isExpandable() {
@@ -281,7 +530,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
if (expand != mIsSystemExpanded) {
final boolean wasExpanded = isExpanded();
mIsSystemExpanded = expand;
- notifyHeightChanged();
+ notifyHeightChanged(false /* needsAnimation */);
logExpansionEvent(false, wasExpanded);
}
}
@@ -295,7 +544,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
mExpansionDisabled = expansionDisabled;
logExpansionEvent(false, wasExpanded);
if (wasExpanded != isExpanded()) {
- notifyHeightChanged();
+ notifyHeightChanged(false /* needsAnimation */);
}
}
}
@@ -313,9 +562,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
public void applyExpansionToLayout() {
boolean expand = isExpanded();
if (expand && mExpandable) {
- setActualHeight(mMaxExpandHeight);
+ setContentHeight(mMaxExpandHeight);
} else {
- setActualHeight(mRowMinHeight);
+ setContentHeight(mRowMinHeight);
}
}
@@ -325,12 +574,34 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
return getActualHeight();
}
boolean inExpansionState = isExpanded();
- if (!inExpansionState) {
- // not expanded, so we return the collapsed size
+ int maxContentHeight;
+ if (mSensitive && mHideSensitiveForIntrinsicHeight) {
return mRowMinHeight;
+ } else if (mIsHeadsUp) {
+ if (inExpansionState) {
+ maxContentHeight = Math.max(mMaxExpandHeight, mHeadsUpHeight);
+ } else {
+ maxContentHeight = Math.max(mRowMinHeight, mHeadsUpHeight);
+ }
+ } else if ((!inExpansionState && !mChildrenExpanded)) {
+ maxContentHeight = mRowMinHeight;
+ } else if (mChildrenExpanded) {
+ maxContentHeight = mChildrenContainer.getIntrinsicHeight();
+ } else {
+ maxContentHeight = getMaxExpandHeight();
}
+ return maxContentHeight + getBottomDecorHeight();
+ }
- return mShowingPublicForIntrinsicHeight ? mRowMinHeight : getMaxExpandHeight();
+ @Override
+ protected boolean hasBottomDecor() {
+ return BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS
+ && !mIsHeadsUp && mGroupManager.hasGroupChildren(mStatusBarNotification);
+ }
+
+ @Override
+ protected boolean canHaveBottomDecor() {
+ return BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS && !mIsHeadsUp;
}
/**
@@ -343,25 +614,52 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
*/
private boolean isExpanded() {
return !mExpansionDisabled
- && (!hasUserChangedExpansion() && isSystemExpanded() || isUserExpanded());
+ && (!hasUserChangedExpansion() && (isSystemExpanded() || isSystemChildExpanded())
+ || isUserExpanded());
+ }
+
+ private boolean isSystemChildExpanded() {
+ return mIsSystemChildExpanded;
+ }
+
+ public void setSystemChildExpanded(boolean expanded) {
+ mIsSystemChildExpanded = expanded;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
boolean updateExpandHeight = mMaxExpandHeight == 0 && !mWasReset;
- updateMaxExpandHeight();
+ updateMaxHeights();
if (updateExpandHeight) {
applyExpansionToLayout();
}
mWasReset = false;
}
- private void updateMaxExpandHeight() {
+ @Override
+ protected boolean isChildInvisible(View child) {
+
+ // We don't want to layout the ChildrenContainer if this is a heads-up view, otherwise the
+ // view will get too high and the shadows will be off.
+ boolean isInvisibleChildContainer = child == mChildrenContainer && mIsHeadsUp;
+ return super.isChildInvisible(child) || isInvisibleChildContainer;
+ }
+
+ private void updateMaxHeights() {
int intrinsicBefore = getIntrinsicHeight();
- mMaxExpandHeight = mPrivateLayout.getMaxHeight();
+ View expandedChild = mPrivateLayout.getExpandedChild();
+ if (expandedChild == null) {
+ expandedChild = mPrivateLayout.getContractedChild();
+ }
+ mMaxExpandHeight = expandedChild.getHeight();
+ View headsUpChild = mPrivateLayout.getHeadsUpChild();
+ if (headsUpChild == null) {
+ headsUpChild = mPrivateLayout.getContractedChild();
+ }
+ mHeadsUpHeight = headsUpChild.getHeight();
if (intrinsicBefore != getIntrinsicHeight()) {
- notifyHeightChanged();
+ notifyHeightChanged(false /* needsAnimation */);
}
}
@@ -370,7 +668,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
}
public void setHideSensitiveForIntrinsicHeight(boolean hideSensitive) {
- mShowingPublicForIntrinsicHeight = mSensitive && hideSensitive;
+ mHideSensitiveForIntrinsicHeight = hideSensitive;
}
public void setHideSensitive(boolean hideSensitive, boolean animated, long delay,
@@ -428,8 +726,127 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
mVetoButton.setVisibility(isClearable() && !mShowingPublic ? View.VISIBLE : View.GONE);
}
+ public void setChildrenExpanded(boolean expanded, boolean animate) {
+ mChildrenExpanded = expanded;
+ updateChildrenVisibility(animate);
+ }
+
+ public void updateExpandButton() {
+ boolean hasExpand = hasBottomDecor();
+ if (hasExpand != mHasExpandAction) {
+ if (hasExpand) {
+ if (mExpandButtonContainer == null) {
+ mExpandButtonStub.inflate();
+ }
+ mExpandButtonContainer.setVisibility(View.VISIBLE);
+ updateExpandButtonAppearance();
+ updateExpandButtonColor();
+ } else if (mExpandButtonContainer != null) {
+ mExpandButtonContainer.setVisibility(View.GONE);
+ }
+ notifyHeightChanged(true /* needsAnimation */);
+ }
+ mHasExpandAction = hasExpand;
+ }
+
+ private void updateExpandButtonAppearance() {
+ if (mExpandButtonContainer == null) {
+ return;
+ }
+ float expandButtonAlpha = 0.0f;
+ float expandButtonTranslation = 0.0f;
+ float containerTranslation = 0.0f;
+ int minHeight = getMinHeight();
+ if (!mChildrenExpanded || mChildExpandAnimator != null) {
+ int expandActionHeight = getBottomDecorHeight();
+ int translationY = getActualHeight() - expandActionHeight;
+ if (translationY > minHeight) {
+ containerTranslation = translationY;
+ expandButtonAlpha = 1.0f;
+ expandButtonTranslation = 0.0f;
+ } else {
+ containerTranslation = minHeight;
+ float progress = expandActionHeight != 0
+ ? (minHeight - translationY) / (float) expandActionHeight
+ : 1.0f;
+ expandButtonTranslation = -progress * expandActionHeight * 0.7f;
+ float alphaProgress = Math.min(progress / 0.7f, 1.0f);
+ alphaProgress = PhoneStatusBar.ALPHA_OUT.getInterpolation(alphaProgress);
+ expandButtonAlpha = 1.0f - alphaProgress;
+ }
+ }
+ if (mChildExpandAnimator != null || mChildrenExpanded) {
+ expandButtonAlpha = (1.0f - mChildrenExpandProgress)
+ * expandButtonAlpha;
+ expandButtonTranslation = (1.0f - mChildrenExpandProgress)
+ * expandButtonTranslation;
+ float newTranslation = -getBottomDecorHeight();
+
+ // We don't want to take the actual height of the view as this is already
+ // interpolated by a custom interpolator leading to a confusing animation. We want
+ // to have a stable end value to interpolate in between
+ float collapsedHeight = !mChildrenExpanded
+ ? Math.max(StackStateAnimator.getFinalActualHeight(this)
+ - getBottomDecorHeight(), minHeight)
+ : mExpandButtonStart;
+ float translationProgress = mFastOutSlowInInterpolator.getInterpolation(
+ mChildrenExpandProgress);
+ containerTranslation = (1.0f - translationProgress) * collapsedHeight
+ + translationProgress * newTranslation;
+ }
+ mExpandButton.setAlpha(expandButtonAlpha);
+ mExpandButtonDivider.setAlpha(expandButtonAlpha);
+ mExpandButton.setTranslationY(expandButtonTranslation);
+ mExpandButtonContainer.setTranslationY(containerTranslation);
+ NotificationContentView showingLayout = getShowingLayout();
+ float layoutTranslation =
+ mExpandButtonContainer.getTranslationY() - showingLayout.getContentHeight();
+ layoutTranslation = Math.min(layoutTranslation, 0);
+ if (!mChildrenExpanded && mChildExpandAnimator == null) {
+ // Needed for the DragDownHelper in order not to jump there, as the position
+ // can be negative for a short time.
+ layoutTranslation = 0;
+ }
+ showingLayout.setTranslationY(layoutTranslation);
+ if (mChildrenContainer != null) {
+ mChildrenContainer.setTranslationY(
+ mExpandButtonContainer.getTranslationY() + getBottomDecorHeight());
+ }
+ }
+
+ private void updateExpandButtonColor() {
+ // TODO: This needs some more baking, currently only the divider is colored according to
+ // the tint, but legacy black doesn't work yet perfectly for the button etc.
+ int color = getRippleColor();
+ if (color == mNormalRippleColor) {
+ color = 0;
+ }
+ if (mExpandButtonDivider != null) {
+ applyTint(mExpandButtonDivider, color);
+ }
+ if (mChildrenContainer != null) {
+ mChildrenContainer.setTintColor(color);
+ }
+ }
+
+ public static void applyTint(View v, int color) {
+ int alpha;
+ if (color != 0) {
+ alpha = COLORED_DIVIDER_ALPHA;
+ } else {
+ color = 0xff000000;
+ alpha = DEFAULT_DIVIDER_ALPHA;
+ }
+ if (v.getBackground() instanceof ColorDrawable) {
+ ColorDrawable background = (ColorDrawable) v.getBackground();
+ background.mutate();
+ background.setColor(color);
+ background.setAlpha(alpha);
+ }
+ }
+
public int getMaxExpandHeight() {
- return mShowingPublicForIntrinsicHeight ? mRowMinHeight : mMaxExpandHeight;
+ return mMaxExpandHeight;
}
@Override
@@ -439,18 +856,25 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
}
@Override
+ protected View getContentView() {
+ return getShowingLayout();
+ }
+
+ @Override
public void setActualHeight(int height, boolean notifyListeners) {
- mPrivateLayout.setActualHeight(height);
- mPublicLayout.setActualHeight(height);
+ super.setActualHeight(height, notifyListeners);
+ int contentHeight = calculateContentHeightFromActualHeight(height);
+ mPrivateLayout.setContentHeight(contentHeight);
+ mPublicLayout.setContentHeight(contentHeight);
if (mGuts != null) {
mGuts.setActualHeight(height);
}
invalidate();
- super.setActualHeight(height, notifyListeners);
+ updateExpandButtonAppearance();
}
@Override
- public int getMaxHeight() {
+ public int getMaxContentHeight() {
NotificationContentView showingLayout = getShowingLayout();
return showingLayout.getMaxHeight();
}
@@ -484,12 +908,18 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
return mShowingPublic ? mPublicLayout : mPrivateLayout;
}
+ @Override
+ public void setShowingLegacyBackground(boolean showing) {
+ super.setShowingLegacyBackground(showing);
+ mPrivateLayout.setShowingLegacyBackground(showing);
+ mPublicLayout.setShowingLegacyBackground(showing);
+ }
+
public void setExpansionLogger(ExpansionLogger logger, String key) {
mLogger = logger;
mLoggingKey = key;
}
-
private void logExpansionEvent(boolean userAction, boolean wasExpanded) {
final boolean nowExpanded = isExpanded();
if (wasExpanded != nowExpanded && mLogger != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
index a18fff2..d77e050 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
@@ -24,16 +24,21 @@ import android.util.AttributeSet;
import android.view.View;
import android.view.ViewOutlineProvider;
+import com.android.systemui.R;
+
/**
* Like {@link ExpandableView}, but setting an outline for the height and clipping.
*/
public abstract class ExpandableOutlineView extends ExpandableView {
private final Rect mOutlineRect = new Rect();
+ protected final int mRoundedRectCornerRadius;
private boolean mCustomOutline;
public ExpandableOutlineView(Context context, AttributeSet attrs) {
super(context, attrs);
+ mRoundedRectCornerRadius = getResources().getDimensionPixelSize(
+ R.dimen.notification_material_rounded_rect_radius);
setOutlineProvider(new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
@@ -43,7 +48,7 @@ public abstract class ExpandableOutlineView extends ExpandableView {
getWidth(),
Math.max(getActualHeight(), mClipTopAmount));
} else {
- outline.setRect(mOutlineRect);
+ outline.setRoundRect(mOutlineRect, mRoundedRectCornerRadius);
}
}
});
@@ -66,12 +71,14 @@ public abstract class ExpandableOutlineView extends ExpandableView {
setOutlineRect(rect.left, rect.top, rect.right, rect.bottom);
} else {
mCustomOutline = false;
+ setClipToOutline(false);
invalidateOutline();
}
}
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);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
index ebc663c..08a6603 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
@@ -33,36 +33,47 @@ import java.util.ArrayList;
*/
public abstract class ExpandableView extends FrameLayout {
- private final int mMaxNotificationHeight;
-
- private OnHeightChangedListener mOnHeightChangedListener;
+ private final int mBottomDecorHeight;
+ protected OnHeightChangedListener mOnHeightChangedListener;
+ protected int mMaxViewHeight;
private int mActualHeight;
protected int mClipTopAmount;
private boolean mActualHeightInitialized;
private boolean mDark;
private ArrayList<View> mMatchParentViews = new ArrayList<View>();
+ private int mClipTopOptimization;
+ private static Rect mClipRect = new Rect();
+ private boolean mWillBeGone;
public ExpandableView(Context context, AttributeSet attrs) {
super(context, attrs);
- mMaxNotificationHeight = getResources().getDimensionPixelSize(
+ mMaxViewHeight = getResources().getDimensionPixelSize(
R.dimen.notification_max_height);
+ mBottomDecorHeight = resolveBottomDecorHeight();
+ }
+
+ protected int resolveBottomDecorHeight() {
+ return getResources().getDimensionPixelSize(
+ R.dimen.notification_bottom_decor_height);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int ownMaxHeight = mMaxNotificationHeight;
+ int ownMaxHeight = mMaxViewHeight;
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
boolean hasFixedHeight = heightMode == MeasureSpec.EXACTLY;
- boolean isHeightLimited = heightMode == MeasureSpec.AT_MOST;
- if (hasFixedHeight || isHeightLimited) {
- int size = MeasureSpec.getSize(heightMeasureSpec);
- ownMaxHeight = Math.min(ownMaxHeight, size);
+ if (hasFixedHeight) {
+ // We have a height set in our layout, so we want to be at most as big as given
+ ownMaxHeight = Math.min(MeasureSpec.getSize(heightMeasureSpec), ownMaxHeight);
}
int newHeightSpec = MeasureSpec.makeMeasureSpec(ownMaxHeight, MeasureSpec.AT_MOST);
int maxChildHeight = 0;
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
+ if (child.getVisibility() == GONE || isChildInvisible(child)) {
+ continue;
+ }
int childHeightSpec = newHeightSpec;
ViewGroup.LayoutParams layoutParams = child.getLayoutParams();
if (layoutParams.height != ViewGroup.LayoutParams.MATCH_PARENT) {
@@ -81,7 +92,7 @@ public abstract class ExpandableView extends FrameLayout {
mMatchParentViews.add(child);
}
}
- int ownHeight = hasFixedHeight ? ownMaxHeight : maxChildHeight;
+ int ownHeight = hasFixedHeight ? ownMaxHeight : Math.min(ownMaxHeight, maxChildHeight);
newHeightSpec = MeasureSpec.makeMeasureSpec(ownHeight, MeasureSpec.EXACTLY);
for (View child : mMatchParentViews) {
child.measure(getChildMeasureSpec(
@@ -90,6 +101,10 @@ public abstract class ExpandableView extends FrameLayout {
}
mMatchParentViews.clear();
int width = MeasureSpec.getSize(widthMeasureSpec);
+ if (canHaveBottomDecor()) {
+ // We always account for the expandAction as well.
+ ownHeight += mBottomDecorHeight;
+ }
setMeasuredDimension(width, ownHeight);
}
@@ -99,9 +114,10 @@ public abstract class ExpandableView extends FrameLayout {
if (!mActualHeightInitialized && mActualHeight == 0) {
int initialHeight = getInitialHeight();
if (initialHeight != 0) {
- setActualHeight(initialHeight);
+ setContentHeight(initialHeight);
}
}
+ updateClipping();
}
/**
@@ -118,6 +134,14 @@ public abstract class ExpandableView extends FrameLayout {
}
@Override
+ public boolean dispatchGenericMotionEvent(MotionEvent ev) {
+ if (filterMotionEvent(ev)) {
+ return super.dispatchGenericMotionEvent(ev);
+ }
+ return false;
+ }
+
+ @Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (filterMotionEvent(ev)) {
return super.dispatchTouchEvent(ev);
@@ -127,6 +151,8 @@ public abstract class ExpandableView extends FrameLayout {
protected boolean filterMotionEvent(MotionEvent event) {
return event.getActionMasked() != MotionEvent.ACTION_DOWN
+ && event.getActionMasked() != MotionEvent.ACTION_HOVER_ENTER
+ && event.getActionMasked() != MotionEvent.ACTION_HOVER_MOVE
|| event.getY() > mClipTopAmount && event.getY() < mActualHeight;
}
@@ -140,13 +166,14 @@ public abstract class ExpandableView extends FrameLayout {
public void setActualHeight(int actualHeight, boolean notifyListeners) {
mActualHeightInitialized = true;
mActualHeight = actualHeight;
+ updateClipping();
if (notifyListeners) {
- notifyHeightChanged();
+ notifyHeightChanged(false /* needsAnimation */);
}
}
- public void setActualHeight(int actualHeight) {
- setActualHeight(actualHeight, true);
+ public void setContentHeight(int contentHeight) {
+ setActualHeight(contentHeight + getBottomDecorHeight(), true);
}
/**
@@ -159,14 +186,39 @@ public abstract class ExpandableView extends FrameLayout {
}
/**
+ * This view may have a bottom decor which will be placed below the content. If it has one, this
+ * view will be layouted higher than just the content by {@link #mBottomDecorHeight}.
+ * @return the height of the decor if it currently has one
+ */
+ public int getBottomDecorHeight() {
+ return hasBottomDecor() ? mBottomDecorHeight : 0;
+ }
+
+ /**
+ * @return whether this view may have a bottom decor at all. This will force the view to layout
+ * itself higher than just it's content
+ */
+ protected boolean canHaveBottomDecor() {
+ return false;
+ }
+
+ /**
+ * @return whether this view has a decor view below it's content. This will make the intrinsic
+ * height from {@link #getIntrinsicHeight()} higher as well
+ */
+ protected boolean hasBottomDecor() {
+ return false;
+ }
+
+ /**
* @return The maximum height of this notification.
*/
- public int getMaxHeight() {
+ public int getMaxContentHeight() {
return getHeight();
}
/**
- * @return The minimum height of this notification.
+ * @return The minimum content height of this notification.
*/
public int getMinHeight() {
return getHeight();
@@ -245,9 +297,9 @@ public abstract class ExpandableView extends FrameLayout {
return false;
}
- public void notifyHeightChanged() {
+ public void notifyHeightChanged(boolean needsAnimation) {
if (mOnHeightChangedListener != null) {
- mOnHeightChangedListener.onHeightChanged(this);
+ mOnHeightChangedListener.onHeightChanged(this, needsAnimation);
}
}
@@ -298,6 +350,56 @@ public abstract class ExpandableView extends FrameLayout {
outRect.top += getTranslationY() + getClipTopAmount();
}
+ @Override
+ public void getBoundsOnScreen(Rect outRect, boolean clipToParent) {
+ super.getBoundsOnScreen(outRect, clipToParent);
+ outRect.bottom = outRect.top + getActualHeight();
+ outRect.top += getClipTopOptimization();
+ }
+
+ public int getContentHeight() {
+ return mActualHeight - getBottomDecorHeight();
+ }
+
+ /**
+ * @return whether the given child can be ignored for layouting and measuring purposes
+ */
+ protected boolean isChildInvisible(View child) {
+ return false;
+ }
+
+ public boolean areChildrenExpanded() {
+ return false;
+ }
+
+ private void updateClipping() {
+ mClipRect.set(0, mClipTopOptimization, getWidth(), getActualHeight());
+ setClipBounds(mClipRect);
+ }
+
+ public int getClipTopOptimization() {
+ return mClipTopOptimization;
+ }
+
+ /**
+ * Set that the view will be clipped by a given amount from the top. Contrary to
+ * {@link #setClipTopAmount} this amount doesn't effect shadows and the background.
+ *
+ * @param clipTopOptimization the amount to clip from the top
+ */
+ public void setClipTopOptimization(int clipTopOptimization) {
+ mClipTopOptimization = clipTopOptimization;
+ updateClipping();
+ }
+
+ public boolean willBeGone() {
+ return mWillBeGone;
+ }
+
+ public void setWillBeGone(boolean willBeGone) {
+ mWillBeGone = willBeGone;
+ }
+
/**
* A listener notifying when {@link #getActualHeight} changes.
*/
@@ -306,8 +408,9 @@ public abstract class ExpandableView extends FrameLayout {
/**
* @param view the view for which the height changed, or {@code null} if just the top
* padding or the padding between the elements changed
+ * @param needsAnimation whether the view height needs to be animated
*/
- void onHeightChanged(ExpandableView view);
+ void onHeightChanged(ExpandableView view, boolean needsAnimation);
/**
* Called when the view is reset and therefore the height will change abruptly
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java b/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java
index 9f0f84e..0fa088b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar;
import android.animation.Animator;
-import android.animation.ValueAnimator;
import android.content.Context;
import android.view.ViewPropertyAnimator;
import android.view.animation.AnimationUtils;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java
index e2464c2..58fb2b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java
@@ -23,17 +23,22 @@ import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
+import android.graphics.CanvasProperty;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
+import android.view.DisplayListCanvas;
+import android.view.RenderNodeAnimator;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.ImageView;
import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.KeyguardAffordanceHelper;
+import com.android.systemui.statusbar.phone.PhoneStatusBar;
/**
* An ImageView which does not have overlapping renderings commands and therefore does not need a
@@ -55,26 +60,31 @@ public class KeyguardAffordanceView extends ImageView {
private final int mNormalColor;
private final ArgbEvaluator mColorInterpolator;
private final FlingAnimationUtils mFlingAnimationUtils;
- private final Drawable mArrowDrawable;
- private final int mHintChevronPadding;
private float mCircleRadius;
private int mCenterX;
private int mCenterY;
private ValueAnimator mCircleAnimator;
private ValueAnimator mAlphaAnimator;
private ValueAnimator mScaleAnimator;
- private ValueAnimator mArrowAnimator;
private float mCircleStartValue;
private boolean mCircleWillBeHidden;
private int[] mTempPoint = new int[2];
private float mImageScale;
private int mCircleColor;
private boolean mIsLeft;
- private float mArrowAlpha = 0.0f;
private View mPreviewView;
private float mCircleStartRadius;
private float mMaxCircleSize;
private Animator mPreviewClipper;
+ private float mRestingAlpha = KeyguardAffordanceHelper.SWIPE_RESTING_ALPHA_AMOUNT;
+ private boolean mSupportHardware;
+ private boolean mFinishing;
+
+ private CanvasProperty<Float> mHwCircleRadius;
+ private CanvasProperty<Float> mHwCenterX;
+ private CanvasProperty<Float> mHwCenterY;
+ private CanvasProperty<Paint> mHwCirclePaint;
+
private AnimatorListenerAdapter mClipEndListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -99,12 +109,6 @@ public class KeyguardAffordanceView extends ImageView {
mAlphaAnimator = null;
}
};
- private AnimatorListenerAdapter mArrowEndListener = new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mArrowAnimator = null;
- }
- };
public KeyguardAffordanceView(Context context) {
this(context, null);
@@ -130,17 +134,12 @@ public class KeyguardAffordanceView extends ImageView {
mInverseColor = 0xff000000;
mMinBackgroundRadius = mContext.getResources().getDimensionPixelSize(
R.dimen.keyguard_affordance_min_background_radius);
- mHintChevronPadding = mContext.getResources().getDimensionPixelSize(
- R.dimen.hint_chevron_circle_padding);
mAppearInterpolator = AnimationUtils.loadInterpolator(mContext,
android.R.interpolator.linear_out_slow_in);
mDisappearInterpolator = AnimationUtils.loadInterpolator(mContext,
android.R.interpolator.fast_out_linear_in);
mColorInterpolator = new ArgbEvaluator();
mFlingAnimationUtils = new FlingAnimationUtils(mContext, 0.3f);
- mArrowDrawable = context.getDrawable(R.drawable.ic_chevron_left);
- mArrowDrawable.setBounds(0, 0, mArrowDrawable.getIntrinsicWidth(),
- mArrowDrawable.getIntrinsicHeight());
}
@Override
@@ -153,8 +152,8 @@ public class KeyguardAffordanceView extends ImageView {
@Override
protected void onDraw(Canvas canvas) {
+ mSupportHardware = canvas.isHardwareAccelerated();
drawBackgroundCircle(canvas);
- drawArrow(canvas);
canvas.save();
canvas.scale(mImageScale, mImageScale, getWidth() / 2, getHeight() / 2);
super.onDraw(canvas);
@@ -168,22 +167,6 @@ public class KeyguardAffordanceView extends ImageView {
}
}
- private void drawArrow(Canvas canvas) {
- if (mArrowAlpha > 0) {
- canvas.save();
- canvas.translate(mCenterX, mCenterY);
- if (mIsLeft) {
- canvas.scale(-1.0f, 1.0f);
- }
- canvas.translate(- mCircleRadius - mHintChevronPadding
- - mArrowDrawable.getIntrinsicWidth() / 2,
- - mArrowDrawable.getIntrinsicHeight() / 2);
- mArrowDrawable.setAlpha((int) (mArrowAlpha * 255));
- mArrowDrawable.draw(canvas);
- canvas.restore();
- }
- }
-
private void updateIconColor() {
Drawable drawable = getDrawable().mutate();
float alpha = mCircleRadius / mMinBackgroundRadius;
@@ -194,15 +177,21 @@ public class KeyguardAffordanceView extends ImageView {
private void drawBackgroundCircle(Canvas canvas) {
if (mCircleRadius > 0) {
- updateCircleColor();
- canvas.drawCircle(mCenterX, mCenterY, mCircleRadius, mCirclePaint);
+ if (mFinishing && mSupportHardware) {
+ DisplayListCanvas displayListCanvas = (DisplayListCanvas) canvas;
+ displayListCanvas.drawCircle(mHwCenterX, mHwCenterY, mHwCircleRadius,
+ mHwCirclePaint);
+ } else {
+ updateCircleColor();
+ canvas.drawCircle(mCenterX, mCenterY, mCircleRadius, mCirclePaint);
+ }
}
}
private void updateCircleColor() {
float fraction = 0.5f + 0.5f * Math.max(0.0f, Math.min(1.0f,
(mCircleRadius - mMinBackgroundRadius) / (0.5f * mMinBackgroundRadius)));
- if (mPreviewView != null) {
+ if (mPreviewView != null && mPreviewView.getVisibility() == VISIBLE) {
float finishingFraction = 1 - Math.max(0, mCircleRadius - mCircleStartRadius)
/ (mMaxCircleSize - mCircleStartRadius);
fraction *= finishingFraction;
@@ -216,15 +205,23 @@ public class KeyguardAffordanceView extends ImageView {
public void finishAnimation(float velocity, final Runnable mAnimationEndRunnable) {
cancelAnimator(mCircleAnimator);
cancelAnimator(mPreviewClipper);
+ mFinishing = true;
mCircleStartRadius = mCircleRadius;
float maxCircleSize = getMaxCircleSize();
- ValueAnimator animatorToRadius = getAnimatorToRadius(maxCircleSize);
+ Animator animatorToRadius;
+ if (mSupportHardware) {
+ initHwProperties();
+ animatorToRadius = getRtAnimatorToRadius(maxCircleSize);
+ } else {
+ animatorToRadius = getAnimatorToRadius(maxCircleSize);
+ }
mFlingAnimationUtils.applyDismissing(animatorToRadius, mCircleRadius, maxCircleSize,
velocity, maxCircleSize);
animatorToRadius.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mAnimationEndRunnable.run();
+ mFinishing = false;
}
});
animatorToRadius.start();
@@ -238,9 +235,34 @@ public class KeyguardAffordanceView extends ImageView {
velocity, maxCircleSize);
mPreviewClipper.addListener(mClipEndListener);
mPreviewClipper.start();
+ if (mSupportHardware) {
+ startRtCircleFadeOut(animatorToRadius.getDuration());
+ }
}
}
+ private void startRtCircleFadeOut(long duration) {
+ RenderNodeAnimator animator = new RenderNodeAnimator(mHwCirclePaint,
+ RenderNodeAnimator.PAINT_ALPHA, 0);
+ animator.setDuration(duration);
+ animator.setInterpolator(PhoneStatusBar.ALPHA_OUT);
+ animator.setTarget(this);
+ animator.start();
+ }
+
+ private Animator getRtAnimatorToRadius(float circleRadius) {
+ RenderNodeAnimator animator = new RenderNodeAnimator(mHwCircleRadius, circleRadius);
+ animator.setTarget(this);
+ return animator;
+ }
+
+ private void initHwProperties() {
+ mHwCenterX = CanvasProperty.createFloat(mCenterX);
+ mHwCenterY = CanvasProperty.createFloat(mCenterY);
+ mHwCirclePaint = CanvasProperty.createPaint(mCirclePaint);
+ mHwCircleRadius = CanvasProperty.createFloat(mCircleRadius);
+ }
+
private float getMaxCircleSize() {
getLocationInWindow(mTempPoint);
float rootWidth = getRootView().getWidth();
@@ -394,6 +416,17 @@ public class KeyguardAffordanceView extends ImageView {
}
}
+ public void setRestingAlpha(float alpha) {
+ mRestingAlpha = alpha;
+
+ // TODO: Handle the case an animation is playing.
+ setImageAlpha(alpha, false);
+ }
+
+ public float getRestingAlpha() {
+ return mRestingAlpha;
+ }
+
public void setImageAlpha(float alpha, boolean animate) {
setImageAlpha(alpha, animate, -1, null, null);
}
@@ -468,36 +501,6 @@ public class KeyguardAffordanceView extends ImageView {
return mCircleRadius;
}
- public void showArrow(boolean show) {
- cancelAnimator(mArrowAnimator);
- float targetAlpha = show ? 1.0f : 0.0f;
- if (mArrowAlpha == targetAlpha) {
- return;
- }
- ValueAnimator animator = ValueAnimator.ofFloat(mArrowAlpha, targetAlpha);
- mArrowAnimator = animator;
- animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- mArrowAlpha = (float) animation.getAnimatedValue();
- invalidate();
- }
- });
- animator.addListener(mArrowEndListener);
- Interpolator interpolator = show
- ? mAppearInterpolator
- : mDisappearInterpolator;
- animator.setInterpolator(interpolator);
- float durationFactor = Math.abs(mArrowAlpha - targetAlpha);
- long duration = (long) (NORMAL_ANIMATION_DURATION * durationFactor);
- animator.setDuration(duration);
- animator.start();
- }
-
- public void setIsLeft(boolean left) {
- mIsLeft = left;
- }
-
@Override
public boolean performClick() {
if (isClickable()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 58067c3..07a055c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -26,6 +26,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.graphics.Color;
import android.os.BatteryManager;
import android.os.BatteryStats;
import android.os.Handler;
@@ -53,6 +54,7 @@ public class KeyguardIndicationController {
private String mRestingIndication;
private String mTransientIndication;
+ private int mTransientTextColor;
private boolean mVisible;
private boolean mPowerPluggedIn;
@@ -105,7 +107,15 @@ public class KeyguardIndicationController {
* Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}.
*/
public void showTransientIndication(String transientIndication) {
+ showTransientIndication(transientIndication, Color.WHITE);
+ }
+
+ /**
+ * Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}.
+ */
+ public void showTransientIndication(String transientIndication, int textColor) {
mTransientIndication = transientIndication;
+ mTransientTextColor = textColor;
mHandler.removeMessages(MSG_HIDE_TRANSIENT);
updateIndication();
}
@@ -124,7 +134,15 @@ public class KeyguardIndicationController {
private void updateIndication() {
if (mVisible) {
mTextView.switchIndication(computeIndication());
+ mTextView.setTextColor(computeColor());
+ }
+ }
+
+ private int computeColor() {
+ if (!TextUtils.isEmpty(mTransientIndication)) {
+ return mTransientTextColor;
}
+ return Color.WHITE;
}
private String computeIndication() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java
index 0fc46e9..01aa8d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java
@@ -44,7 +44,7 @@ public class NotificationBackgroundView extends View {
}
private void draw(Canvas canvas, Drawable drawable) {
- if (drawable != null) {
+ if (drawable != null && mActualHeight > mClipTopAmount) {
drawable.setBounds(0, mClipTopAmount, getWidth(), mActualHeight);
drawable.draw(canvas);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/TickerView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBigMediaNarrowViewWrapper.java
index bf13751..91e5404 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/TickerView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBigMediaNarrowViewWrapper.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * 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.
@@ -11,32 +11,26 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
- * limitations under the License.
+ * limitations under the License
*/
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.statusbar;
import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.TextSwitcher;
+import android.view.View;
+/**
+ * Wraps a big media narrow notification template layout.
+ */
+public class NotificationBigMediaNarrowViewWrapper extends NotificationMediaViewWrapper {
-public class TickerView extends TextSwitcher
-{
- Ticker mTicker;
-
- public TickerView(Context context, AttributeSet attrs) {
- super(context, attrs);
+ protected NotificationBigMediaNarrowViewWrapper(Context ctx,
+ View view) {
+ super(ctx, view);
}
@Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- super.onSizeChanged(w, h, oldw, oldh);
- if (mTicker != null) mTicker.reflowText();
- }
-
- public void setTicker(Ticker t) {
- mTicker = t;
+ public boolean needsRoundRectClipping() {
+ return true;
}
}
-
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBigPictureViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBigPictureViewWrapper.java
new file mode 100644
index 0000000..ffe0cd1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBigPictureViewWrapper.java
@@ -0,0 +1,35 @@
+/*
+ * 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.statusbar;
+
+import android.content.Context;
+import android.view.View;
+
+/**
+ * Wraps a notification view inflated from a big picture style template.
+ */
+public class NotificationBigPictureViewWrapper extends NotificationTemplateViewWrapper {
+
+ protected NotificationBigPictureViewWrapper(Context ctx, View view) {
+ super(ctx, view);
+ }
+
+ @Override
+ public boolean needsRoundRectClipping() {
+ return true;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index 914b3d8..86d7f4f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -17,51 +17,59 @@
package com.android.systemui.statusbar;
import android.content.Context;
-import android.graphics.ColorFilter;
-import android.graphics.ColorMatrix;
-import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
import android.view.ViewTreeObserver;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.widget.FrameLayout;
-import android.widget.ImageView;
+
import com.android.systemui.R;
/**
- * A frame layout containing the actual payload of the notification, including the contracted and
- * expanded layout. This class is responsible for clipping the content and and switching between the
- * expanded and contracted view depending on its clipped size.
+ * A frame layout containing the actual payload of the notification, including the contracted,
+ * expanded and heads up layout. This class is responsible for clipping the content and and
+ * switching between the expanded, contracted and the heads up view depending on its clipped size.
*/
public class NotificationContentView extends FrameLayout {
private static final long ANIMATION_DURATION_LENGTH = 170;
+ private static final int VISIBLE_TYPE_CONTRACTED = 0;
+ private static final int VISIBLE_TYPE_EXPANDED = 1;
+ private static final int VISIBLE_TYPE_HEADSUP = 2;
private final Rect mClipBounds = new Rect();
+ private final int mSmallHeight;
+ private final int mHeadsUpHeight;
+ private final int mRoundRectRadius;
+ private final Interpolator mLinearInterpolator = new LinearInterpolator();
+ private final boolean mRoundRectClippingEnabled;
private View mContractedChild;
private View mExpandedChild;
+ private View mHeadsUpChild;
private NotificationViewWrapper mContractedWrapper;
-
- private int mSmallHeight;
+ private NotificationViewWrapper mExpandedWrapper;
+ private NotificationViewWrapper mHeadsUpWrapper;
private int mClipTopAmount;
- private int mActualHeight;
-
- private final Interpolator mLinearInterpolator = new LinearInterpolator();
-
- private boolean mContractedVisible = true;
+ private int mContentHeight;
+ private int mUnrestrictedContentHeight;
+ private int mVisibleType = VISIBLE_TYPE_CONTRACTED;
private boolean mDark;
-
private final Paint mFadePaint = new Paint();
private boolean mAnimate;
- private ViewTreeObserver.OnPreDrawListener mEnableAnimationPredrawListener
+ private boolean mIsHeadsUp;
+ private boolean mShowingLegacyBackground;
+
+ private final ViewTreeObserver.OnPreDrawListener mEnableAnimationPredrawListener
= new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
@@ -71,16 +79,77 @@ public class NotificationContentView extends FrameLayout {
}
};
+ private final ViewOutlineProvider mOutlineProvider = new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ outline.setRoundRect(0, 0, view.getWidth(), mUnrestrictedContentHeight,
+ mRoundRectRadius);
+ }
+ };
+
public NotificationContentView(Context context, AttributeSet attrs) {
super(context, attrs);
mFadePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
+ mSmallHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
+ mHeadsUpHeight = getResources().getDimensionPixelSize(R.dimen.notification_mid_height);
+ mRoundRectRadius = getResources().getDimensionPixelSize(
+ R.dimen.notification_material_rounded_rect_radius);
+ mRoundRectClippingEnabled = getResources().getBoolean(
+ R.bool.config_notifications_round_rect_clipping);
reset(true);
+ setOutlineProvider(mOutlineProvider);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+ boolean hasFixedHeight = heightMode == MeasureSpec.EXACTLY;
+ boolean isHeightLimited = heightMode == MeasureSpec.AT_MOST;
+ int maxSize = Integer.MAX_VALUE;
+ if (hasFixedHeight || isHeightLimited) {
+ maxSize = MeasureSpec.getSize(heightMeasureSpec);
+ }
+ int maxChildHeight = 0;
+ if (mContractedChild != null) {
+ int size = Math.min(maxSize, mSmallHeight);
+ mContractedChild.measure(widthMeasureSpec,
+ MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY));
+ maxChildHeight = Math.max(maxChildHeight, mContractedChild.getMeasuredHeight());
+ }
+ if (mExpandedChild != null) {
+ int size = maxSize;
+ ViewGroup.LayoutParams layoutParams = mExpandedChild.getLayoutParams();
+ if (layoutParams.height >= 0) {
+ // An actual height is set
+ size = Math.min(maxSize, layoutParams.height);
+ }
+ int spec = size == Integer.MAX_VALUE
+ ? MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
+ : MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST);
+ mExpandedChild.measure(widthMeasureSpec, spec);
+ maxChildHeight = Math.max(maxChildHeight, mExpandedChild.getMeasuredHeight());
+ }
+ if (mHeadsUpChild != null) {
+ int size = Math.min(maxSize, mHeadsUpHeight);
+ ViewGroup.LayoutParams layoutParams = mHeadsUpChild.getLayoutParams();
+ if (layoutParams.height >= 0) {
+ // An actual height is set
+ size = Math.min(maxSize, layoutParams.height);
+ }
+ mHeadsUpChild.measure(widthMeasureSpec,
+ MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST));
+ maxChildHeight = Math.max(maxChildHeight, mHeadsUpChild.getMeasuredHeight());
+ }
+ int ownHeight = Math.min(maxChildHeight, maxSize);
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ setMeasuredDimension(width, ownHeight);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
updateClipping();
+ invalidateOutline();
}
@Override
@@ -96,13 +165,16 @@ public class NotificationContentView extends FrameLayout {
if (mExpandedChild != null) {
mExpandedChild.animate().cancel();
}
+ if (mHeadsUpChild != null) {
+ mHeadsUpChild.animate().cancel();
+ }
removeAllViews();
mContractedChild = null;
mExpandedChild = null;
- mSmallHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
- mContractedVisible = true;
+ mHeadsUpChild = null;
+ mVisibleType = VISIBLE_TYPE_CONTRACTED;
if (resetActualHeight) {
- mActualHeight = mSmallHeight;
+ mContentHeight = mSmallHeight;
}
}
@@ -114,17 +186,21 @@ public class NotificationContentView extends FrameLayout {
return mExpandedChild;
}
+ public View getHeadsUpChild() {
+ return mHeadsUpChild;
+ }
+
public void setContractedChild(View child) {
if (mContractedChild != null) {
mContractedChild.animate().cancel();
removeView(mContractedChild);
}
- sanitizeContractedLayoutParams(child);
addView(child);
mContractedChild = child;
mContractedWrapper = NotificationViewWrapper.wrap(getContext(), child);
selectLayout(false /* animate */, true /* force */);
mContractedWrapper.setDark(mDark, false /* animate */, 0 /* delay */);
+ updateRoundRectClipping();
}
public void setExpandedChild(View child) {
@@ -134,7 +210,21 @@ public class NotificationContentView extends FrameLayout {
}
addView(child);
mExpandedChild = child;
+ mExpandedWrapper = NotificationViewWrapper.wrap(getContext(), child);
+ selectLayout(false /* animate */, true /* force */);
+ updateRoundRectClipping();
+ }
+
+ public void setHeadsUpChild(View child) {
+ if (mHeadsUpChild != null) {
+ mHeadsUpChild.animate().cancel();
+ removeView(mHeadsUpChild);
+ }
+ addView(child);
+ mHeadsUpChild = child;
+ mHeadsUpWrapper = NotificationViewWrapper.wrap(getContext(), child);
selectLayout(false /* animate */, true /* force */);
+ updateRoundRectClipping();
}
@Override
@@ -159,16 +249,25 @@ public class NotificationContentView extends FrameLayout {
}
}
- public void setActualHeight(int actualHeight) {
- mActualHeight = actualHeight;
+ public void setContentHeight(int contentHeight) {
+ mContentHeight = Math.max(Math.min(contentHeight, getHeight()), getMinHeight());;
+ mUnrestrictedContentHeight = Math.max(contentHeight, getMinHeight());
selectLayout(mAnimate /* animate */, false /* force */);
updateClipping();
+ invalidateOutline();
}
- public int getMaxHeight() {
+ public int getContentHeight() {
+ return mContentHeight;
+ }
- // The maximum height is just the laid out height.
- return getHeight();
+ public int getMaxHeight() {
+ if (mIsHeadsUp && mHeadsUpChild != null) {
+ return mHeadsUpChild.getHeight();
+ } else if (mExpandedChild != null) {
+ return mExpandedChild.getHeight();
+ }
+ return mSmallHeight;
}
public int getMinHeight() {
@@ -180,67 +279,129 @@ public class NotificationContentView extends FrameLayout {
updateClipping();
}
- private void updateClipping() {
- mClipBounds.set(0, mClipTopAmount, getWidth(), mActualHeight);
- setClipBounds(mClipBounds);
+ private void updateRoundRectClipping() {
+ boolean enabled = needsRoundRectClipping();
+ setClipToOutline(enabled);
+ }
+
+ private boolean needsRoundRectClipping() {
+ if (!mRoundRectClippingEnabled) {
+ return false;
+ }
+ boolean needsForContracted = mContractedChild != null
+ && mContractedChild.getVisibility() == View.VISIBLE
+ && mContractedWrapper.needsRoundRectClipping();
+ boolean needsForExpanded = mExpandedChild != null
+ && mExpandedChild.getVisibility() == View.VISIBLE
+ && mExpandedWrapper.needsRoundRectClipping();
+ boolean needsForHeadsUp = mExpandedChild != null
+ && mExpandedChild.getVisibility() == View.VISIBLE
+ && mExpandedWrapper.needsRoundRectClipping();
+ return needsForContracted || needsForExpanded || needsForHeadsUp;
}
- private void sanitizeContractedLayoutParams(View contractedChild) {
- LayoutParams lp = (LayoutParams) contractedChild.getLayoutParams();
- lp.height = mSmallHeight;
- contractedChild.setLayoutParams(lp);
+ private void updateClipping() {
+ mClipBounds.set(0, mClipTopAmount, getWidth(), mContentHeight);
+ setClipBounds(mClipBounds);
}
private void selectLayout(boolean animate, boolean force) {
if (mContractedChild == null) {
return;
}
- boolean showContractedChild = showContractedChild();
- if (showContractedChild != mContractedVisible || force) {
- if (animate && mExpandedChild != null) {
- runSwitchAnimation(showContractedChild);
- } else if (mExpandedChild != null) {
- mContractedChild.setVisibility(showContractedChild ? View.VISIBLE : View.INVISIBLE);
- mContractedChild.setAlpha(showContractedChild ? 1f : 0f);
- mExpandedChild.setVisibility(showContractedChild ? View.INVISIBLE : View.VISIBLE);
- mExpandedChild.setAlpha(showContractedChild ? 0f : 1f);
+ int visibleType = calculateVisibleType();
+ if (visibleType != mVisibleType || force) {
+ if (animate && ((visibleType == VISIBLE_TYPE_EXPANDED && mExpandedChild != null)
+ || (visibleType == VISIBLE_TYPE_HEADSUP && mHeadsUpChild != null)
+ || visibleType == VISIBLE_TYPE_CONTRACTED)) {
+ runSwitchAnimation(visibleType);
+ } else {
+ updateViewVisibilities(visibleType);
}
+ mVisibleType = visibleType;
+ }
+ }
+
+ private void updateViewVisibilities(int visibleType) {
+ boolean contractedVisible = visibleType == VISIBLE_TYPE_CONTRACTED;
+ mContractedChild.setVisibility(contractedVisible ? View.VISIBLE : View.INVISIBLE);
+ mContractedChild.setAlpha(contractedVisible ? 1f : 0f);
+ mContractedChild.setLayerType(LAYER_TYPE_NONE, null);
+ if (mExpandedChild != null) {
+ boolean expandedVisible = visibleType == VISIBLE_TYPE_EXPANDED;
+ mExpandedChild.setVisibility(expandedVisible ? View.VISIBLE : View.INVISIBLE);
+ mExpandedChild.setAlpha(expandedVisible ? 1f : 0f);
+ mExpandedChild.setLayerType(LAYER_TYPE_NONE, null);
}
- mContractedVisible = showContractedChild;
+ if (mHeadsUpChild != null) {
+ boolean headsUpVisible = visibleType == VISIBLE_TYPE_HEADSUP;
+ mHeadsUpChild.setVisibility(headsUpVisible ? View.VISIBLE : View.INVISIBLE);
+ mHeadsUpChild.setAlpha(headsUpVisible ? 1f : 0f);
+ mHeadsUpChild.setLayerType(LAYER_TYPE_NONE, null);
+ }
+ setLayerType(LAYER_TYPE_NONE, null);
+ updateRoundRectClipping();
}
- private void runSwitchAnimation(final boolean showContractedChild) {
- mContractedChild.setVisibility(View.VISIBLE);
- mExpandedChild.setVisibility(View.VISIBLE);
- mContractedChild.setLayerType(LAYER_TYPE_HARDWARE, mFadePaint);
- mExpandedChild.setLayerType(LAYER_TYPE_HARDWARE, mFadePaint);
+ private void runSwitchAnimation(int visibleType) {
+ View shownView = getViewForVisibleType(visibleType);
+ View hiddenView = getViewForVisibleType(mVisibleType);
+ shownView.setVisibility(View.VISIBLE);
+ hiddenView.setVisibility(View.VISIBLE);
+ shownView.setLayerType(LAYER_TYPE_HARDWARE, mFadePaint);
+ hiddenView.setLayerType(LAYER_TYPE_HARDWARE, mFadePaint);
setLayerType(LAYER_TYPE_HARDWARE, null);
- mContractedChild.animate()
- .alpha(showContractedChild ? 1f : 0f)
+ hiddenView.animate()
+ .alpha(0f)
.setDuration(ANIMATION_DURATION_LENGTH)
- .setInterpolator(mLinearInterpolator);
- mExpandedChild.animate()
- .alpha(showContractedChild ? 0f : 1f)
+ .setInterpolator(mLinearInterpolator)
+ .withEndAction(null); // In case we have multiple changes in one frame.
+ shownView.animate()
+ .alpha(1f)
.setDuration(ANIMATION_DURATION_LENGTH)
.setInterpolator(mLinearInterpolator)
.withEndAction(new Runnable() {
@Override
public void run() {
- mContractedChild.setLayerType(LAYER_TYPE_NONE, null);
- mExpandedChild.setLayerType(LAYER_TYPE_NONE, null);
- setLayerType(LAYER_TYPE_NONE, null);
- mContractedChild.setVisibility(showContractedChild
- ? View.VISIBLE
- : View.INVISIBLE);
- mExpandedChild.setVisibility(showContractedChild
- ? View.INVISIBLE
- : View.VISIBLE);
+ updateViewVisibilities(mVisibleType);
}
});
+ updateRoundRectClipping();
+ }
+
+ /**
+ * @param visibleType one of the static enum types in this view
+ * @return the corresponding view according to the given visible type
+ */
+ private View getViewForVisibleType(int visibleType) {
+ switch (visibleType) {
+ case VISIBLE_TYPE_EXPANDED:
+ return mExpandedChild;
+ case VISIBLE_TYPE_HEADSUP:
+ return mHeadsUpChild;
+ default:
+ return mContractedChild;
+ }
}
- private boolean showContractedChild() {
- return mActualHeight <= mSmallHeight || mExpandedChild == null;
+ /**
+ * @return one of the static enum types in this view, calculated form the current state
+ */
+ private int calculateVisibleType() {
+ boolean noExpandedChild = mExpandedChild == null;
+ if (mIsHeadsUp && mHeadsUpChild != null) {
+ if (mContentHeight <= mHeadsUpChild.getHeight() || noExpandedChild) {
+ return VISIBLE_TYPE_HEADSUP;
+ } else {
+ return VISIBLE_TYPE_EXPANDED;
+ }
+ } else {
+ if (mContentHeight <= mSmallHeight || noExpandedChild) {
+ return VISIBLE_TYPE_CONTRACTED;
+ } else {
+ return VISIBLE_TYPE_EXPANDED;
+ }
+ }
}
public void notifyContentUpdated() {
@@ -249,6 +410,10 @@ public class NotificationContentView extends FrameLayout {
mContractedWrapper.notifyContentUpdated();
mContractedWrapper.setDark(mDark, false /* animate */, 0 /* delay */);
}
+ if (mExpandedChild != null) {
+ mExpandedWrapper.notifyContentUpdated();
+ }
+ updateRoundRectClipping();
}
public boolean isContentExpandable() {
@@ -258,7 +423,12 @@ public class NotificationContentView extends FrameLayout {
public void setDark(boolean dark, boolean fade, long delay) {
if (mDark == dark || mContractedChild == null) return;
mDark = dark;
- mContractedWrapper.setDark(dark, fade, delay);
+ mContractedWrapper.setDark(dark && !mShowingLegacyBackground, fade, delay);
+ }
+
+ public void setHeadsUp(boolean headsUp) {
+ mIsHeadsUp = headsUp;
+ selectLayout(false /* animate */, true /* force */);
}
@Override
@@ -268,4 +438,8 @@ public class NotificationContentView extends FrameLayout {
// layout, and saves us some layers.
return false;
}
+
+ public void setShowingLegacyBackground(boolean showing) {
+ mShowingLegacyBackground = showing;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationCustomViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationCustomViewWrapper.java
index 0702d7e..6fd341b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationCustomViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationCustomViewWrapper.java
@@ -41,4 +41,9 @@ public class NotificationCustomViewWrapper extends NotificationViewWrapper {
mInvertHelper.update(dark);
}
}
+
+ @Override
+ public boolean needsRoundRectClipping() {
+ return true;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index 34c458a..aedae52 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -17,14 +17,17 @@
package com.android.systemui.statusbar;
import android.app.Notification;
+import android.os.SystemClock;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
-import android.util.ArraySet;
import android.view.View;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
@@ -36,33 +39,26 @@ import java.util.Comparator;
public class NotificationData {
private final Environment mEnvironment;
+ private HeadsUpManager mHeadsUpManager;
public static final class Entry {
+ private static final long LAUNCH_COOLDOWN = 2000;
+ private static final long NOT_LAUNCHED_YET = -LAUNCH_COOLDOWN;
public String key;
public StatusBarNotification notification;
public StatusBarIconView icon;
public ExpandableNotificationRow row; // the outer expanded view
- public View expanded; // the inflated RemoteViews
- public View expandedPublic; // for insecure lockscreens
- public View expandedBig;
private boolean interruption;
public boolean autoRedacted; // whether the redacted notification was generated by us
public boolean legacy; // whether the notification has a legacy, dark background
public int targetSdk;
+ private long lastFullScreenIntentLaunchTime = NOT_LAUNCHED_YET;
public Entry(StatusBarNotification n, StatusBarIconView ic) {
this.key = n.getKey();
this.notification = n;
this.icon = ic;
}
- public void setBigContentView(View bigContentView) {
- this.expandedBig = bigContentView;
- row.setExpandable(bigContentView != null);
- }
- public View getBigContentView() {
- return expandedBig;
- }
- public View getPublicContentView() { return expandedPublic; }
public void setInterruption() {
interruption = true;
@@ -78,60 +74,94 @@ public class NotificationData {
public void reset() {
// NOTE: Icon needs to be preserved for now.
// We should fix this at some point.
- expanded = null;
- expandedPublic = null;
- expandedBig = null;
autoRedacted = false;
legacy = false;
+ lastFullScreenIntentLaunchTime = NOT_LAUNCHED_YET;
if (row != null) {
row.reset();
}
}
+
+ public View getContentView() {
+ return row.getPrivateLayout().getContractedChild();
+ }
+
+ public View getExpandedContentView() {
+ return row.getPrivateLayout().getExpandedChild();
+ }
+
+ public View getHeadsUpContentView() {
+ return row.getPrivateLayout().getHeadsUpChild();
+ }
+
+ public View getPublicContentView() {
+ return row.getPublicLayout().getContractedChild();
+ }
+
+ public void notifyFullScreenIntentLaunched() {
+ lastFullScreenIntentLaunchTime = SystemClock.elapsedRealtime();
+ }
+
+ public boolean hasJustLaunchedFullScreenIntent() {
+ return SystemClock.elapsedRealtime() < lastFullScreenIntentLaunchTime + LAUNCH_COOLDOWN;
+ }
}
private final ArrayMap<String, Entry> mEntries = new ArrayMap<>();
private final ArrayList<Entry> mSortedAndFiltered = new ArrayList<>();
- private ArraySet<String> mGroupsWithSummaries = new ArraySet<>();
+
+ private NotificationGroupManager mGroupManager;
private RankingMap mRankingMap;
private final Ranking mTmpRanking = new Ranking();
+
+ public void setHeadsUpManager(HeadsUpManager headsUpManager) {
+ mHeadsUpManager = headsUpManager;
+ }
+
private final Comparator<Entry> mRankingComparator = new Comparator<Entry>() {
private final Ranking mRankingA = new Ranking();
private final Ranking mRankingB = new Ranking();
@Override
public int compare(Entry a, Entry b) {
- // Upsort current media notification.
- String mediaNotification = mEnvironment.getCurrentMediaNotificationKey();
- boolean aMedia = a.key.equals(mediaNotification);
- boolean bMedia = b.key.equals(mediaNotification);
- if (aMedia != bMedia) {
- return aMedia ? -1 : 1;
- }
-
final StatusBarNotification na = a.notification;
final StatusBarNotification nb = b.notification;
+ final int aPriority = na.getNotification().priority;
+ final int bPriority = nb.getNotification().priority;
+
+ String mediaNotification = mEnvironment.getCurrentMediaNotificationKey();
- // Upsort PRIORITY_MAX system notifications
- boolean aSystemMax = na.getNotification().priority >= Notification.PRIORITY_MAX &&
+ // PRIORITY_MIN media streams are allowed to drift to the bottom
+ final boolean aMedia = a.key.equals(mediaNotification)
+ && aPriority > Notification.PRIORITY_MIN;
+ final boolean bMedia = b.key.equals(mediaNotification)
+ && bPriority > Notification.PRIORITY_MIN;
+
+ boolean aSystemMax = aPriority >= Notification.PRIORITY_MAX &&
isSystemNotification(na);
- boolean bSystemMax = nb.getNotification().priority >= Notification.PRIORITY_MAX &&
+ boolean bSystemMax = bPriority >= Notification.PRIORITY_MAX &&
isSystemNotification(nb);
- if (aSystemMax != bSystemMax) {
- return aSystemMax ? -1 : 1;
- }
+ int d = nb.getScore() - na.getScore();
- // RankingMap as received from NoMan.
- if (mRankingMap != null) {
+ boolean isHeadsUp = a.row.isHeadsUp();
+ if (isHeadsUp != b.row.isHeadsUp()) {
+ return isHeadsUp ? -1 : 1;
+ } else if (isHeadsUp) {
+ // Provide consistent ranking with headsUpManager
+ return mHeadsUpManager.compare(a, b);
+ } else if (aMedia != bMedia) {
+ // Upsort current media notification.
+ return aMedia ? -1 : 1;
+ } else if (aSystemMax != bSystemMax) {
+ // Upsort PRIORITY_MAX system notifications
+ return aSystemMax ? -1 : 1;
+ } else if (mRankingMap != null) {
+ // RankingMap as received from NoMan
mRankingMap.getRanking(a.key, mRankingA);
mRankingMap.getRanking(b.key, mRankingB);
return mRankingA.getRank() - mRankingB.getRank();
- }
-
- int d = nb.getScore() - na.getScore();
- if (a.interruption != b.interruption) {
- return a.interruption ? -1 : 1;
- } else if (d != 0) {
+ } if (d != 0) {
return d;
} else {
return (int) (nb.getNotification().when - na.getNotification().when);
@@ -141,6 +171,7 @@ public class NotificationData {
public NotificationData(Environment environment) {
mEnvironment = environment;
+ mGroupManager = environment.getGroupManager();
}
/**
@@ -163,12 +194,14 @@ public class NotificationData {
public void add(Entry entry, RankingMap ranking) {
mEntries.put(entry.notification.getKey(), entry);
updateRankingAndSort(ranking);
+ mGroupManager.onEntryAdded(entry);
}
public Entry remove(String key, RankingMap ranking) {
Entry removed = mEntries.remove(key);
if (removed == null) return null;
updateRankingAndSort(ranking);
+ mGroupManager.onEntryRemoved(removed);
return removed;
}
@@ -203,7 +236,6 @@ public class NotificationData {
// anything changed, and this class should call back the UI so it updates itself.
public void filterAndSort() {
mSortedAndFiltered.clear();
- mGroupsWithSummaries.clear();
final int N = mEntries.size();
for (int i = 0; i < N; i++) {
@@ -214,32 +246,12 @@ public class NotificationData {
continue;
}
- if (sbn.getNotification().isGroupSummary()) {
- mGroupsWithSummaries.add(sbn.getGroupKey());
- }
mSortedAndFiltered.add(entry);
}
- // Second pass: Filter out group children with summary.
- if (!mGroupsWithSummaries.isEmpty()) {
- final int M = mSortedAndFiltered.size();
- for (int i = M - 1; i >= 0; i--) {
- Entry ent = mSortedAndFiltered.get(i);
- StatusBarNotification sbn = ent.notification;
- if (sbn.getNotification().isGroupChild() &&
- mGroupsWithSummaries.contains(sbn.getGroupKey())) {
- mSortedAndFiltered.remove(i);
- }
- }
- }
-
Collections.sort(mSortedAndFiltered, mRankingComparator);
}
- public boolean isGroupWithSummary(String groupKey) {
- return mGroupsWithSummaries.contains(groupKey);
- }
-
boolean shouldFilterOut(StatusBarNotification sbn) {
if (!(mEnvironment.isDeviceProvisioned() ||
showNotificationEvenIfUnprovisioned(sbn))) {
@@ -254,6 +266,11 @@ public class NotificationData {
mEnvironment.shouldHideSensitiveContents(sbn.getUserId())) {
return true;
}
+
+ if (!BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS
+ && mGroupManager.isChildInGroupWithSummary(sbn)) {
+ return true;
+ }
return false;
}
@@ -262,7 +279,7 @@ public class NotificationData {
*/
public boolean hasActiveClearableNotifications() {
for (Entry e : mSortedAndFiltered) {
- if (e.expanded != null) { // the view successfully inflated
+ if (e.getContentView() != null) { // the view successfully inflated
if (e.notification.isClearable()) {
return true;
}
@@ -328,5 +345,6 @@ public class NotificationData {
public boolean isDeviceProvisioned();
public boolean isNotificationForCurrentProfiles(StatusBarNotification sbn);
public String getCurrentMediaNotificationKey();
+ public NotificationGroupManager getGroupManager();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java
index bfa3aa5..9653b67 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar;
import android.content.Context;
import android.util.AttributeSet;
+import android.view.View;
import android.widget.TextView;
import com.android.systemui.R;
@@ -32,6 +33,7 @@ public class NotificationOverflowContainer extends ActivatableNotificationView {
private NotificationOverflowIconsView mIconsView;
private ViewInvertHelper mViewInvertHelper;
private boolean mDark;
+ private View mContent;
public NotificationOverflowContainer(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -43,7 +45,8 @@ public class NotificationOverflowContainer extends ActivatableNotificationView {
mIconsView = (NotificationOverflowIconsView) findViewById(R.id.overflow_icons_view);
mIconsView.setMoreText((TextView) findViewById(R.id.more_text));
mIconsView.setOverflowIndicator(findViewById(R.id.more_icon_overflow));
- mViewInvertHelper = new ViewInvertHelper(findViewById(R.id.content),
+ mContent = findViewById(R.id.content);
+ mViewInvertHelper = new ViewInvertHelper(mContent,
NotificationPanelView.DOZE_ANIMATION_DURATION);
}
@@ -59,7 +62,21 @@ public class NotificationOverflowContainer extends ActivatableNotificationView {
}
}
+ @Override
+ protected View getContentView() {
+ return mContent;
+ }
+
public NotificationOverflowIconsView getIconsView() {
return mIconsView;
}
+
+ protected int getContentHeightFromActualHeight(int actualHeight) {
+ int realActualHeight = actualHeight;
+ if (hasBottomDecor()) {
+ realActualHeight -= getBottomDecorHeight();
+ }
+ realActualHeight = Math.max(getMinHeight(), realActualHeight);
+ return realActualHeight;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowIconsView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowIconsView.java
index c4c9dac..88bb714 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowIconsView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowIconsView.java
@@ -18,7 +18,6 @@ package com.android.systemui.statusbar;
import android.app.Notification;
import android.content.Context;
-import android.content.res.Configuration;
import android.graphics.PorterDuff;
import android.util.AttributeSet;
import android.widget.ImageView;
@@ -46,7 +45,7 @@ public class NotificationOverflowIconsView extends IconMerger {
protected void onFinishInflate() {
super.onFinishInflate();
mNotificationColorUtil = NotificationColorUtil.getInstance(getContext());
- mTintColor = getResources().getColor(R.color.keyguard_overflow_content_color);
+ mTintColor = getContext().getColor(R.color.keyguard_overflow_content_color);
mIconSize = getResources().getDimensionPixelSize(
com.android.internal.R.dimen.status_bar_icon_size);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java
index 57d162b..958b8b5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java
@@ -60,7 +60,7 @@ public class NotificationTemplateViewWrapper extends NotificationViewWrapper {
super(view);
mIconDarkAlpha = ctx.getResources().getInteger(R.integer.doze_small_icon_alpha);
mIconBackgroundDarkColor =
- ctx.getResources().getColor(R.color.doze_small_icon_background_color);
+ ctx.getColor(R.color.doze_small_icon_background_color);
mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(ctx,
android.R.interpolator.linear_out_slow_in);
resolveViews();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java
index 78b9739..b362a29 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java
@@ -19,23 +19,29 @@ package com.android.systemui.statusbar;
import android.content.Context;
import android.view.View;
-import com.android.internal.R;
-
/**
* Wraps the actual notification content view; used to implement behaviors which are different for
* the individual templates and custom views.
*/
public abstract class NotificationViewWrapper {
+ private static final String TAG_BIG_MEDIA_NARROW = "bigMediaNarrow";
+ private static final String TAG_MEDIA = "media";
+ private static final String TAG_BIG_PICTURE = "bigPicture";
+
protected final View mView;
public static NotificationViewWrapper wrap(Context ctx, View v) {
-
- // TODO: Figure out a better way to find out which template the view is.
- if (v.findViewById(com.android.internal.R.id.media_actions) != null) {
- return new NotificationMediaViewWrapper(ctx, v);
- } else if (v.getId() == com.android.internal.R.id.status_bar_latest_event_content) {
- return new NotificationTemplateViewWrapper(ctx, v);
+ if (v.getId() == com.android.internal.R.id.status_bar_latest_event_content) {
+ if (TAG_BIG_MEDIA_NARROW.equals(v.getTag())) {
+ return new NotificationBigMediaNarrowViewWrapper(ctx, v);
+ } else if (TAG_MEDIA.equals(v.getTag())) {
+ return new NotificationMediaViewWrapper(ctx, v);
+ } else if (TAG_BIG_PICTURE.equals(v.getTag())) {
+ return new NotificationBigMediaNarrowViewWrapper(ctx, v);
+ } else {
+ return new NotificationTemplateViewWrapper(ctx, v);
+ }
} else {
return new NotificationCustomViewWrapper(v);
}
@@ -58,4 +64,12 @@ public abstract class NotificationViewWrapper {
* Notifies this wrapper that the content of the view might have changed.
*/
public void notifyContentUpdated() {}
+
+ /**
+ * @return true if this template might need to be clipped with a round rect to make it look
+ * nice, false otherwise
+ */
+ public boolean needsRoundRectClipping() {
+ return false;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ServiceMonitor.java b/packages/SystemUI/src/com/android/systemui/statusbar/ServiceMonitor.java
index aea9ec6..602989a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ServiceMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ServiceMonitor.java
@@ -177,6 +177,7 @@ public class ServiceMonitor {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_PACKAGE_ADDED);
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addDataScheme("package");
mContext.registerReceiver(mBroadcastReceiver, filter);
@@ -196,13 +197,14 @@ public class ServiceMonitor {
+ " extras=" + bundleToString(intent.getExtras()));
if (Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())) {
mHandler.sendEmptyMessage(MSG_START_SERVICE);
- } else if (Intent.ACTION_PACKAGE_CHANGED.equals(intent.getAction())) {
- PackageManager pm = mContext.getPackageManager();
- boolean serviceEnabled =
- pm.getApplicationEnabledSetting(mServiceName.getPackageName())
- != PackageManager.COMPONENT_ENABLED_STATE_DISABLED
+ } else if (Intent.ACTION_PACKAGE_CHANGED.equals(intent.getAction())
+ || Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
+ final PackageManager pm = mContext.getPackageManager();
+ final boolean serviceEnabled = isPackageAvailable()
+ && pm.getApplicationEnabledSetting(mServiceName.getPackageName())
+ != PackageManager.COMPONENT_ENABLED_STATE_DISABLED
&& pm.getComponentEnabledSetting(mServiceName)
- != PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+ != PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
if (mBound && !serviceEnabled) {
stopService();
scheduleCheckBound();
@@ -279,4 +281,25 @@ public class ServiceMonitor {
}
return sb.append('}').toString();
}
+
+ public ComponentName getComponent() {
+ return getComponentNameFromSetting();
+ }
+
+ public void setComponent(ComponentName component) {
+ final String setting = component == null ? null : component.flattenToShortString();
+ Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ mSettingKey, setting, UserHandle.USER_CURRENT);
+ }
+
+ public boolean isPackageAvailable() {
+ final ComponentName component = getComponent();
+ if (component == null) return false;
+ try {
+ return mContext.getPackageManager().isPackageAvailable(component.getPackageName());
+ } catch (RuntimeException e) {
+ Log.w(mTag, "Error checking package availability", e);
+ return false;
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
index 8e35ee9..da1f03e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
@@ -17,7 +17,13 @@
package com.android.systemui.statusbar;
import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.Color;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Animatable;
+import android.graphics.drawable.Drawable;
import android.telephony.SubscriptionInfo;
+import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
@@ -28,8 +34,12 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.StatusBarIconController;
+import com.android.systemui.statusbar.policy.NetworkController.IconState;
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 java.util.ArrayList;
import java.util.List;
@@ -37,27 +47,41 @@ import java.util.List;
// Intimately tied to the design of res/layout/signal_cluster_view.xml
public class SignalClusterView
extends LinearLayout
- implements NetworkControllerImpl.SignalCluster,
- SecurityController.SecurityControllerCallback {
+ implements NetworkControllerImpl.SignalCallback,
+ SecurityController.SecurityControllerCallback, Tunable {
static final String TAG = "SignalClusterView";
static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final String SLOT_AIRPLANE = "airplane";
+ private static final String SLOT_MOBILE = "mobile";
+ private static final String SLOT_WIFI = "wifi";
+ private static final String SLOT_ETHERNET = "ethernet";
+
NetworkControllerImpl mNC;
SecurityController mSC;
private boolean mNoSimsVisible = false;
private boolean mVpnVisible = false;
+ private boolean mEthernetVisible = false;
+ private int mEthernetIconId = 0;
+ private int mLastEthernetIconId = -1;
private boolean mWifiVisible = false;
private int mWifiStrengthId = 0;
+ private int mLastWifiStrengthId = -1;
private boolean mIsAirplaneMode = false;
private int mAirplaneIconId = 0;
- private int mAirplaneContentDescription;
+ private int mLastAirplaneIconId = -1;
+ private String mAirplaneContentDescription;
private String mWifiDescription;
+ private String mEthernetDescription;
private ArrayList<PhoneState> mPhoneStates = new ArrayList<PhoneState>();
+ private int mIconTint = Color.WHITE;
+ private float mDarkIntensity;
- ViewGroup mWifiGroup;
- ImageView mVpn, mWifi, mAirplane, mNoSims;
+ ViewGroup mEthernetGroup, mWifiGroup;
+ View mNoSimsCombo;
+ ImageView mVpn, mEthernet, mWifi, mAirplane, mNoSims, mEthernetDark, mWifiDark, mNoSimsDark;
View mWifiAirplaneSpacer;
View mWifiSignalSpacer;
LinearLayout mMobileSignalGroup;
@@ -67,6 +91,11 @@ public class SignalClusterView
private int mEndPadding;
private int mEndPaddingNothingVisible;
+ private boolean mBlockAirplane;
+ private boolean mBlockMobile;
+ private boolean mBlockWifi;
+ private boolean mBlockEthernet;
+
public SignalClusterView(Context context) {
this(context, null);
}
@@ -79,6 +108,29 @@ public class SignalClusterView
super(context, attrs, defStyle);
}
+ @Override
+ public void onTuningChanged(String key, String newValue) {
+ if (!StatusBarIconController.ICON_BLACKLIST.equals(key)) {
+ return;
+ }
+ ArraySet<String> blockList = StatusBarIconController.getIconBlacklist(newValue);
+ boolean blockAirplane = blockList.contains(SLOT_AIRPLANE);
+ boolean blockMobile = blockList.contains(SLOT_MOBILE);
+ boolean blockWifi = blockList.contains(SLOT_WIFI);
+ boolean blockEthernet = blockList.contains(SLOT_ETHERNET);
+
+ if (blockAirplane != mBlockAirplane || blockMobile != mBlockMobile
+ || blockEthernet != mBlockEthernet || blockWifi != mBlockWifi) {
+ mBlockAirplane = blockAirplane;
+ mBlockMobile = blockMobile;
+ mBlockEthernet = blockEthernet;
+ mBlockWifi = blockWifi;
+ // Re-register to get new callbacks.
+ mNC.removeSignalCallback(this);
+ mNC.addSignalCallback(this);
+ }
+ }
+
public void setNetworkController(NetworkControllerImpl nc) {
if (DEBUG) Log.d(TAG, "NetworkController=" + nc);
mNC = nc;
@@ -109,28 +161,39 @@ public class SignalClusterView
super.onAttachedToWindow();
mVpn = (ImageView) findViewById(R.id.vpn);
+ mEthernetGroup = (ViewGroup) findViewById(R.id.ethernet_combo);
+ mEthernet = (ImageView) findViewById(R.id.ethernet);
+ mEthernetDark = (ImageView) findViewById(R.id.ethernet_dark);
mWifiGroup = (ViewGroup) findViewById(R.id.wifi_combo);
mWifi = (ImageView) findViewById(R.id.wifi_signal);
+ mWifiDark = (ImageView) findViewById(R.id.wifi_signal_dark);
mAirplane = (ImageView) findViewById(R.id.airplane);
mNoSims = (ImageView) findViewById(R.id.no_sims);
+ mNoSimsDark = (ImageView) findViewById(R.id.no_sims_dark);
+ mNoSimsCombo = findViewById(R.id.no_sims_combo);
mWifiAirplaneSpacer = findViewById(R.id.wifi_airplane_spacer);
mWifiSignalSpacer = findViewById(R.id.wifi_signal_spacer);
mMobileSignalGroup = (LinearLayout) findViewById(R.id.mobile_signal_group);
for (PhoneState state : mPhoneStates) {
mMobileSignalGroup.addView(state.mMobileGroup);
}
+ TunerService.get(mContext).addTunable(this, StatusBarIconController.ICON_BLACKLIST);
apply();
+ applyIconTint();
}
@Override
protected void onDetachedFromWindow() {
mVpn = null;
+ mEthernetGroup = null;
+ mEthernet = null;
mWifiGroup = null;
mWifi = null;
mAirplane = null;
mMobileSignalGroup.removeAllViews();
mMobileSignalGroup = null;
+ TunerService.get(mContext).removeTunable(this);
super.onDetachedFromWindow();
}
@@ -148,36 +211,52 @@ public class SignalClusterView
}
@Override
- public void setWifiIndicators(boolean visible, int strengthIcon, String contentDescription) {
- mWifiVisible = visible;
- mWifiStrengthId = strengthIcon;
- mWifiDescription = contentDescription;
+ public void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon,
+ boolean activityIn, boolean activityOut, String description) {
+ mWifiVisible = statusIcon.visible && !mBlockWifi;
+ mWifiStrengthId = statusIcon.icon;
+ mWifiDescription = statusIcon.contentDescription;
apply();
}
@Override
- public void setMobileDataIndicators(boolean visible, int strengthIcon, int typeIcon,
- String contentDescription, String typeContentDescription, boolean isTypeIconWide,
- int subId) {
- PhoneState state = getOrInflateState(subId);
- state.mMobileVisible = visible;
- state.mMobileStrengthId = strengthIcon;
- state.mMobileTypeId = typeIcon;
- state.mMobileDescription = contentDescription;
+ public void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType,
+ int qsType, boolean activityIn, boolean activityOut, String typeContentDescription,
+ String description, boolean isWide, int subId) {
+ PhoneState state = getState(subId);
+ if (state == null) {
+ return;
+ }
+ state.mMobileVisible = statusIcon.visible && !mBlockMobile;
+ state.mMobileStrengthId = statusIcon.icon;
+ state.mMobileTypeId = statusType;
+ state.mMobileDescription = statusIcon.contentDescription;
state.mMobileTypeDescription = typeContentDescription;
- state.mIsMobileTypeIconWide = isTypeIconWide;
+ state.mIsMobileTypeIconWide = statusType != 0 && isWide;
+
+ apply();
+ }
+
+ @Override
+ public void setEthernetIndicators(IconState state) {
+ mEthernetVisible = state.visible && !mBlockEthernet;
+ mEthernetIconId = state.icon;
+ mEthernetDescription = state.contentDescription;
apply();
}
@Override
public void setNoSims(boolean show) {
- mNoSimsVisible = show;
+ mNoSimsVisible = show && !mBlockMobile;
}
@Override
public void setSubs(List<SubscriptionInfo> subs) {
+ if (hasCorrectSubs(subs)) {
+ return;
+ }
// Clear out all old subIds.
mPhoneStates.clear();
if (mMobileSignalGroup != null) {
@@ -187,15 +266,32 @@ public class SignalClusterView
for (int i = 0; i < n; i++) {
inflatePhoneState(subs.get(i).getSubscriptionId());
}
+ if (isAttachedToWindow()) {
+ applyIconTint();
+ }
+ }
+
+ private boolean hasCorrectSubs(List<SubscriptionInfo> subs) {
+ final int N = subs.size();
+ if (N != mPhoneStates.size()) {
+ return false;
+ }
+ for (int i = 0; i < N; i++) {
+ if (mPhoneStates.get(i).mSubId != subs.get(i).getSubscriptionId()) {
+ return false;
+ }
+ }
+ return true;
}
- private PhoneState getOrInflateState(int subId) {
+ private PhoneState getState(int subId) {
for (PhoneState state : mPhoneStates) {
if (state.mSubId == subId) {
return state;
}
}
- return inflatePhoneState(subId);
+ Log.e(TAG, "Unexpected subscription " + subId);
+ return null;
}
private PhoneState inflatePhoneState(int subId) {
@@ -208,32 +304,48 @@ public class SignalClusterView
}
@Override
- public void setIsAirplaneMode(boolean is, int airplaneIconId, int contentDescription) {
- mIsAirplaneMode = is;
- mAirplaneIconId = airplaneIconId;
- mAirplaneContentDescription = contentDescription;
+ public void setIsAirplaneMode(IconState icon) {
+ mIsAirplaneMode = icon.visible && !mBlockAirplane;
+ mAirplaneIconId = icon.icon;
+ mAirplaneContentDescription = icon.contentDescription;
apply();
}
@Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ public void setMobileDataEnabled(boolean enabled) {
+ // Don't care.
+ }
+
+ @Override
+ public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
// Standard group layout onPopulateAccessibilityEvent() implementations
// ignore content description, so populate manually
+ if (mEthernetVisible && mEthernetGroup != null &&
+ mEthernetGroup.getContentDescription() != null)
+ event.getText().add(mEthernetGroup.getContentDescription());
if (mWifiVisible && mWifiGroup != null && mWifiGroup.getContentDescription() != null)
event.getText().add(mWifiGroup.getContentDescription());
for (PhoneState state : mPhoneStates) {
state.populateAccessibilityEvent(event);
}
- return super.dispatchPopulateAccessibilityEvent(event);
+ return super.dispatchPopulateAccessibilityEventInternal(event);
}
@Override
public void onRtlPropertiesChanged(int layoutDirection) {
super.onRtlPropertiesChanged(layoutDirection);
+ if (mEthernet != null) {
+ mEthernet.setImageDrawable(null);
+ mEthernetDark.setImageDrawable(null);
+ mLastEthernetIconId = -1;
+ }
+
if (mWifi != null) {
mWifi.setImageDrawable(null);
+ mWifiDark.setImageDrawable(null);
+ mLastWifiStrengthId = -1;
}
for (PhoneState state : mPhoneStates) {
@@ -245,8 +357,9 @@ public class SignalClusterView
}
}
- if(mAirplane != null) {
+ if (mAirplane != null) {
mAirplane.setImageDrawable(null);
+ mLastAirplaneIconId = -1;
}
apply();
@@ -263,8 +376,30 @@ public class SignalClusterView
mVpn.setVisibility(mVpnVisible ? View.VISIBLE : View.GONE);
if (DEBUG) Log.d(TAG, String.format("vpn: %s", mVpnVisible ? "VISIBLE" : "GONE"));
+
+ if (mEthernetVisible) {
+ if (mLastEthernetIconId != mEthernetIconId) {
+ mEthernet.setImageResource(mEthernetIconId);
+ mEthernetDark.setImageResource(mEthernetIconId);
+ mLastEthernetIconId = mEthernetIconId;
+ }
+ mEthernetGroup.setContentDescription(mEthernetDescription);
+ mEthernetGroup.setVisibility(View.VISIBLE);
+ } else {
+ mEthernetGroup.setVisibility(View.GONE);
+ }
+
+ if (DEBUG) Log.d(TAG,
+ String.format("ethernet: %s",
+ (mEthernetVisible ? "VISIBLE" : "GONE")));
+
+
if (mWifiVisible) {
- mWifi.setImageResource(mWifiStrengthId);
+ if (mWifiStrengthId != mLastWifiStrengthId) {
+ mWifi.setImageResource(mWifiStrengthId);
+ mWifiDark.setImageResource(mWifiStrengthId);
+ mLastWifiStrengthId = mWifiStrengthId;
+ }
mWifiGroup.setContentDescription(mWifiDescription);
mWifiGroup.setVisibility(View.VISIBLE);
} else {
@@ -288,9 +423,11 @@ public class SignalClusterView
}
if (mIsAirplaneMode) {
- mAirplane.setImageResource(mAirplaneIconId);
- mAirplane.setContentDescription(mAirplaneContentDescription != 0 ?
- mContext.getString(mAirplaneContentDescription) : null);
+ if (mLastAirplaneIconId != mAirplaneIconId) {
+ mAirplane.setImageResource(mAirplaneIconId);
+ mLastAirplaneIconId = mAirplaneIconId;
+ }
+ mAirplane.setContentDescription(mAirplaneContentDescription);
mAirplane.setVisibility(View.VISIBLE);
} else {
mAirplane.setVisibility(View.GONE);
@@ -308,13 +445,43 @@ public class SignalClusterView
mWifiSignalSpacer.setVisibility(View.GONE);
}
- mNoSims.setVisibility(mNoSimsVisible ? View.VISIBLE : View.GONE);
+ mNoSimsCombo.setVisibility(mNoSimsVisible ? View.VISIBLE : View.GONE);
boolean anythingVisible = mNoSimsVisible || mWifiVisible || mIsAirplaneMode
- || anyMobileVisible || mVpnVisible;
+ || anyMobileVisible || mVpnVisible || mEthernetVisible;
setPaddingRelative(0, 0, anythingVisible ? mEndPadding : mEndPaddingNothingVisible, 0);
}
+ public void setIconTint(int tint, float darkIntensity) {
+ boolean changed = tint != mIconTint || darkIntensity != mDarkIntensity;
+ mIconTint = tint;
+ mDarkIntensity = darkIntensity;
+ if (changed && isAttachedToWindow()) {
+ applyIconTint();
+ }
+ }
+
+ private void applyIconTint() {
+ setTint(mVpn, mIconTint);
+ setTint(mAirplane, mIconTint);
+ applyDarkIntensity(mDarkIntensity, mNoSims, mNoSimsDark);
+ applyDarkIntensity(mDarkIntensity, mWifi, mWifiDark);
+ applyDarkIntensity(mDarkIntensity, mEthernet, mEthernetDark);
+ for (int i = 0; i < mPhoneStates.size(); i++) {
+ mPhoneStates.get(i).setIconTint(mIconTint, mDarkIntensity);
+ }
+ }
+
+ private void applyDarkIntensity(float darkIntensity, View lightIcon, View darkIcon) {
+ lightIcon.setAlpha(1 - darkIntensity);
+ darkIcon.setAlpha(darkIntensity);
+ }
+
+ private void setTint(ImageView v, int tint) {
+ v.setImageTintMode(PorterDuff.Mode.SRC_ATOP);
+ v.setImageTintList(ColorStateList.valueOf(tint));
+ }
+
private class PhoneState {
private final int mSubId;
private boolean mMobileVisible = false;
@@ -323,7 +490,7 @@ public class SignalClusterView
private String mMobileDescription, mMobileTypeDescription;
private ViewGroup mMobileGroup;
- private ImageView mMobile, mMobileType;
+ private ImageView mMobile, mMobileDark, mMobileType;
public PhoneState(int subId, Context context) {
ViewGroup root = (ViewGroup) LayoutInflater.from(context)
@@ -335,12 +502,30 @@ public class SignalClusterView
public void setViews(ViewGroup root) {
mMobileGroup = root;
mMobile = (ImageView) root.findViewById(R.id.mobile_signal);
+ mMobileDark = (ImageView) root.findViewById(R.id.mobile_signal_dark);
mMobileType = (ImageView) root.findViewById(R.id.mobile_type);
}
public boolean apply(boolean isSecondaryIcon) {
if (mMobileVisible && !mIsAirplaneMode) {
mMobile.setImageResource(mMobileStrengthId);
+ Drawable mobileDrawable = mMobile.getDrawable();
+ if (mobileDrawable instanceof Animatable) {
+ Animatable ad = (Animatable) mobileDrawable;
+ if (!ad.isRunning()) {
+ ad.start();
+ }
+ }
+
+ mMobileDark.setImageResource(mMobileStrengthId);
+ Drawable mobileDarkDrawable = mMobileDark.getDrawable();
+ if (mobileDarkDrawable instanceof Animatable) {
+ Animatable ad = (Animatable) mobileDarkDrawable;
+ if (!ad.isRunning()) {
+ ad.start();
+ }
+ }
+
mMobileType.setImageResource(mMobileTypeId);
mMobileGroup.setContentDescription(mMobileTypeDescription
+ " " + mMobileDescription);
@@ -354,6 +539,8 @@ public class SignalClusterView
0, 0, 0);
mMobile.setPaddingRelative(mIsMobileTypeIconWide ? mWideTypeIconStartPadding : 0,
0, 0, 0);
+ mMobileDark.setPaddingRelative(mIsMobileTypeIconWide ? mWideTypeIconStartPadding : 0,
+ 0, 0, 0);
if (DEBUG) Log.d(TAG, String.format("mobile: %s sig=%d typ=%d",
(mMobileVisible ? "VISIBLE" : "GONE"), mMobileStrengthId, mMobileTypeId));
@@ -369,6 +556,11 @@ public class SignalClusterView
event.getText().add(mMobileGroup.getContentDescription());
}
}
+
+ public void setIconTint(int tint, float darkIntensity) {
+ applyDarkIntensity(darkIntensity, mMobile, mMobileDark);
+ setTint(mMobileType, tint);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StackScrollerDecorView.java
index 64d80cc..2f66c41 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StackScrollerDecorView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StackScrollerDecorView.java
@@ -32,7 +32,6 @@ public abstract class StackScrollerDecorView extends ExpandableView {
protected View mContent;
private boolean mIsVisible;
private boolean mAnimating;
- private boolean mWillBeGone;
public StackScrollerDecorView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -134,13 +133,5 @@ public abstract class StackScrollerDecorView extends ExpandableView {
mContent.animate().cancel();
}
- public boolean willBeGone() {
- return mWillBeGone;
- }
-
- public void setWillBeGone(boolean willBeGone) {
- mWillBeGone = willBeGone;
- }
-
protected abstract View findContentView();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 20dd3e7..baac8ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -18,12 +18,12 @@ package com.android.systemui.statusbar;
import android.app.Notification;
import android.content.Context;
-import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.AttributeSet;
@@ -48,14 +48,21 @@ public class StatusBarIconView extends AnimatedImageView {
private int mNumberY;
private String mNumberText;
private Notification mNotification;
+ private final boolean mBlocked;
public StatusBarIconView(Context context, String slot, Notification notification) {
+ this(context, slot, notification, false);
+ }
+
+ public StatusBarIconView(Context context, String slot, Notification notification,
+ boolean blocked) {
super(context);
final Resources res = context.getResources();
+ mBlocked = blocked;
mSlot = slot;
mNumberPain = new Paint();
mNumberPain.setTextAlign(Paint.Align.CENTER);
- mNumberPain.setColor(res.getColor(R.drawable.notification_number_text_color));
+ mNumberPain.setColor(context.getColor(R.drawable.notification_number_text_color));
mNumberPain.setAntiAlias(true);
setNotification(notification);
@@ -79,6 +86,7 @@ public class StatusBarIconView extends AnimatedImageView {
public StatusBarIconView(Context context, AttributeSet attrs) {
super(context, attrs);
+ mBlocked = false;
final Resources res = context.getResources();
final int outerBounds = res.getDimensionPixelSize(R.dimen.status_bar_icon_size);
final int imageBounds = res.getDimensionPixelSize(R.dimen.status_bar_icon_drawing_size);
@@ -100,13 +108,23 @@ public class StatusBarIconView extends AnimatedImageView {
return a.equals(b);
}
+ public boolean equalIcons(Icon a, Icon b) {
+ if (a == b) return true;
+ if (a.getType() != b.getType()) return false;
+ switch (a.getType()) {
+ case Icon.TYPE_RESOURCE:
+ return a.getResPackage().equals(b.getResPackage()) && a.getResId() == b.getResId();
+ case Icon.TYPE_URI:
+ return a.getUriString().equals(b.getUriString());
+ default:
+ return false;
+ }
+ }
/**
* Returns whether the set succeeded.
*/
public boolean set(StatusBarIcon icon) {
- final boolean iconEquals = mIcon != null
- && streq(mIcon.iconPackage, icon.iconPackage)
- && mIcon.iconId == icon.iconId;
+ final boolean iconEquals = mIcon != null && equalIcons(mIcon.icon, icon.icon);
final boolean levelEquals = iconEquals
&& mIcon.iconLevel == icon.iconLevel;
final boolean visibilityEquals = mIcon != null
@@ -137,7 +155,7 @@ public class StatusBarIconView extends AnimatedImageView {
invalidate();
}
if (!visibilityEquals) {
- setVisibility(icon.visible ? VISIBLE : GONE);
+ setVisibility(icon.visible && !mBlocked ? VISIBLE : GONE);
}
return true;
}
@@ -147,6 +165,9 @@ public class StatusBarIconView extends AnimatedImageView {
}
private boolean updateDrawable(boolean withClear) {
+ if (mIcon == null) {
+ return false;
+ }
Drawable drawable = getIcon(mIcon);
if (drawable == null) {
Log.w(TAG, "No icon for slot " + mSlot);
@@ -164,45 +185,18 @@ public class StatusBarIconView extends AnimatedImageView {
}
/**
- * Returns the right icon to use for this item, respecting the iconId and
- * iconPackage (if set)
+ * Returns the right icon to use for this item
*
- * @param context Context to use to get resources if iconPackage is not set
+ * @param context Context to use to get resources
* @return Drawable for this item, or null if the package or item could not
* be found
*/
public static Drawable getIcon(Context context, StatusBarIcon icon) {
- Resources r = null;
-
- if (icon.iconPackage != null) {
- try {
- int userId = icon.user.getIdentifier();
- if (userId == UserHandle.USER_ALL) {
- userId = UserHandle.USER_OWNER;
- }
- r = context.getPackageManager()
- .getResourcesForApplicationAsUser(icon.iconPackage, userId);
- } catch (PackageManager.NameNotFoundException ex) {
- Log.e(TAG, "Icon package not found: " + icon.iconPackage);
- return null;
- }
- } else {
- r = context.getResources();
+ int userId = icon.user.getIdentifier();
+ if (userId == UserHandle.USER_ALL) {
+ userId = UserHandle.USER_OWNER;
}
-
- if (icon.iconId == 0) {
- return null;
- }
-
- try {
- return r.getDrawable(icon.iconId);
- } catch (RuntimeException e) {
- Log.w(TAG, "Icon not found in "
- + (icon.iconPackage != null ? icon.iconId : "<system>")
- + ": " + Integer.toHexString(icon.iconId));
- }
-
- return null;
+ return icon.icon.loadDrawableAsUser(context, userId);
}
public StatusBarIcon getStatusBarIcon() {
@@ -226,6 +220,12 @@ public class StatusBarIconView extends AnimatedImageView {
}
@Override
+ public void onRtlPropertiesChanged(int layoutDirection) {
+ super.onRtlPropertiesChanged(layoutDirection);
+ updateDrawable();
+ }
+
+ @Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
@@ -288,4 +288,8 @@ public class StatusBarIconView extends AnimatedImageView {
return "StatusBarIconView(slot=" + mSlot + " icon=" + mIcon
+ " notification=" + mNotification + ")";
}
+
+ public String getSlot() {
+ return mSlot;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarter.java
index 23810f9..9ef320b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarter.java
@@ -24,5 +24,11 @@ import android.content.Intent;
* Keyguard.
*/
public interface ActivityStarter {
- public void startActivity(Intent intent, boolean dismissShade);
+ void startActivity(Intent intent, boolean dismissShade);
+ void startActivity(Intent intent, boolean dismissShade, Callback callback);
+ void preventNextAnimation();
+
+ interface Callback {
+ void onActivityStarted(int resultCode);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
index e89e15d..1601b83 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
@@ -145,12 +145,12 @@ public class BarTransitions {
mTransparent = 0x2f0000ff;
mWarning = 0xffff0000;
} else {
- mOpaque = res.getColor(R.color.system_bar_background_opaque);
- mSemiTransparent = res.getColor(R.color.system_bar_background_semi_transparent);
- mTransparent = res.getColor(R.color.system_bar_background_transparent);
- mWarning = res.getColor(com.android.internal.R.color.battery_saver_mode_color);
+ mOpaque = context.getColor(R.color.system_bar_background_opaque);
+ mSemiTransparent = context.getColor(R.color.system_bar_background_semi_transparent);
+ mTransparent = context.getColor(R.color.system_bar_background_transparent);
+ mWarning = context.getColor(com.android.internal.R.color.battery_saver_mode_color);
}
- mGradient = res.getDrawable(gradientResourceId);
+ mGradient = context.getDrawable(gradientResourceId);
mInterpolator = new LinearInterpolator();
}
@@ -160,7 +160,7 @@ public class BarTransitions {
}
@Override
- public void setColorFilter(ColorFilter cf) {
+ public void setColorFilter(ColorFilter colorFilter) {
// noop
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
index 6cb5bcc..fcdd4b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.phone;
+import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.os.UserHandle;
import android.view.Gravity;
@@ -90,24 +91,12 @@ public class DemoStatusIcons extends LinearLayout implements DemoMode {
: 0;
updateSlot("alarm_clock", null, iconId);
}
- String sync = args.getString("sync");
- if (sync != null) {
- int iconId = sync.equals("show") ? R.drawable.stat_sys_sync
- : 0;
- updateSlot("sync_active", null, iconId);
- }
String tty = args.getString("tty");
if (tty != null) {
int iconId = tty.equals("show") ? R.drawable.stat_sys_tty_mode
: 0;
updateSlot("tty", null, iconId);
}
- String eri = args.getString("eri");
- if (eri != null) {
- int iconId = eri.equals("show") ? R.drawable.stat_sys_roaming_cdma_0
- : 0;
- updateSlot("cdma_eri", null, iconId);
- }
String mute = args.getString("mute");
if (mute != null) {
int iconId = mute.equals("show") ? android.R.drawable.stat_notify_call_mute
@@ -135,6 +124,9 @@ public class DemoStatusIcons extends LinearLayout implements DemoMode {
private void updateSlot(String slot, String iconPkg, int iconId) {
if (!mDemoMode) return;
+ if (iconPkg == null) {
+ iconPkg = mContext.getPackageName();
+ }
int removeIndex = -1;
for (int i = 0; i < getChildCount(); i++) {
StatusBarIconView v = (StatusBarIconView) getChildAt(i);
@@ -144,8 +136,7 @@ public class DemoStatusIcons extends LinearLayout implements DemoMode {
break;
} else {
StatusBarIcon icon = v.getStatusBarIcon();
- icon.iconPackage = iconPkg;
- icon.iconId = iconId;
+ icon.icon = Icon.createWithResource(icon.icon.getResPackage(), iconId);
v.set(icon);
v.updateDrawable();
return;
@@ -155,13 +146,13 @@ public class DemoStatusIcons extends LinearLayout implements DemoMode {
if (iconId == 0) {
if (removeIndex != -1) {
removeViewAt(removeIndex);
- return;
}
+ return;
}
- StatusBarIcon icon = new StatusBarIcon(iconPkg, UserHandle.CURRENT, iconId, 0, 0, "Demo");
+ StatusBarIcon icon = new StatusBarIcon(iconPkg, UserHandle.OWNER, iconId, 0, 0, "Demo");
StatusBarIconView v = new StatusBarIconView(getContext(), null, null);
v.setTag(slot);
v.set(icon);
addView(v, 0, new LinearLayout.LayoutParams(mIconSize, mIconSize));
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
new file mode 100644
index 0000000..df8c7fa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
@@ -0,0 +1,172 @@
+/*
+ * 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.statusbar.phone;
+
+import android.content.Context;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+import com.android.systemui.Gefingerpoken;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.ExpandableView;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
+
+/**
+ * A helper class to handle touches on the heads-up views.
+ */
+public class HeadsUpTouchHelper implements Gefingerpoken {
+
+ private HeadsUpManager mHeadsUpManager;
+ private NotificationStackScrollLayout mStackScroller;
+ private int mTrackingPointer;
+ private float mTouchSlop;
+ private float mInitialTouchX;
+ private float mInitialTouchY;
+ private boolean mTouchingHeadsUpView;
+ private boolean mTrackingHeadsUp;
+ private boolean mCollapseSnoozes;
+ private NotificationPanelView mPanel;
+ private ExpandableNotificationRow mPickedChild;
+ private final int mNotificationsTopPadding;
+
+ public HeadsUpTouchHelper(HeadsUpManager headsUpManager,
+ NotificationStackScrollLayout stackScroller,
+ NotificationPanelView notificationPanelView) {
+ mHeadsUpManager = headsUpManager;
+ mStackScroller = stackScroller;
+ mPanel = notificationPanelView;
+ Context context = stackScroller.getContext();
+ final ViewConfiguration configuration = ViewConfiguration.get(context);
+ mTouchSlop = configuration.getScaledTouchSlop();
+ mNotificationsTopPadding = context.getResources()
+ .getDimensionPixelSize(R.dimen.notifications_top_padding);
+ }
+
+ public boolean isTrackingHeadsUp() {
+ return mTrackingHeadsUp;
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent event) {
+ if (!mTouchingHeadsUpView && event.getActionMasked() != MotionEvent.ACTION_DOWN) {
+ return false;
+ }
+ int pointerIndex = event.findPointerIndex(mTrackingPointer);
+ if (pointerIndex < 0) {
+ pointerIndex = 0;
+ mTrackingPointer = event.getPointerId(pointerIndex);
+ }
+ final float x = event.getX(pointerIndex);
+ final float y = event.getY(pointerIndex);
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ mInitialTouchY = y;
+ mInitialTouchX = x;
+ setTrackingHeadsUp(false);
+ ExpandableView child = mStackScroller.getChildAtRawPosition(x, y);
+ if (child == null && y < mNotificationsTopPadding) {
+ // We should also allow drags from the margin above the heads up
+ child = mStackScroller.getChildAtRawPosition(x, y + mNotificationsTopPadding);
+ }
+ mTouchingHeadsUpView = false;
+ if (child instanceof ExpandableNotificationRow) {
+ mPickedChild = (ExpandableNotificationRow) child;
+ mTouchingHeadsUpView = mPickedChild.isHeadsUp() && mPickedChild.isPinned();
+ }
+ break;
+ case MotionEvent.ACTION_POINTER_UP:
+ final int upPointer = event.getPointerId(event.getActionIndex());
+ if (mTrackingPointer == upPointer) {
+ // gesture is ongoing, find a new pointer to track
+ final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
+ mTrackingPointer = event.getPointerId(newIndex);
+ mInitialTouchX = event.getX(newIndex);
+ mInitialTouchY = event.getY(newIndex);
+ }
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ final float h = y - mInitialTouchY;
+ if (mTouchingHeadsUpView && Math.abs(h) > mTouchSlop
+ && Math.abs(h) > Math.abs(x - mInitialTouchX)) {
+ setTrackingHeadsUp(true);
+ mCollapseSnoozes = h < 0;
+ mInitialTouchX = x;
+ mInitialTouchY = y;
+ int expandedHeight = mPickedChild.getActualHeight();
+ mPanel.setPanelScrimMinFraction((float) expandedHeight
+ / mPanel.getMaxPanelHeight());
+ mPanel.startExpandMotion(x, y, true /* startTracking */, expandedHeight
+ + mNotificationsTopPadding);
+ mPanel.clearNotificattonEffects();
+ mHeadsUpManager.unpinAll();
+ return true;
+ }
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ if (mPickedChild != null && mTouchingHeadsUpView) {
+ // We may swallow this click if the heads up just came in.
+ if (mHeadsUpManager.shouldSwallowClick(
+ mPickedChild.getStatusBarNotification().getKey())) {
+ endMotion();
+ return true;
+ }
+ }
+ endMotion();
+ break;
+ }
+ return false;
+ }
+
+ private void setTrackingHeadsUp(boolean tracking) {
+ mTrackingHeadsUp = tracking;
+ mHeadsUpManager.setTrackingHeadsUp(tracking);
+ mPanel.setTrackingHeadsUp(tracking);
+ }
+
+ public void notifyFling(boolean collapse) {
+ if (collapse && mCollapseSnoozes) {
+ mHeadsUpManager.snooze();
+ }
+ mCollapseSnoozes = false;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (!mTrackingHeadsUp) {
+ return false;
+ }
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ endMotion();
+ setTrackingHeadsUp(false);
+ break;
+ }
+ return true;
+ }
+
+ private void endMotion() {
+ mTrackingPointer = -1;
+ mPickedChild = null;
+ mTouchingHeadsUpView = false;
+ }
+}
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 3b8fccc..0877ff9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
@@ -39,7 +39,7 @@ public class KeyguardAffordanceHelper {
public static final float SWIPE_RESTING_ALPHA_AMOUNT = 0.5f;
public static final long HINT_PHASE1_DURATION = 200;
private static final long HINT_PHASE2_DURATION = 350;
- private static final float BACKGROUND_RADIUS_SCALE_FACTOR = 0.15f;
+ private static final float BACKGROUND_RADIUS_SCALE_FACTOR = 0.25f;
private static final int HINT_CIRCLE_OPEN_DURATION = 500;
private final Context mContext;
@@ -63,13 +63,30 @@ public class KeyguardAffordanceHelper {
private Interpolator mDisappearInterpolator;
private Animator mSwipeAnimator;
private int mMinBackgroundRadius;
- private boolean mMotionPerformedByUser;
private boolean mMotionCancelled;
+ private int mTouchTargetSize;
+ private View mTargetedView;
+ private boolean mTouchSlopExeeded;
private AnimatorListenerAdapter mFlingEndListener = new AnimatorListenerAdapter() {
+ public boolean mCancelled;
+
@Override
public void onAnimationEnd(Animator animation) {
mSwipeAnimator = null;
- setSwipingInProgress(false);
+ mSwipingInProgress = false;
+ if (!mCancelled) {
+ mTargetedView = null;
+ }
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCancelled = true;
+ }
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mCancelled = false;
}
};
private Runnable mAnimationEndRunnable = new Runnable() {
@@ -83,9 +100,9 @@ public class KeyguardAffordanceHelper {
mContext = context;
mCallback = callback;
initIcons();
- updateIcon(mLeftIcon, 0.0f, SWIPE_RESTING_ALPHA_AMOUNT, false, false);
- updateIcon(mCenterIcon, 0.0f, SWIPE_RESTING_ALPHA_AMOUNT, false, false);
- updateIcon(mRightIcon, 0.0f, SWIPE_RESTING_ALPHA_AMOUNT, false, false);
+ updateIcon(mLeftIcon, 0.0f, mLeftIcon.getRestingAlpha(), false, false);
+ updateIcon(mCenterIcon, 0.0f, mCenterIcon.getRestingAlpha(), false, false);
+ updateIcon(mRightIcon, 0.0f, mRightIcon.getRestingAlpha(), false, false);
initDimens();
}
@@ -97,6 +114,8 @@ public class KeyguardAffordanceHelper {
R.dimen.keyguard_min_swipe_amount);
mMinBackgroundRadius = mContext.getResources().getDimensionPixelSize(
R.dimen.keyguard_affordance_min_background_radius);
+ mTouchTargetSize = mContext.getResources().getDimensionPixelSize(
+ R.dimen.keyguard_affordance_touch_target_size);
mHintGrowAmount =
mContext.getResources().getDimensionPixelSize(R.dimen.hint_grow_amount_sideways);
mFlingAnimationUtils = new FlingAnimationUtils(mContext, 0.4f);
@@ -108,70 +127,111 @@ public class KeyguardAffordanceHelper {
private void initIcons() {
mLeftIcon = mCallback.getLeftIcon();
- mLeftIcon.setIsLeft(true);
mCenterIcon = mCallback.getCenterIcon();
mRightIcon = mCallback.getRightIcon();
- mRightIcon.setIsLeft(false);
+ updatePreviews();
+ }
+
+ public void updatePreviews() {
mLeftIcon.setPreviewView(mCallback.getLeftPreview());
mRightIcon.setPreviewView(mCallback.getRightPreview());
}
public boolean onTouchEvent(MotionEvent event) {
- if (mMotionCancelled && event.getActionMasked() != MotionEvent.ACTION_DOWN) {
+ int action = event.getActionMasked();
+ if (mMotionCancelled && action != MotionEvent.ACTION_DOWN) {
return false;
}
final float y = event.getY();
final float x = event.getX();
boolean isUp = false;
- switch (event.getActionMasked()) {
+ switch (action) {
case MotionEvent.ACTION_DOWN:
- if (mSwipingInProgress) {
+ View targetView = getIconAtPosition(x, y);
+ if (targetView == null || (mTargetedView != null && mTargetedView != targetView)) {
+ mMotionCancelled = true;
+ return false;
+ }
+ if (mTargetedView != null) {
cancelAnimation();
+ } else {
+ mTouchSlopExeeded = false;
}
- mInitialTouchY = y;
+ mCallback.onSwipingStarted(targetView == mRightIcon);
+ mSwipingInProgress = true;
+ mTargetedView = targetView;
mInitialTouchX = x;
+ mInitialTouchY = y;
mTranslationOnDown = mTranslation;
initVelocityTracker();
trackMovement(event);
- mMotionPerformedByUser = false;
mMotionCancelled = false;
break;
case MotionEvent.ACTION_POINTER_DOWN:
mMotionCancelled = true;
- endMotion(event, true /* forceSnapBack */);
+ endMotion(true /* forceSnapBack */, x, y);
break;
case MotionEvent.ACTION_MOVE:
- final float w = x - mInitialTouchX;
trackMovement(event);
- if (((leftSwipePossible() && w > mTouchSlop)
- || (rightSwipePossible() && w < -mTouchSlop))
- && Math.abs(w) > Math.abs(y - mInitialTouchY)
- && !mSwipingInProgress) {
- cancelAnimation();
- mInitialTouchY = y;
- mInitialTouchX = x;
- mTranslationOnDown = mTranslation;
- setSwipingInProgress(true);
+ float xDist = x - mInitialTouchX;
+ float yDist = y - mInitialTouchY;
+ float distance = (float) Math.hypot(xDist, yDist);
+ if (!mTouchSlopExeeded && distance > mTouchSlop) {
+ mTouchSlopExeeded = true;
}
if (mSwipingInProgress) {
- setTranslation(mTranslationOnDown + x - mInitialTouchX, false, false);
+ if (mTargetedView == mRightIcon) {
+ distance = mTranslationOnDown - distance;
+ distance = Math.min(0, distance);
+ } else {
+ distance = mTranslationOnDown + distance;
+ distance = Math.max(0, distance);
+ }
+ setTranslation(distance, false /* isReset */, false /* animateReset */);
}
break;
case MotionEvent.ACTION_UP:
isUp = true;
case MotionEvent.ACTION_CANCEL:
+ boolean hintOnTheRight = mTargetedView == mRightIcon;
trackMovement(event);
- endMotion(event, !isUp);
+ endMotion(!isUp, x, y);
+ if (!mTouchSlopExeeded && isUp) {
+ mCallback.onIconClicked(hintOnTheRight);
+ }
break;
}
return true;
}
- private void endMotion(MotionEvent event, boolean forceSnapBack) {
+ private View getIconAtPosition(float x, float y) {
+ if (leftSwipePossible() && isOnIcon(mLeftIcon, x, y)) {
+ return mLeftIcon;
+ }
+ if (rightSwipePossible() && isOnIcon(mRightIcon, x, y)) {
+ return mRightIcon;
+ }
+ return null;
+ }
+
+ public boolean isOnAffordanceIcon(float x, float y) {
+ return isOnIcon(mLeftIcon, x, y) || isOnIcon(mRightIcon, x, y);
+ }
+
+ private boolean isOnIcon(View icon, float x, float y) {
+ float iconX = icon.getX() + icon.getWidth() / 2.0f;
+ float iconY = icon.getY() + icon.getHeight() / 2.0f;
+ double distance = Math.hypot(x - iconX, y - iconY);
+ return distance <= mTouchTargetSize / 2;
+ }
+
+ private void endMotion(boolean forceSnapBack, float lastX, float lastY) {
if (mSwipingInProgress) {
- flingWithCurrentVelocity(forceSnapBack);
+ flingWithCurrentVelocity(forceSnapBack, lastX, lastY);
+ } else {
+ mTargetedView = null;
}
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
@@ -179,13 +239,6 @@ public class KeyguardAffordanceHelper {
}
}
- private void setSwipingInProgress(boolean inProgress) {
- mSwipingInProgress = inProgress;
- if (inProgress) {
- mCallback.onSwipingStarted();
- }
- }
-
private boolean rightSwipePossible() {
return mRightIcon.getVisibility() == View.VISIBLE;
}
@@ -198,14 +251,14 @@ public class KeyguardAffordanceHelper {
return false;
}
- public void startHintAnimation(boolean right, Runnable onFinishedListener) {
-
+ public void startHintAnimation(boolean right,
+ Runnable onFinishedListener) {
+ cancelAnimation();
startHintAnimationPhase1(right, onFinishedListener);
}
private void startHintAnimationPhase1(final boolean right, final Runnable onFinishedListener) {
final KeyguardAffordanceView targetView = right ? mRightIcon : mLeftIcon;
- targetView.showArrow(true);
ValueAnimator animator = getAnimatorToRadius(right, mHintGrowAmount);
animator.addListener(new AnimatorListenerAdapter() {
private boolean mCancelled;
@@ -219,8 +272,8 @@ public class KeyguardAffordanceHelper {
public void onAnimationEnd(Animator animation) {
if (mCancelled) {
mSwipeAnimator = null;
+ mTargetedView = null;
onFinishedListener.run();
- targetView.showArrow(false);
} else {
startUnlockHintAnimationPhase2(right, onFinishedListener);
}
@@ -230,6 +283,7 @@ public class KeyguardAffordanceHelper {
animator.setDuration(HINT_PHASE1_DURATION);
animator.start();
mSwipeAnimator = animator;
+ mTargetedView = targetView;
}
/**
@@ -242,14 +296,9 @@ public class KeyguardAffordanceHelper {
@Override
public void onAnimationEnd(Animator animation) {
mSwipeAnimator = null;
- targetView.showArrow(false);
+ mTargetedView = null;
onFinishedListener.run();
}
-
- @Override
- public void onAnimationStart(Animator animation) {
- targetView.showArrow(false);
- }
});
animator.setInterpolator(mDisappearInterpolator);
animator.setDuration(HINT_PHASE2_DURATION);
@@ -268,7 +317,7 @@ public class KeyguardAffordanceHelper {
targetView.setCircleRadiusWithoutAnimation(newRadius);
float translation = getTranslationFromRadius(newRadius);
mTranslation = right ? -translation : translation;
- updateIconsFromRadius(targetView, newRadius);
+ updateIconsFromTranslation(targetView);
}
});
return animator;
@@ -280,8 +329,8 @@ public class KeyguardAffordanceHelper {
}
}
- private void flingWithCurrentVelocity(boolean forceSnapBack) {
- float vel = getCurrentVelocity();
+ private void flingWithCurrentVelocity(boolean forceSnapBack, float lastX, float lastY) {
+ float vel = getCurrentVelocity(lastX, lastY);
// We snap back if the current translation is not far enough
boolean snapBack = isBelowFalsingThreshold();
@@ -303,7 +352,9 @@ public class KeyguardAffordanceHelper {
}
private void fling(float vel, final boolean snapBack) {
- float target = mTranslation < 0 ? -mCallback.getPageWidth() : mCallback.getPageWidth();
+ float target = mTranslation < 0
+ ? -mCallback.getMaxTranslationDistance()
+ : mCallback.getMaxTranslationDistance();
target = snapBack ? 0 : target;
ValueAnimator animator = ValueAnimator.ofFloat(mTranslation, target);
@@ -323,6 +374,9 @@ public class KeyguardAffordanceHelper {
}
animator.start();
mSwipeAnimator = animator;
+ if (snapBack) {
+ mCallback.onSwipingAborted();
+ }
}
private void startFinishingCircleAnimation(float velocity, Runnable mAnimationEndRunnable) {
@@ -334,62 +388,64 @@ public class KeyguardAffordanceHelper {
translation = rightSwipePossible() ? translation : Math.max(0, translation);
translation = leftSwipePossible() ? translation : Math.min(0, translation);
float absTranslation = Math.abs(translation);
- if (absTranslation > Math.abs(mTranslationOnDown) + getMinTranslationAmount() ||
- mMotionPerformedByUser) {
- mMotionPerformedByUser = true;
- }
if (translation != mTranslation || isReset) {
KeyguardAffordanceView targetView = translation > 0 ? mLeftIcon : mRightIcon;
KeyguardAffordanceView otherView = translation > 0 ? mRightIcon : mLeftIcon;
float alpha = absTranslation / getMinTranslationAmount();
// We interpolate the alpha of the other icons to 0
- float fadeOutAlpha = SWIPE_RESTING_ALPHA_AMOUNT * (1.0f - alpha);
- fadeOutAlpha = Math.max(0.0f, fadeOutAlpha);
-
- // We interpolate the alpha of the targetView to 1
- alpha = fadeOutAlpha + alpha;
+ float fadeOutAlpha = 1.0f - alpha;
+ fadeOutAlpha = Math.max(fadeOutAlpha, 0.0f);
boolean animateIcons = isReset && animateReset;
float radius = getRadiusFromTranslation(absTranslation);
boolean slowAnimation = isReset && isBelowFalsingThreshold();
if (!isReset) {
- updateIcon(targetView, radius, alpha, false, false);
+ updateIcon(targetView, radius, alpha + fadeOutAlpha * targetView.getRestingAlpha(),
+ false, false);
} else {
- updateIcon(targetView, 0.0f, fadeOutAlpha, animateIcons, slowAnimation);
+ updateIcon(targetView, 0.0f, fadeOutAlpha * targetView.getRestingAlpha(),
+ animateIcons, slowAnimation);
}
- updateIcon(otherView, 0.0f, fadeOutAlpha, animateIcons, slowAnimation);
- updateIcon(mCenterIcon, 0.0f, fadeOutAlpha, animateIcons, slowAnimation);
+ updateIcon(otherView, 0.0f, fadeOutAlpha * otherView.getRestingAlpha(),
+ animateIcons, slowAnimation);
+ updateIcon(mCenterIcon, 0.0f, fadeOutAlpha * mCenterIcon.getRestingAlpha(),
+ animateIcons, slowAnimation);
mTranslation = translation;
}
}
- private void updateIconsFromRadius(KeyguardAffordanceView targetView, float newRadius) {
- float alpha = newRadius / mMinBackgroundRadius;
+ private void updateIconsFromTranslation(KeyguardAffordanceView targetView) {
+ float absTranslation = Math.abs(mTranslation);
+ float alpha = absTranslation / getMinTranslationAmount();
// We interpolate the alpha of the other icons to 0
- float fadeOutAlpha = SWIPE_RESTING_ALPHA_AMOUNT * (1.0f - alpha);
+ float fadeOutAlpha = 1.0f - alpha;
fadeOutAlpha = Math.max(0.0f, fadeOutAlpha);
// We interpolate the alpha of the targetView to 1
- alpha = fadeOutAlpha + alpha;
KeyguardAffordanceView otherView = targetView == mRightIcon ? mLeftIcon : mRightIcon;
- updateIconAlpha(targetView, alpha, false);
- updateIconAlpha(otherView, fadeOutAlpha, false);
- updateIconAlpha(mCenterIcon, fadeOutAlpha, false);
+ updateIconAlpha(targetView, alpha + fadeOutAlpha * targetView.getRestingAlpha(), false);
+ updateIconAlpha(otherView, fadeOutAlpha * otherView.getRestingAlpha(), false);
+ updateIconAlpha(mCenterIcon, fadeOutAlpha * mCenterIcon.getRestingAlpha(), false);
}
private float getTranslationFromRadius(float circleSize) {
- float translation = (circleSize - mMinBackgroundRadius) / BACKGROUND_RADIUS_SCALE_FACTOR;
- return Math.max(0, translation);
+ float translation = (circleSize - mMinBackgroundRadius)
+ / BACKGROUND_RADIUS_SCALE_FACTOR;
+ return translation > 0.0f ? translation + mTouchSlop : 0.0f;
}
private float getRadiusFromTranslation(float translation) {
- return translation * BACKGROUND_RADIUS_SCALE_FACTOR + mMinBackgroundRadius;
+ if (translation <= mTouchSlop) {
+ return 0.0f;
+ }
+ return (translation - mTouchSlop) * BACKGROUND_RADIUS_SCALE_FACTOR + mMinBackgroundRadius;
}
public void animateHideLeftRightIcon() {
+ cancelAnimation();
updateIcon(mRightIcon, 0f, 0f, true, false);
updateIcon(mLeftIcon, 0f, 0f, true, false);
}
@@ -404,14 +460,14 @@ public class KeyguardAffordanceHelper {
}
private void updateIconAlpha(KeyguardAffordanceView view, float alpha, boolean animate) {
- float scale = getScale(alpha);
+ float scale = getScale(alpha, view);
alpha = Math.min(1.0f, alpha);
view.setImageAlpha(alpha, animate);
view.setImageScale(scale, animate);
}
- private float getScale(float alpha) {
- float scale = alpha / SWIPE_RESTING_ALPHA_AMOUNT * 0.2f +
+ private float getScale(float alpha, KeyguardAffordanceView icon) {
+ float scale = alpha / icon.getRestingAlpha() * 0.2f +
KeyguardAffordanceView.MIN_ICON_SCALE_AMOUNT;
return Math.min(scale, KeyguardAffordanceView.MAX_ICON_SCALE_AMOUNT);
}
@@ -429,12 +485,22 @@ public class KeyguardAffordanceHelper {
mVelocityTracker = VelocityTracker.obtain();
}
- private float getCurrentVelocity() {
+ private float getCurrentVelocity(float lastX, float lastY) {
if (mVelocityTracker == null) {
return 0;
}
mVelocityTracker.computeCurrentVelocity(1000);
- return mVelocityTracker.getXVelocity();
+ float aX = mVelocityTracker.getXVelocity();
+ float aY = mVelocityTracker.getYVelocity();
+ float bX = lastX - mInitialTouchX;
+ float bY = lastY - mInitialTouchY;
+ float bLen = (float) Math.hypot(bX, bY);
+ // Project the velocity onto the distance vector: a * b / |b|
+ float projectedVelocity = (aX * bX + aY * bY) / bLen;
+ if (mTargetedView == mRightIcon) {
+ projectedVelocity = -projectedVelocity;
+ }
+ return projectedVelocity;
}
public void onConfigurationChanged() {
@@ -451,7 +517,11 @@ public class KeyguardAffordanceHelper {
mSwipeAnimator.cancel();
}
setTranslation(0.0f, true, animate);
- setSwipingInProgress(false);
+ mMotionCancelled = true;
+ if (mSwipingInProgress) {
+ mCallback.onSwipingAborted();
+ }
+ mSwipingInProgress = false;
}
public interface Callback {
@@ -468,9 +538,13 @@ public class KeyguardAffordanceHelper {
*/
void onAnimationToSideEnded();
- float getPageWidth();
+ float getMaxTranslationDistance();
+
+ void onSwipingStarted(boolean rightIcon);
+
+ void onSwipingAborted();
- void onSwipingStarted();
+ void onIconClicked(boolean rightIcon);
KeyguardAffordanceView getLeftIcon();
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 0c21b20..815e123 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -16,12 +16,17 @@
package com.android.systemui.statusbar.phone;
+import android.app.ActivityManager;
import android.app.ActivityManagerNative;
+import android.app.Application;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
@@ -29,9 +34,13 @@ import android.graphics.drawable.Drawable;
import android.graphics.drawable.InsetDrawable;
import android.os.AsyncTask;
import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.MediaStore;
+import android.service.media.CameraPrewarmService;
import android.telecom.TelecomManager;
import android.util.AttributeSet;
import android.util.Log;
@@ -50,6 +59,7 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.EventLogConstants;
import com.android.systemui.EventLogTags;
import com.android.systemui.R;
+import com.android.systemui.assist.AssistManager;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.KeyguardAffordanceView;
import com.android.systemui.statusbar.KeyguardIndicationController;
@@ -78,14 +88,15 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
private static final Intent PHONE_INTENT = new Intent(Intent.ACTION_DIAL);
private static final int DOZE_ANIMATION_STAGGER_DELAY = 48;
private static final int DOZE_ANIMATION_ELEMENT_DURATION = 250;
+ private static final long TRANSIENT_FP_ERROR_TIMEOUT = 1300;
private KeyguardAffordanceView mCameraImageView;
- private KeyguardAffordanceView mPhoneImageView;
- private KeyguardAffordanceView mLockIcon;
+ private KeyguardAffordanceView mLeftAffordanceView;
+ private LockIcon mLockIcon;
private TextView mIndicationText;
private ViewGroup mPreviewContainer;
- private View mPhonePreview;
+ private View mLeftPreview;
private View mCameraPreview;
private ActivityStarter mActivityStarter;
@@ -97,9 +108,26 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
private AccessibilityController mAccessibilityController;
private PhoneStatusBar mPhoneStatusBar;
- private final TrustDrawable mTrustDrawable;
private final Interpolator mLinearOutSlowInInterpolator;
- private int mLastUnlockIconRes = 0;
+ private boolean mPrewarmBound;
+ private Messenger mPrewarmMessenger;
+ private final ServiceConnection mPrewarmConnection = new ServiceConnection() {
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ mPrewarmMessenger = new Messenger(service);
+ mPrewarmBound = true;
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ mPrewarmBound = false;
+ mPrewarmMessenger = null;
+ }
+ };
+
+ private boolean mLeftIsVoiceAssist;
+ private AssistManager mAssistManager;
public KeyguardBottomAreaView(Context context) {
this(context, null);
@@ -116,7 +144,6 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
public KeyguardBottomAreaView(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- mTrustDrawable = new TrustDrawable(mContext);
mLinearOutSlowInInterpolator =
AnimationUtils.loadInterpolator(context, android.R.interpolator.linear_out_slow_in);
}
@@ -130,8 +157,12 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
label = getResources().getString(R.string.unlock_label);
} else if (host == mCameraImageView) {
label = getResources().getString(R.string.camera_label);
- } else if (host == mPhoneImageView) {
- label = getResources().getString(R.string.phone_label);
+ } else if (host == mLeftAffordanceView) {
+ if (mLeftIsVoiceAssist) {
+ label = getResources().getString(R.string.voice_assist_label);
+ } else {
+ label = getResources().getString(R.string.phone_label);
+ }
}
info.addAction(new AccessibilityAction(ACTION_CLICK, label));
}
@@ -146,8 +177,8 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
} else if (host == mCameraImageView) {
launchCamera();
return true;
- } else if (host == mPhoneImageView) {
- launchPhone();
+ } else if (host == mLeftAffordanceView) {
+ launchLeftAffordance();
return true;
}
}
@@ -161,30 +192,28 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
mLockPatternUtils = new LockPatternUtils(mContext);
mPreviewContainer = (ViewGroup) findViewById(R.id.preview_container);
mCameraImageView = (KeyguardAffordanceView) findViewById(R.id.camera_button);
- mPhoneImageView = (KeyguardAffordanceView) findViewById(R.id.phone_button);
- mLockIcon = (KeyguardAffordanceView) findViewById(R.id.lock_icon);
+ mLeftAffordanceView = (KeyguardAffordanceView) findViewById(R.id.left_button);
+ mLockIcon = (LockIcon) findViewById(R.id.lock_icon);
mIndicationText = (TextView) findViewById(R.id.keyguard_indication_text);
watchForCameraPolicyChanges();
updateCameraVisibility();
- updatePhoneVisibility();
mUnlockMethodCache = UnlockMethodCache.getInstance(getContext());
mUnlockMethodCache.addListener(this);
- updateLockIcon();
+ mLockIcon.update();
setClipChildren(false);
setClipToPadding(false);
mPreviewInflater = new PreviewInflater(mContext, new LockPatternUtils(mContext));
- inflatePreviews();
+ inflateCameraPreview();
mLockIcon.setOnClickListener(this);
- mLockIcon.setBackground(mTrustDrawable);
mLockIcon.setOnLongClickListener(this);
mCameraImageView.setOnClickListener(this);
- mPhoneImageView.setOnClickListener(this);
+ mLeftAffordanceView.setOnClickListener(this);
initAccessibility();
}
private void initAccessibility() {
mLockIcon.setAccessibilityDelegate(mAccessibilityDelegate);
- mPhoneImageView.setAccessibilityDelegate(mAccessibilityDelegate);
+ mLeftAffordanceView.setAccessibilityDelegate(mAccessibilityDelegate);
mCameraImageView.setAccessibilityDelegate(mAccessibilityDelegate);
}
@@ -215,6 +244,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
public void setAccessibilityController(AccessibilityController accessibilityController) {
mAccessibilityController = accessibilityController;
+ mLockIcon.setAccessibilityController(accessibilityController);
accessibilityController.addStateChangedCallback(this);
}
@@ -226,9 +256,9 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
private Intent getCameraIntent() {
KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
boolean currentUserHasTrust = updateMonitor.getUserHasTrust(
- mLockPatternUtils.getCurrentUser());
- return mLockPatternUtils.isSecure() && !currentUserHasTrust
- ? SECURE_CAMERA_INTENT : INSECURE_CAMERA_INTENT;
+ KeyguardUpdateMonitor.getCurrentUser());
+ boolean secure = mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser());
+ return (secure && !currentUserHasTrust) ? SECURE_CAMERA_INTENT : INSECURE_CAMERA_INTENT;
}
private void updateCameraVisibility() {
@@ -238,15 +268,32 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
}
ResolveInfo resolved = mContext.getPackageManager().resolveActivityAsUser(getCameraIntent(),
PackageManager.MATCH_DEFAULT_ONLY,
- mLockPatternUtils.getCurrentUser());
+ KeyguardUpdateMonitor.getCurrentUser());
boolean visible = !isCameraDisabledByDpm() && resolved != null
&& getResources().getBoolean(R.bool.config_keyguardShowCameraAffordance);
mCameraImageView.setVisibility(visible ? View.VISIBLE : View.GONE);
}
- private void updatePhoneVisibility() {
- boolean visible = isPhoneVisible();
- mPhoneImageView.setVisibility(visible ? View.VISIBLE : View.GONE);
+ private void updateLeftAffordanceIcon() {
+ mLeftIsVoiceAssist = canLaunchVoiceAssist();
+ int drawableId;
+ int contentDescription;
+ if (mLeftIsVoiceAssist) {
+ mLeftAffordanceView.setVisibility(View.VISIBLE);
+ drawableId = R.drawable.ic_mic_26dp;
+ contentDescription = R.string.accessibility_voice_assist_button;
+ } else {
+ boolean visible = isPhoneVisible();
+ mLeftAffordanceView.setVisibility(visible ? View.VISIBLE : View.GONE);
+ drawableId = R.drawable.ic_phone_24dp;
+ contentDescription = R.string.accessibility_phone_button;
+ }
+ mLeftAffordanceView.setImageDrawable(mContext.getDrawable(drawableId));
+ mLeftAffordanceView.setContentDescription(mContext.getString(contentDescription));
+ }
+
+ public boolean isLeftVoiceAssist() {
+ return mLeftIsVoiceAssist;
}
private boolean isPhoneVisible() {
@@ -284,32 +331,18 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
@Override
public void onStateChanged(boolean accessibilityEnabled, boolean touchExplorationEnabled) {
mCameraImageView.setClickable(touchExplorationEnabled);
- mPhoneImageView.setClickable(touchExplorationEnabled);
+ mLeftAffordanceView.setClickable(touchExplorationEnabled);
mCameraImageView.setFocusable(accessibilityEnabled);
- mPhoneImageView.setFocusable(accessibilityEnabled);
- updateLockIconClickability();
- }
-
- private void updateLockIconClickability() {
- if (mAccessibilityController == null) {
- return;
- }
- boolean clickToUnlock = mAccessibilityController.isTouchExplorationEnabled();
- boolean clickToForceLock = mUnlockMethodCache.isTrustManaged()
- && !mAccessibilityController.isAccessibilityEnabled();
- boolean longClickToForceLock = mUnlockMethodCache.isTrustManaged()
- && !clickToForceLock;
- mLockIcon.setClickable(clickToForceLock || clickToUnlock);
- mLockIcon.setLongClickable(longClickToForceLock);
- mLockIcon.setFocusable(mAccessibilityController.isAccessibilityEnabled());
+ mLeftAffordanceView.setFocusable(accessibilityEnabled);
+ mLockIcon.update();
}
@Override
public void onClick(View v) {
if (v == mCameraImageView) {
launchCamera();
- } else if (v == mPhoneImageView) {
- launchPhone();
+ } else if (v == mLeftAffordanceView) {
+ launchLeftAffordance();
} if (v == mLockIcon) {
if (!mAccessibilityController.isAccessibilityEnabled()) {
handleTrustCircleClick();
@@ -332,25 +365,124 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
0 /* velocityDp - N/A */);
mIndicationController.showTransientIndication(
R.string.keyguard_indication_trust_disabled);
- mLockPatternUtils.requireCredentialEntry(mLockPatternUtils.getCurrentUser());
+ mLockPatternUtils.requireCredentialEntry(KeyguardUpdateMonitor.getCurrentUser());
}
- public void launchCamera() {
- mFlashlightController.killFlashlight();
+ public void bindCameraPrewarmService() {
Intent intent = getCameraIntent();
+ ActivityInfo targetInfo = PreviewInflater.getTargetActivityInfo(mContext, intent,
+ KeyguardUpdateMonitor.getCurrentUser());
+ if (targetInfo != null) {
+ String clazz = targetInfo.metaData.getString(
+ MediaStore.META_DATA_STILL_IMAGE_CAMERA_PREWARM_SERVICE);
+ if (clazz != null) {
+ Intent serviceIntent = new Intent();
+ serviceIntent.setClassName(targetInfo.packageName, clazz);
+ serviceIntent.setAction(CameraPrewarmService.ACTION_PREWARM);
+ try {
+ getContext().bindServiceAsUser(serviceIntent, mPrewarmConnection,
+ Context.BIND_AUTO_CREATE, new UserHandle(UserHandle.USER_CURRENT));
+ } catch (SecurityException e) {
+ Log.w(TAG, "Unable to bind to prewarm service package=" + targetInfo.packageName
+ + " class=" + clazz, e);
+ }
+ }
+ }
+ }
+
+ public void unbindCameraPrewarmService(boolean launched) {
+ if (mPrewarmBound) {
+ if (launched) {
+ try {
+ mPrewarmMessenger.send(Message.obtain(null /* handler */,
+ CameraPrewarmService.MSG_CAMERA_FIRED));
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error sending camera fired message", e);
+ }
+ }
+ mContext.unbindService(mPrewarmConnection);
+ mPrewarmBound = false;
+ }
+ }
+
+ public void launchCamera() {
+ final Intent intent = getCameraIntent();
boolean wouldLaunchResolverActivity = PreviewInflater.wouldLaunchResolverActivity(
- mContext, intent, mLockPatternUtils.getCurrentUser());
+ mContext, intent, KeyguardUpdateMonitor.getCurrentUser());
if (intent == SECURE_CAMERA_INTENT && !wouldLaunchResolverActivity) {
- mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+ AsyncTask.execute(new Runnable() {
+ @Override
+ public void run() {
+ int result = ActivityManager.START_CANCELED;
+ try {
+ result = ActivityManagerNative.getDefault().startActivityAsUser(
+ null, getContext().getBasePackageName(),
+ intent,
+ intent.resolveTypeIfNeeded(getContext().getContentResolver()),
+ null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null, null,
+ UserHandle.CURRENT.getIdentifier());
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unable to start camera activity", e);
+ }
+ mActivityStarter.preventNextAnimation();
+ final boolean launched = isSuccessfulLaunch(result);
+ post(new Runnable() {
+ @Override
+ public void run() {
+ unbindCameraPrewarmService(launched);
+ }
+ });
+ }
+ });
} else {
// We need to delay starting the activity because ResolverActivity finishes itself if
// launched behind lockscreen.
- mActivityStarter.startActivity(intent, false /* dismissShade */);
+ mActivityStarter.startActivity(intent, false /* dismissShade */,
+ new ActivityStarter.Callback() {
+ @Override
+ public void onActivityStarted(int resultCode) {
+ unbindCameraPrewarmService(isSuccessfulLaunch(resultCode));
+ }
+ });
}
}
- public void launchPhone() {
+ private static boolean isSuccessfulLaunch(int result) {
+ return result == ActivityManager.START_SUCCESS
+ || result == ActivityManager.START_DELIVERED_TO_TOP
+ || result == ActivityManager.START_TASK_TO_FRONT;
+ }
+
+ public void launchLeftAffordance() {
+ if (mLeftIsVoiceAssist) {
+ launchVoiceAssist();
+ } else {
+ launchPhone();
+ }
+ }
+
+ private void launchVoiceAssist() {
+ Runnable runnable = new Runnable() {
+ @Override
+ public void run() {
+ mAssistManager.launchVoiceAssistFromKeyguard();
+ mActivityStarter.preventNextAnimation();
+ }
+ };
+ if (mPhoneStatusBar.isKeyguardCurrentlySecure()) {
+ AsyncTask.execute(runnable);
+ } else {
+ mPhoneStatusBar.executeRunnableDismissingKeyguard(runnable, null /* cancelAction */,
+ false /* dismissShade */, false /* afterKeyguardGone */);
+ }
+ }
+
+ private boolean canLaunchVoiceAssist() {
+ return mAssistManager.canVoiceAssistBeLaunchedFromKeyguard();
+ }
+
+ private void launchPhone() {
final TelecomManager tm = TelecomManager.from(mContext);
if (tm.isInCall()) {
AsyncTask.execute(new Runnable() {
@@ -368,69 +500,25 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
@Override
protected void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
- if (isShown()) {
- mTrustDrawable.start();
- } else {
- mTrustDrawable.stop();
- }
if (changedView == this && visibility == VISIBLE) {
- updateLockIcon();
+ mLockIcon.update();
updateCameraVisibility();
}
}
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- mTrustDrawable.stop();
+ public KeyguardAffordanceView getLeftView() {
+ return mLeftAffordanceView;
}
- private void updateLockIcon() {
- boolean visible = isShown() && KeyguardUpdateMonitor.getInstance(mContext).isScreenOn();
- if (visible) {
- mTrustDrawable.start();
- } else {
- mTrustDrawable.stop();
- }
- if (!visible) {
- return;
- }
- // TODO: Real icon for facelock.
- int iconRes = mUnlockMethodCache.isFaceUnlockRunning()
- ? com.android.internal.R.drawable.ic_account_circle
- : mUnlockMethodCache.isCurrentlyInsecure() ? R.drawable.ic_lock_open_24dp
- : R.drawable.ic_lock_24dp;
- if (mLastUnlockIconRes != iconRes) {
- Drawable icon = mContext.getDrawable(iconRes);
- int iconHeight = getResources().getDimensionPixelSize(
- R.dimen.keyguard_affordance_icon_height);
- int iconWidth = getResources().getDimensionPixelSize(
- R.dimen.keyguard_affordance_icon_width);
- if (icon.getIntrinsicHeight() != iconHeight || icon.getIntrinsicWidth() != iconWidth) {
- icon = new IntrinsicSizeDrawable(icon, iconWidth, iconHeight);
- }
- mLockIcon.setImageDrawable(icon);
- }
- boolean trustManaged = mUnlockMethodCache.isTrustManaged();
- mTrustDrawable.setTrustManaged(trustManaged);
- updateLockIconClickability();
- }
-
-
-
- public KeyguardAffordanceView getPhoneView() {
- return mPhoneImageView;
- }
-
- public KeyguardAffordanceView getCameraView() {
+ public KeyguardAffordanceView getRightView() {
return mCameraImageView;
}
- public View getPhonePreview() {
- return mPhonePreview;
+ public View getLeftPreview() {
+ return mLeftPreview;
}
- public View getCameraPreview() {
+ public View getRightPreview() {
return mCameraPreview;
}
@@ -449,27 +537,39 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
@Override
public void onUnlockMethodStateChanged() {
- updateLockIcon();
+ mLockIcon.update();
updateCameraVisibility();
}
- private void inflatePreviews() {
- mPhonePreview = mPreviewInflater.inflatePreview(PHONE_INTENT);
+ private void inflateCameraPreview() {
mCameraPreview = mPreviewInflater.inflatePreview(getCameraIntent());
- if (mPhonePreview != null) {
- mPreviewContainer.addView(mPhonePreview);
- mPhonePreview.setVisibility(View.INVISIBLE);
- }
if (mCameraPreview != null) {
mPreviewContainer.addView(mCameraPreview);
mCameraPreview.setVisibility(View.INVISIBLE);
}
}
+ private void updateLeftPreview() {
+ View previewBefore = mLeftPreview;
+ if (previewBefore != null) {
+ mPreviewContainer.removeView(previewBefore);
+ }
+ if (mLeftIsVoiceAssist) {
+ mLeftPreview = mPreviewInflater.inflatePreviewFromService(
+ mAssistManager.getVoiceInteractorComponentName());
+ } else {
+ mLeftPreview = mPreviewInflater.inflatePreview(PHONE_INTENT);
+ }
+ if (mLeftPreview != null) {
+ mPreviewContainer.addView(mLeftPreview);
+ mLeftPreview.setVisibility(View.INVISIBLE);
+ }
+ }
+
public void startFinishDozeAnimation() {
long delay = 0;
- if (mPhoneImageView.getVisibility() == View.VISIBLE) {
- startFinishDozeAnimationElement(mPhoneImageView, delay);
+ if (mLeftAffordanceView.getVisibility() == View.VISIBLE) {
+ startFinishDozeAnimationElement(mLeftAffordanceView, delay);
delay += DOZE_ANIMATION_STAGGER_DELAY;
}
startFinishDozeAnimationElement(mLockIcon, delay);
@@ -506,6 +606,21 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
}
};
+ private final Runnable mTransientFpErrorClearRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mLockIcon.setTransientFpError(false);
+ mIndicationController.hideTransientIndication();
+ }
+ };
+
+ private final Runnable mHideTransientIndicationRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mIndicationController.hideTransientIndication();
+ }
+ };
+
private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback =
new KeyguardUpdateMonitorCallback() {
@Override
@@ -515,48 +630,59 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
@Override
public void onScreenTurnedOn() {
- updateLockIcon();
+ mLockIcon.setScreenOn(true);
}
@Override
public void onScreenTurnedOff(int why) {
- updateLockIcon();
+ mLockIcon.setScreenOn(false);
}
@Override
public void onKeyguardVisibilityChanged(boolean showing) {
- updateLockIcon();
+ mLockIcon.update();
}
- };
-
- public void setKeyguardIndicationController(
- KeyguardIndicationController keyguardIndicationController) {
- mIndicationController = keyguardIndicationController;
- }
-
-
- /**
- * A wrapper around another Drawable that overrides the intrinsic size.
- */
- private static class IntrinsicSizeDrawable extends InsetDrawable {
- private final int mIntrinsicWidth;
- private final int mIntrinsicHeight;
+ @Override
+ public void onFingerprintAuthenticated(int userId) {
+ }
- public IntrinsicSizeDrawable(Drawable drawable, int intrinsicWidth, int intrinsicHeight) {
- super(drawable, 0);
- mIntrinsicWidth = intrinsicWidth;
- mIntrinsicHeight = intrinsicHeight;
+ @Override
+ public void onFingerprintRunningStateChanged(boolean running) {
+ mLockIcon.update();
}
@Override
- public int getIntrinsicWidth() {
- return mIntrinsicWidth;
+ public void onFingerprintHelp(int msgId, String helpString) {
+ mLockIcon.setTransientFpError(true);
+ mIndicationController.showTransientIndication(helpString,
+ getResources().getColor(R.color.system_warning_color, null));
+ removeCallbacks(mTransientFpErrorClearRunnable);
+ postDelayed(mTransientFpErrorClearRunnable, TRANSIENT_FP_ERROR_TIMEOUT);
}
@Override
- public int getIntrinsicHeight() {
- return mIntrinsicHeight;
+ public void onFingerprintError(int msgId, String errString) {
+ // TODO: Go to bouncer if this is "too many attempts" (lockout) error.
+ mIndicationController.showTransientIndication(errString,
+ getResources().getColor(R.color.system_warning_color, null));
+ removeCallbacks(mHideTransientIndicationRunnable);
+ postDelayed(mHideTransientIndicationRunnable, 5000);
}
+ };
+
+ public void setKeyguardIndicationController(
+ KeyguardIndicationController keyguardIndicationController) {
+ mIndicationController = keyguardIndicationController;
+ }
+
+ public void setAssistManager(AssistManager assistManager) {
+ mAssistManager = assistManager;
+ updateLeftAffordance();
+ }
+
+ public void updateLeftAffordance() {
+ updateLeftAffordanceIcon();
+ updateLeftPreview();
}
}
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 d0fe32e..a7afec4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -25,10 +25,10 @@ import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import com.android.internal.widget.LockPatternUtils;
-import com.android.keyguard.KeyguardViewBase;
+import com.android.keyguard.KeyguardHostView;
+import com.android.keyguard.KeyguardSecurityView;
import com.android.keyguard.R;
import com.android.keyguard.ViewMediatorCallback;
-import com.android.systemui.keyguard.KeyguardViewMediator;
import static com.android.keyguard.KeyguardHostView.OnDismissAction;
import static com.android.keyguard.KeyguardSecurityModel.SecurityMode;
@@ -43,10 +43,11 @@ public class KeyguardBouncer {
private LockPatternUtils mLockPatternUtils;
private ViewGroup mContainer;
private StatusBarWindowManager mWindowManager;
- private KeyguardViewBase mKeyguardView;
+ private KeyguardHostView mKeyguardView;
private ViewGroup mRoot;
private boolean mShowingSoon;
private Choreographer mChoreographer = Choreographer.getInstance();
+ private int mBouncerPromptReason;
public KeyguardBouncer(Context context, ViewMediatorCallback callback,
LockPatternUtils lockPatternUtils, StatusBarWindowManager windowManager,
@@ -69,6 +70,8 @@ public class KeyguardBouncer {
return;
}
+ mBouncerPromptReason = mCallback.getBouncerPromptReason();
+
// Try to dismiss the Keyguard. If no security pattern is set, this will dismiss the whole
// Keyguard. If we need to authenticate, show the bouncer.
if (!mKeyguardView.dismiss()) {
@@ -85,27 +88,39 @@ public class KeyguardBouncer {
public void run() {
mRoot.setVisibility(View.VISIBLE);
mKeyguardView.onResume();
+ showPromptReason(mBouncerPromptReason);
mKeyguardView.startAppearAnimation();
mShowingSoon = false;
mKeyguardView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
}
};
+ /**
+ * Show a string explaining why the security view needs to be solved.
+ *
+ * @param reason a flag indicating which string should be shown, see
+ * {@link KeyguardSecurityView#PROMPT_REASON_NONE}
+ * and {@link KeyguardSecurityView#PROMPT_REASON_RESTART}
+ */
+ public void showPromptReason(int reason) {
+ mKeyguardView.showPromptReason(reason);
+ }
+
private void cancelShowRunnable() {
mChoreographer.removeCallbacks(Choreographer.CALLBACK_ANIMATION, mShowRunnable, null);
mShowingSoon = false;
}
- public void showWithDismissAction(OnDismissAction r) {
+ public void showWithDismissAction(OnDismissAction r, Runnable cancelAction) {
ensureView();
- mKeyguardView.setOnDismissAction(r);
+ mKeyguardView.setOnDismissAction(r, cancelAction);
show(false /* resetSecuritySelection */);
}
public void hide(boolean destroyView) {
cancelShowRunnable();
if (mKeyguardView != null) {
- mKeyguardView.setOnDismissAction(null);
+ mKeyguardView.cancelDismissAction();
mKeyguardView.cleanUp();
}
if (destroyView) {
@@ -140,16 +155,6 @@ public class KeyguardBouncer {
}
}
- public long getUserActivityTimeout() {
- if (mKeyguardView != null) {
- long timeout = mKeyguardView.getUserActivityTimeout();
- if (timeout >= 0) {
- return timeout;
- }
- }
- return KeyguardViewMediator.AWAKE_INTERVAL_DEFAULT_MS;
- }
-
public boolean isShowing() {
return mShowingSoon || (mRoot != null && mRoot.getVisibility() == View.VISIBLE);
}
@@ -171,7 +176,7 @@ public class KeyguardBouncer {
private void inflateView() {
removeView();
mRoot = (ViewGroup) LayoutInflater.from(mContext).inflate(R.layout.keyguard_bouncer, null);
- mKeyguardView = (KeyguardViewBase) mRoot.findViewById(R.id.keyguard_host_view);
+ mKeyguardView = (KeyguardHostView) mRoot.findViewById(R.id.keyguard_host_view);
mKeyguardView.setLockPatternUtils(mLockPatternUtils);
mKeyguardView.setViewMediatorCallback(mCallback);
mContainer.addView(mRoot, mContainer.getChildCount());
@@ -195,6 +200,7 @@ public class KeyguardBouncer {
* notifications on Keyguard, like SIM PIN/PUK.
*/
public boolean needsFullscreenBouncer() {
+ ensureView();
if (mKeyguardView != null) {
SecurityMode mode = mKeyguardView.getSecurityMode();
return mode == SecurityMode.SimPin || mode == SecurityMode.SimPuk;
@@ -239,4 +245,9 @@ public class KeyguardBouncer {
ensureView();
return mKeyguardView.interceptMediaKey(event);
}
+
+ public void notifyKeyguardAuthenticated() {
+ ensureView();
+ mKeyguardView.finish();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPreviewContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPreviewContainer.java
index 7579039..076e5f1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPreviewContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPreviewContainer.java
@@ -20,7 +20,6 @@ import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
-import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.WindowInsets;
@@ -47,7 +46,7 @@ public class KeyguardPreviewContainer extends FrameLayout {
}
@Override
- public void setColorFilter(ColorFilter cf) {
+ public void setColorFilter(ColorFilter colorFilter) {
// noop
}
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 40c9134..b93fc76 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.phone;
-import android.animation.LayoutTransition;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.drawable.Drawable;
@@ -35,6 +34,7 @@ import com.android.systemui.R;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.statusbar.policy.UserSwitcherController;
import java.text.NumberFormat;
@@ -141,6 +141,10 @@ public class KeyguardStatusBarView extends RelativeLayout
((BatteryMeterView) findViewById(R.id.battery)).setBatteryController(batteryController);
}
+ public void setUserSwitcherController(UserSwitcherController controller) {
+ mMultiUserSwitch.setUserSwitcherController(controller);
+ }
+
public void setUserInfoController(UserInfoController userInfoController) {
userInfoController.addListener(new UserInfoController.OnUserInfoChangedListener() {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
new file mode 100644
index 0000000..6bcb766
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
@@ -0,0 +1,266 @@
+/*
+ * 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.statusbar.phone;
+
+import android.content.Context;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.InsetDrawable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.KeyguardAffordanceView;
+import com.android.systemui.statusbar.policy.AccessibilityController;
+
+/**
+ * Manages the different states and animations of the unlock icon.
+ */
+public class LockIcon extends KeyguardAffordanceView {
+
+ /**
+ * Delay animations a bit when the screen just turned on as a heuristic to start them after
+ * the screen has actually turned on.
+ */
+ private static final long ANIM_DELAY_AFTER_SCREEN_ON = 250;
+
+ private static final int STATE_LOCKED = 0;
+ private static final int STATE_LOCK_OPEN = 1;
+ private static final int STATE_FACE_UNLOCK = 2;
+ private static final int STATE_FINGERPRINT = 3;
+ private static final int STATE_FINGERPRINT_ERROR = 4;
+
+ private int mLastState = 0;
+ private boolean mLastScreenOn;
+ private boolean mTransientFpError;
+ private boolean mScreenOn;
+ private final TrustDrawable mTrustDrawable;
+ private final UnlockMethodCache mUnlockMethodCache;
+ private AccessibilityController mAccessibilityController;
+ private boolean mHasFingerPrintIcon;
+
+ public LockIcon(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mTrustDrawable = new TrustDrawable(context);
+ setBackground(mTrustDrawable);
+ mUnlockMethodCache = UnlockMethodCache.getInstance(context);
+ }
+
+ @Override
+ protected void onVisibilityChanged(View changedView, int visibility) {
+ super.onVisibilityChanged(changedView, visibility);
+ if (isShown()) {
+ mTrustDrawable.start();
+ } else {
+ mTrustDrawable.stop();
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mTrustDrawable.stop();
+ }
+
+ public void setTransientFpError(boolean transientFpError) {
+ mTransientFpError = transientFpError;
+ update();
+ }
+
+ public void setScreenOn(boolean screenOn) {
+ mScreenOn = screenOn;
+ update();
+ }
+
+ public void update() {
+ boolean visible = isShown() && KeyguardUpdateMonitor.getInstance(mContext).isScreenOn();
+ if (visible) {
+ mTrustDrawable.start();
+ } else {
+ mTrustDrawable.stop();
+ }
+ if (!visible) {
+ return;
+ }
+ // TODO: Real icon for facelock.
+ int state = getState();
+ boolean anyFingerprintIcon = state == STATE_FINGERPRINT || state == STATE_FINGERPRINT_ERROR;
+ if (state != mLastState || mScreenOn != mLastScreenOn) {
+ int iconRes = getAnimationResForTransition(mLastState, state, mLastScreenOn, mScreenOn);
+ if (iconRes == R.drawable.lockscreen_fingerprint_draw_off_animation) {
+ anyFingerprintIcon = true;
+ }
+ if (iconRes == -1) {
+ iconRes = getIconForState(state);
+ }
+ Drawable icon = mContext.getDrawable(iconRes);
+ final AnimatedVectorDrawable animation = icon instanceof AnimatedVectorDrawable
+ ? (AnimatedVectorDrawable) icon
+ : null;
+ int iconHeight = getResources().getDimensionPixelSize(
+ R.dimen.keyguard_affordance_icon_height);
+ int iconWidth = getResources().getDimensionPixelSize(
+ R.dimen.keyguard_affordance_icon_width);
+ if (!anyFingerprintIcon && (icon.getIntrinsicHeight() != iconHeight
+ || icon.getIntrinsicWidth() != iconWidth)) {
+ icon = new IntrinsicSizeDrawable(icon, iconWidth, iconHeight);
+ }
+ setPaddingRelative(0, 0, 0, anyFingerprintIcon
+ ? getResources().getDimensionPixelSize(
+ R.dimen.fingerprint_icon_additional_padding)
+ : 0);
+ setRestingAlpha(
+ anyFingerprintIcon ? 1f : KeyguardAffordanceHelper.SWIPE_RESTING_ALPHA_AMOUNT);
+ setImageDrawable(icon);
+ String contentDescription = getResources().getString(anyFingerprintIcon
+ ? R.string.accessibility_unlock_button_fingerprint
+ : R.string.accessibility_unlock_button);
+ setContentDescription(contentDescription);
+ mHasFingerPrintIcon = anyFingerprintIcon;
+ if (animation != null) {
+
+ // If we play the draw on animation, delay it by one frame when the screen is
+ // actually turned on.
+ if (iconRes == R.drawable.lockscreen_fingerprint_draw_on_animation) {
+ postOnAnimationDelayed(new Runnable() {
+ @Override
+ public void run() {
+ animation.start();
+ }
+ }, ANIM_DELAY_AFTER_SCREEN_ON);
+ } else {
+ animation.start();
+ }
+ }
+ mLastState = state;
+ mLastScreenOn = mScreenOn;
+ }
+
+ // Hide trust circle when fingerprint is running.
+ boolean trustManaged = mUnlockMethodCache.isTrustManaged() && !anyFingerprintIcon;
+ mTrustDrawable.setTrustManaged(trustManaged);
+ updateClickability();
+ }
+
+ private void updateClickability() {
+ if (mAccessibilityController == null) {
+ return;
+ }
+ boolean clickToUnlock = mAccessibilityController.isTouchExplorationEnabled();
+ boolean clickToForceLock = mUnlockMethodCache.isTrustManaged()
+ && !mAccessibilityController.isAccessibilityEnabled();
+ boolean longClickToForceLock = mUnlockMethodCache.isTrustManaged()
+ && !clickToForceLock;
+ setClickable(clickToForceLock || clickToUnlock);
+ setLongClickable(longClickToForceLock);
+ setFocusable(mAccessibilityController.isAccessibilityEnabled());
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ if (mHasFingerPrintIcon) {
+ // Avoid that the button description is also spoken
+ info.setClassName(LockIcon.class.getName());
+ AccessibilityNodeInfo.AccessibilityAction unlock
+ = new AccessibilityNodeInfo.AccessibilityAction(
+ AccessibilityNodeInfo.ACTION_CLICK,
+ getContext().getString(R.string.accessibility_unlock_without_fingerprint));
+ info.addAction(unlock);
+ }
+ }
+
+ public void setAccessibilityController(AccessibilityController accessibilityController) {
+ mAccessibilityController = accessibilityController;
+ }
+
+ private int getIconForState(int state) {
+ switch (state) {
+ case STATE_LOCKED:
+ return R.drawable.ic_lock_24dp;
+ case STATE_LOCK_OPEN:
+ return R.drawable.ic_lock_open_24dp;
+ case STATE_FACE_UNLOCK:
+ return com.android.internal.R.drawable.ic_account_circle;
+ case STATE_FINGERPRINT:
+ return R.drawable.ic_fingerprint;
+ case STATE_FINGERPRINT_ERROR:
+ return R.drawable.ic_fingerprint_error;
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+
+ private int getAnimationResForTransition(int oldState, int newState, boolean oldScreenOn,
+ boolean screenOn) {
+ if (oldState == STATE_FINGERPRINT && newState == STATE_FINGERPRINT_ERROR) {
+ return R.drawable.lockscreen_fingerprint_fp_to_error_state_animation;
+ } else if (oldState == STATE_FINGERPRINT_ERROR && newState == STATE_FINGERPRINT) {
+ return R.drawable.lockscreen_fingerprint_error_state_to_fp_animation;
+ } else if (oldState == STATE_FINGERPRINT && newState == STATE_LOCK_OPEN
+ && !mUnlockMethodCache.isCurrentlyInsecure()) {
+ return R.drawable.lockscreen_fingerprint_draw_off_animation;
+ } else if (newState == STATE_FINGERPRINT && !oldScreenOn && screenOn) {
+ return R.drawable.lockscreen_fingerprint_draw_on_animation;
+ } else {
+ return -1;
+ }
+ }
+
+ private int getState() {
+ boolean fingerprintRunning =
+ KeyguardUpdateMonitor.getInstance(mContext).isFingerprintDetectionRunning();
+ if (mUnlockMethodCache.isCurrentlyInsecure()) {
+ return STATE_LOCK_OPEN;
+ } else if (mTransientFpError) {
+ return STATE_FINGERPRINT_ERROR;
+ } else if (fingerprintRunning) {
+ return STATE_FINGERPRINT;
+ } else if (mUnlockMethodCache.isFaceUnlockRunning()) {
+ return STATE_FACE_UNLOCK;
+ } else {
+ return STATE_LOCKED;
+ }
+ }
+
+ /**
+ * A wrapper around another Drawable that overrides the intrinsic size.
+ */
+ private static class IntrinsicSizeDrawable extends InsetDrawable {
+
+ private final int mIntrinsicWidth;
+ private final int mIntrinsicHeight;
+
+ public IntrinsicSizeDrawable(Drawable drawable, int intrinsicWidth, int intrinsicHeight) {
+ super(drawable, 0);
+ mIntrinsicWidth = intrinsicWidth;
+ mIntrinsicHeight = intrinsicHeight;
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ return mIntrinsicWidth;
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ return mIntrinsicHeight;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
index 82f5a9e..e70d146 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
@@ -24,7 +24,7 @@ import android.provider.ContactsContract;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
-import android.view.accessibility.AccessibilityEvent;
+import android.view.ViewGroup;
import android.widget.FrameLayout;
import com.android.systemui.R;
@@ -40,8 +40,14 @@ public class MultiUserSwitch extends FrameLayout implements View.OnClickListener
private QSPanel mQsPanel;
private KeyguardUserSwitcher mKeyguardUserSwitcher;
private boolean mKeyguardMode;
+ private UserSwitcherController.BaseUserAdapter mUserListener;
+
final UserManager mUserManager;
+ private final int[] mTmpInt2 = new int[2];
+
+ private UserSwitcherController mUserSwitcherController;
+
public MultiUserSwitch(Context context, AttributeSet attrs) {
super(context, attrs);
mUserManager = UserManager.get(getContext());
@@ -51,10 +57,18 @@ public class MultiUserSwitch extends FrameLayout implements View.OnClickListener
protected void onFinishInflate() {
super.onFinishInflate();
setOnClickListener(this);
+ refreshContentDescription();
}
public void setQsPanel(QSPanel qsPanel) {
mQsPanel = qsPanel;
+ setUserSwitcherController(qsPanel.getHost().getUserSwitcherController());
+ }
+
+ public void setUserSwitcherController(UserSwitcherController userSwitcherController) {
+ mUserSwitcherController = userSwitcherController;
+ registerListener();
+ refreshContentDescription();
}
public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) {
@@ -63,6 +77,28 @@ public class MultiUserSwitch extends FrameLayout implements View.OnClickListener
public void setKeyguardMode(boolean keyguardShowing) {
mKeyguardMode = keyguardShowing;
+ registerListener();
+ }
+
+ private void registerListener() {
+ if (UserSwitcherController.isUserSwitcherAvailable(mUserManager) && mUserListener == null) {
+
+ final UserSwitcherController controller = mUserSwitcherController;
+ if (controller != null) {
+ mUserListener = new UserSwitcherController.BaseUserAdapter(controller) {
+ @Override
+ public void notifyDataSetChanged() {
+ refreshContentDescription();
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ return null;
+ }
+ };
+ refreshContentDescription();
+ }
+ }
}
@Override
@@ -72,14 +108,16 @@ public class MultiUserSwitch extends FrameLayout implements View.OnClickListener
if (mKeyguardUserSwitcher != null) {
mKeyguardUserSwitcher.show(true /* animate */);
}
- } else {
- if (mQsPanel != null) {
- UserSwitcherController userSwitcherController =
- mQsPanel.getHost().getUserSwitcherController();
- if (userSwitcherController != null) {
- mQsPanel.showDetailAdapter(true, userSwitcherController.userDetailAdapter);
- }
- }
+ } else if (mQsPanel != null && mUserSwitcherController != null) {
+ View center = getChildCount() > 0 ? getChildAt(0) : this;
+
+ center.getLocationInWindow(mTmpInt2);
+ mTmpInt2[0] += center.getWidth() / 2;
+ mTmpInt2[1] += center.getHeight() / 2;
+
+ mQsPanel.showDetailAdapter(true,
+ mUserSwitcherController.userDetailAdapter,
+ mTmpInt2);
}
} else {
Intent intent = ContactsContract.QuickContact.composeQuickContactsIntent(
@@ -90,20 +128,21 @@ public class MultiUserSwitch extends FrameLayout implements View.OnClickListener
}
@Override
- public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
- super.onPopulateAccessibilityEvent(event);
+ public void setClickable(boolean clickable) {
+ super.setClickable(clickable);
+ refreshContentDescription();
+ }
+ private void refreshContentDescription() {
+ String currentUser = null;
+ if (UserSwitcherController.isUserSwitcherAvailable(mUserManager)
+ && mUserSwitcherController != null) {
+ currentUser = mUserSwitcherController.getCurrentUserName(mContext);
+ }
+
+ String text = null;
if (isClickable()) {
- String text;
if (UserSwitcherController.isUserSwitcherAvailable(mUserManager)) {
- String currentUser = null;
- if (mQsPanel != null) {
- UserSwitcherController controller = mQsPanel.getHost()
- .getUserSwitcherController();
- if (controller != null) {
- currentUser = controller.getCurrentUserName(mContext);
- }
- }
if (TextUtils.isEmpty(currentUser)) {
text = mContext.getString(R.string.accessibility_multi_user_switch_switcher);
} else {
@@ -114,11 +153,17 @@ public class MultiUserSwitch extends FrameLayout implements View.OnClickListener
} else {
text = mContext.getString(R.string.accessibility_multi_user_switch_quick_contact);
}
- if (!TextUtils.isEmpty(text)) {
- event.getText().add(text);
+ } else {
+ if (!TextUtils.isEmpty(currentUser)) {
+ text = mContext.getString(
+ R.string.accessibility_multi_user_switch_inactive,
+ currentUser);
}
}
+ if (!TextUtils.equals(getContentDescription(), text)) {
+ setContentDescription(text);
+ }
}
@Override
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 7ec84da..134c579 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
@@ -26,18 +26,13 @@ import android.view.animation.AccelerateInterpolator;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.R;
-import com.android.systemui.statusbar.policy.KeyButtonView;
public final class NavigationBarTransitions extends BarTransitions {
- private static final int CONTENT_FADE_DURATION = 200;
-
private final NavigationBarView mView;
private final IStatusBarService mBarService;
private boolean mLightsOut;
- private boolean mVertical;
- private int mRequestedMode;
public NavigationBarTransitions(NavigationBarView view) {
super(view, R.drawable.nav_background);
@@ -46,31 +41,11 @@ public final class NavigationBarTransitions extends BarTransitions {
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
}
- public void init(boolean isVertical) {
- setVertical(isVertical);
+ public void init() {
applyModeBackground(-1, getMode(), false /*animate*/);
applyMode(getMode(), false /*animate*/, true /*force*/);
}
- public void setVertical(boolean isVertical) {
- mVertical = isVertical;
- transitionTo(mRequestedMode, false /*animate*/);
- }
-
- @Override
- public void transitionTo(int mode, boolean animate) {
- mRequestedMode = mode;
- if (mVertical) {
- // translucent mode not allowed when vertical
- if (mode == MODE_TRANSLUCENT || mode == MODE_TRANSPARENT) {
- mode = MODE_OPAQUE;
- } else if (mode == MODE_LIGHTS_OUT_TRANSPARENT) {
- mode = MODE_LIGHTS_OUT;
- }
- }
- super.transitionTo(mode, animate);
- }
-
@Override
protected void onTransition(int oldMode, int newMode, boolean animate) {
super.onTransition(oldMode, newMode, animate);
@@ -78,48 +53,11 @@ public final class NavigationBarTransitions extends BarTransitions {
}
private void applyMode(int mode, boolean animate, boolean force) {
- // apply to key buttons
- final float alpha = alphaForMode(mode);
- setKeyButtonViewQuiescentAlpha(mView.getHomeButton(), alpha, animate);
- setKeyButtonViewQuiescentAlpha(mView.getRecentsButton(), alpha, animate);
- setKeyButtonViewQuiescentAlpha(mView.getMenuButton(), alpha, animate);
- setKeyButtonViewQuiescentAlpha(mView.getImeSwitchButton(), alpha, animate);
-
- applyBackButtonQuiescentAlpha(mode, animate);
// apply to lights out
applyLightsOut(isLightsOut(mode), animate, force);
}
- private float alphaForMode(int mode) {
- final boolean isOpaque = mode == MODE_OPAQUE || mode == MODE_LIGHTS_OUT;
- return isOpaque ? KeyButtonView.DEFAULT_QUIESCENT_ALPHA : 1f;
- }
-
- public void applyBackButtonQuiescentAlpha(int mode, boolean animate) {
- float backAlpha = 0;
- backAlpha = maxVisibleQuiescentAlpha(backAlpha, mView.getHomeButton());
- backAlpha = maxVisibleQuiescentAlpha(backAlpha, mView.getRecentsButton());
- backAlpha = maxVisibleQuiescentAlpha(backAlpha, mView.getMenuButton());
- backAlpha = maxVisibleQuiescentAlpha(backAlpha, mView.getImeSwitchButton());
- if (backAlpha > 0) {
- setKeyButtonViewQuiescentAlpha(mView.getBackButton(), backAlpha, animate);
- }
- }
-
- private static float maxVisibleQuiescentAlpha(float max, View v) {
- if ((v instanceof KeyButtonView) && v.isShown()) {
- return Math.max(max, ((KeyButtonView)v).getQuiescentAlpha());
- }
- return max;
- }
-
- private void setKeyButtonViewQuiescentAlpha(View button, float alpha, boolean animate) {
- if (button instanceof KeyButtonView) {
- ((KeyButtonView) button).setQuiescentAlpha(alpha, animate);
- }
- }
-
private void applyLightsOut(boolean lightsOut, boolean animate, boolean force) {
if (!force && lightsOut == mLightsOut) return;
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 1e4dfb4..f40f501 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -47,8 +47,6 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import com.android.systemui.R;
-import com.android.systemui.statusbar.BaseStatusBar;
-import com.android.systemui.statusbar.DelegateViewHelper;
import com.android.systemui.statusbar.policy.DeadZone;
import com.android.systemui.statusbar.policy.KeyButtonView;
@@ -80,7 +78,6 @@ public class NavigationBarView extends LinearLayout {
private Drawable mRecentLandIcon;
private NavigationBarViewTaskSwitchHelper mTaskSwitchHelper;
- private DelegateViewHelper mDelegateHelper;
private DeadZone mDeadZone;
private final NavigationBarTransitions mBarTransitions;
@@ -93,7 +90,6 @@ public class NavigationBarView extends LinearLayout {
private OnVerticalChangedListener mOnVerticalChangedListener;
private boolean mIsLayoutRtl;
- private boolean mDelegateIntercepted;
private class NavTransitionListener implements TransitionListener {
private boolean mBackTransitioning;
@@ -144,7 +140,7 @@ public class NavigationBarView extends LinearLayout {
@Override
public void onClick(View view) {
((InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE))
- .showInputMethodPicker();
+ .showInputMethodPicker(true /* showAuxiliarySubtypes */);
}
};
@@ -181,7 +177,6 @@ public class NavigationBarView extends LinearLayout {
mBarSize = res.getDimensionPixelSize(R.dimen.navigation_bar_size);
mVertical = false;
mShowMenu = false;
- mDelegateHelper = new DelegateViewHelper(this);
mTaskSwitchHelper = new NavigationBarViewTaskSwitchHelper(context);
getIcons(res);
@@ -193,13 +188,8 @@ public class NavigationBarView extends LinearLayout {
return mBarTransitions;
}
- public void setDelegateView(View view) {
- mDelegateHelper.setDelegateView(view);
- }
-
- public void setBar(BaseStatusBar phoneStatusBar) {
+ public void setBar(PhoneStatusBar phoneStatusBar) {
mTaskSwitchHelper.setBar(phoneStatusBar);
- mDelegateHelper.setBar(phoneStatusBar);
}
public void setOnVerticalChangedListener(OnVerticalChangedListener onVerticalChangedListener) {
@@ -209,40 +199,22 @@ public class NavigationBarView extends LinearLayout {
@Override
public boolean onTouchEvent(MotionEvent event) {
- initDownStates(event);
- if (!mDelegateIntercepted && mTaskSwitchHelper.onTouchEvent(event)) {
+ if (mTaskSwitchHelper.onTouchEvent(event)) {
return true;
}
if (mDeadZone != null && event.getAction() == MotionEvent.ACTION_OUTSIDE) {
mDeadZone.poke(event);
}
- if (mDelegateHelper != null && mDelegateIntercepted) {
- boolean ret = mDelegateHelper.onInterceptTouchEvent(event);
- if (ret) return true;
- }
return super.onTouchEvent(event);
}
- private void initDownStates(MotionEvent ev) {
- if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- mDelegateIntercepted = false;
- }
- }
-
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
- initDownStates(event);
- boolean intercept = mTaskSwitchHelper.onInterceptTouchEvent(event);
- if (!intercept) {
- mDelegateIntercepted = mDelegateHelper.onInterceptTouchEvent(event);
- intercept = mDelegateIntercepted;
- } else {
- MotionEvent cancelEvent = MotionEvent.obtain(event);
- cancelEvent.setAction(MotionEvent.ACTION_CANCEL);
- mDelegateHelper.onInterceptTouchEvent(cancelEvent);
- cancelEvent.recycle();
- }
- return intercept;
+ return mTaskSwitchHelper.onInterceptTouchEvent(event);
+ }
+
+ public void abortCurrentGesture() {
+ getHomeButton().abortCurrentGesture();
}
private H mHandler = new H();
@@ -263,8 +235,8 @@ public class NavigationBarView extends LinearLayout {
return mCurrentView.findViewById(R.id.back);
}
- public View getHomeButton() {
- return mCurrentView.findViewById(R.id.home);
+ public KeyButtonView getHomeButton() {
+ return (KeyButtonView) mCurrentView.findViewById(R.id.home);
}
public View getImeSwitchButton() {
@@ -369,8 +341,6 @@ public class NavigationBarView extends LinearLayout {
getBackButton() .setVisibility(disableBack ? View.INVISIBLE : View.VISIBLE);
getHomeButton() .setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE);
getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE);
-
- mBarTransitions.applyBackButtonQuiescentAlpha(mBarTransitions.getMode(), true /*animate*/);
}
private boolean inLockTask() {
@@ -451,7 +421,7 @@ public class NavigationBarView extends LinearLayout {
mDeadZone = (DeadZone) mCurrentView.findViewById(R.id.deadzone);
// force the low profile & disabled states into compliance
- mBarTransitions.init(mVertical);
+ mBarTransitions.init();
setDisabledFlags(mDisabledFlags, true /* force */);
setMenuVisibility(mShowMenu, true /* force */);
@@ -459,10 +429,6 @@ public class NavigationBarView extends LinearLayout {
Log.d(TAG, "reorient(): rot=" + mDisplay.getRotation());
}
- // swap to x coordinate if orientation is not in vertical
- if (mDelegateHelper != null) {
- mDelegateHelper.setSwapXY(mVertical);
- }
updateTaskSwitchHelper();
setNavigationIconHints(mNavigationIconHints, true);
@@ -474,12 +440,6 @@ public class NavigationBarView extends LinearLayout {
}
@Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- super.onLayout(changed, l, t, r, b);
- mDelegateHelper.setInitialTouchRegion(getHomeButton(), getBackButton(), getRecentsButton());
- }
-
- @Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
if (DEBUG) Log.d(TAG, String.format(
"onSizeChanged: (%dx%d) old: (%dx%d)", w, h, oldw, oldh));
@@ -662,10 +622,6 @@ public class NavigationBarView extends LinearLayout {
+ " " + visibilityToString(button.getVisibility())
+ " alpha=" + button.getAlpha()
);
- if (button instanceof KeyButtonView) {
- pw.print(" drawingAlpha=" + ((KeyButtonView)button).getDrawingAlpha());
- pw.print(" quiescentAlpha=" + ((KeyButtonView)button).getQuiescentAlpha());
- }
}
pw.println();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
new file mode 100644
index 0000000..7072dcb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
@@ -0,0 +1,238 @@
+/*
+ * 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.statusbar.phone;
+
+import android.app.Notification;
+import android.service.notification.StatusBarNotification;
+
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.NotificationData;
+import com.android.systemui.statusbar.StatusBarState;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A class to handle notifications and their corresponding groups.
+ */
+public class NotificationGroupManager {
+
+ private final HashMap<String, NotificationGroup> mGroupMap = new HashMap<>();
+ private OnGroupChangeListener mListener;
+ private int mBarState = -1;
+
+ public void setOnGroupChangeListener(OnGroupChangeListener listener) {
+ mListener = listener;
+ }
+
+ public boolean isGroupExpanded(StatusBarNotification sbn) {
+ NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
+ if (group == null) {
+ return false;
+ }
+ return group.expanded;
+ }
+
+ public void setGroupExpanded(StatusBarNotification sbn, boolean expanded) {
+ NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
+ if (group == null) {
+ return;
+ }
+ setGroupExpanded(group, expanded);
+ }
+
+ private void setGroupExpanded(NotificationGroup group, boolean expanded) {
+ group.expanded = expanded;
+ if (group.summary != null) {
+ mListener.onGroupExpansionChanged(group.summary.row, expanded);
+ }
+ }
+
+ public void onEntryRemoved(NotificationData.Entry removed) {
+ onEntryRemovedInternal(removed, removed.notification);
+ }
+
+ /**
+ * An entry was removed.
+ *
+ * @param removed the removed entry
+ * @param sbn the notification the entry has, which doesn't need to be the same as it's internal
+ * notification
+ */
+ private void onEntryRemovedInternal(NotificationData.Entry removed,
+ final StatusBarNotification sbn) {
+ Notification notif = sbn.getNotification();
+ String groupKey = sbn.getGroupKey();
+ final NotificationGroup group = mGroupMap.get(groupKey);
+ if (notif.isGroupSummary()) {
+ group.summary = null;
+ } else {
+ group.children.remove(removed);
+ }
+ if (group.children.isEmpty()) {
+ if (group.summary == null) {
+ mGroupMap.remove(groupKey);
+ } else {
+ if (group.expanded) {
+ // only the summary is left. Change it to unexpanded in a few ms. We do this to
+ // avoid raceconditions
+ removed.row.post(new Runnable() {
+ @Override
+ public void run() {
+ if (group.children.isEmpty()) {
+ setGroupExpanded(sbn, false);
+ }
+ }
+ });
+ } else {
+ group.summary.row.updateExpandButton();
+ }
+ }
+ }
+ }
+
+ public void onEntryAdded(NotificationData.Entry added) {
+ StatusBarNotification sbn = added.notification;
+ Notification notif = sbn.getNotification();
+ String groupKey = sbn.getGroupKey();
+ NotificationGroup group = mGroupMap.get(groupKey);
+ if (group == null) {
+ group = new NotificationGroup();
+ mGroupMap.put(groupKey, group);
+ }
+ if (notif.isGroupSummary()) {
+ group.summary = added;
+ group.expanded = added.row.areChildrenExpanded();
+ if (!group.children.isEmpty()) {
+ mListener.onGroupCreatedFromChildren(group);
+ }
+ } else {
+ group.children.add(added);
+ if (group.summary != null && group.children.size() == 1 && !group.expanded) {
+ group.summary.row.updateExpandButton();
+ }
+ }
+ }
+
+ public void onEntryUpdated(NotificationData.Entry entry,
+ StatusBarNotification oldNotification) {
+ if (mGroupMap.get(oldNotification.getGroupKey()) != null) {
+ onEntryRemovedInternal(entry, oldNotification);
+ }
+ onEntryAdded(entry);
+ }
+
+ public boolean isVisible(StatusBarNotification sbn) {
+ if (!sbn.getNotification().isGroupChild()) {
+ return true;
+ }
+ NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
+ if (group != null && group.expanded) {
+ return true;
+ }
+ return false;
+ }
+
+ public boolean hasGroupChildren(StatusBarNotification sbn) {
+ if (areGroupsProhibited()) {
+ return false;
+ }
+ if (!sbn.getNotification().isGroupSummary()) {
+ return false;
+ }
+ NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
+ if (group == null) {
+ return false;
+ }
+ return !group.children.isEmpty();
+ }
+
+ public void setStatusBarState(int newState) {
+ if (mBarState == newState) {
+ return;
+ }
+ boolean prohibitedBefore = areGroupsProhibited();
+ mBarState = newState;
+ boolean nowProhibited = areGroupsProhibited();
+ if (nowProhibited != prohibitedBefore) {
+ if (nowProhibited) {
+ for (NotificationGroup group : mGroupMap.values()) {
+ if (group.expanded) {
+ setGroupExpanded(group, false);
+ }
+ }
+ }
+ mListener.onGroupsProhibitedChanged();
+ }
+ }
+
+ private boolean areGroupsProhibited() {
+ return mBarState == StatusBarState.KEYGUARD;
+ }
+
+ /**
+ * @return whether a given notification is a child in a group which has a summary
+ */
+ public boolean isChildInGroupWithSummary(StatusBarNotification sbn) {
+ if (!sbn.getNotification().isGroupChild()) {
+ return false;
+ }
+ NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
+ if (group == null || group.summary == null) {
+ return false;
+ }
+ return true;
+ }
+
+ public ExpandableNotificationRow getGroupSummary(StatusBarNotification sbn) {
+ NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
+ return group == null ? null
+ : group.summary == null ? null
+ : group.summary.row;
+ }
+
+ public static class NotificationGroup {
+ public final HashSet<NotificationData.Entry> children = new HashSet<>();
+ public NotificationData.Entry summary;
+ public boolean expanded;
+ }
+
+ public interface OnGroupChangeListener {
+ /**
+ * The expansion of a group has changed.
+ *
+ * @param changedRow the row for which the expansion has changed, which is also the summary
+ * @param expanded a boolean indicating the new expanded state
+ */
+ void onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded);
+
+ /**
+ * Children group policy has changed and children may no be prohibited or allowed.
+ */
+ void onGroupsProhibitedChanged();
+
+ /**
+ * A group of children just received a summary notification and should therefore become
+ * children of it.
+ *
+ * @param group the group created
+ */
+ void onGroupCreatedFromChildren(NotificationGroup group);
+ }
+}
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 0ae34bb..094b9b5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -32,23 +32,29 @@ import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewTreeObserver;
+import android.view.WindowInsets;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
import android.widget.FrameLayout;
import android.widget.TextView;
+import com.android.internal.logging.MetricsLogger;
import com.android.keyguard.KeyguardStatusView;
-import com.android.systemui.EventLogTags;
import com.android.systemui.EventLogConstants;
+import com.android.systemui.EventLogTags;
import com.android.systemui.R;
import com.android.systemui.qs.QSContainer;
import com.android.systemui.qs.QSPanel;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.ExpandableView;
import com.android.systemui.statusbar.FlingAnimationUtils;
import com.android.systemui.statusbar.GestureRecorder;
import com.android.systemui.statusbar.KeyguardAffordanceView;
+import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.stack.StackStateAnimator;
@@ -56,7 +62,8 @@ import com.android.systemui.statusbar.stack.StackStateAnimator;
public class NotificationPanelView extends PanelView implements
ExpandableView.OnHeightChangedListener, ObservableScrollView.Listener,
View.OnClickListener, NotificationStackScrollLayout.OnOverscrollTopChangedListener,
- KeyguardAffordanceHelper.Callback, NotificationStackScrollLayout.OnEmptySpaceClickListener {
+ KeyguardAffordanceHelper.Callback, NotificationStackScrollLayout.OnEmptySpaceClickListener,
+ HeadsUpManager.OnHeadsUpChangedListener {
private static final boolean DEBUG = false;
@@ -68,6 +75,10 @@ public class NotificationPanelView extends PanelView implements
private static final float HEADER_RUBBERBAND_FACTOR = 2.05f;
private static final float LOCK_ICON_ACTIVE_SCALE = 1.2f;
+ private static final String COUNTER_PANEL_OPEN = "panel_open";
+ private static final String COUNTER_PANEL_OPEN_QS = "panel_open_qs";
+ private static final String COUNTER_PANEL_OPEN_PEEK = "panel_open_peek";
+
public static final long DOZE_ANIMATION_DURATION = 700;
private KeyguardAffordanceHelper mAfforanceHelper;
@@ -81,7 +92,7 @@ public class NotificationPanelView extends PanelView implements
private TextView mClockView;
private View mReserveNotificationSpace;
private View mQsNavbarScrim;
- private View mNotificationContainerParent;
+ private NotificationsQuickSettingsContainer mNotificationContainerParent;
private NotificationStackScrollLayout mNotificationStackScroller;
private int mNotificationTopPadding;
private boolean mAnimateNextTopPaddingChange;
@@ -107,11 +118,13 @@ public class NotificationPanelView extends PanelView implements
* intercepted yet.
*/
private boolean mIntercepting;
+ private boolean mPanelExpanded;
private boolean mQsExpanded;
private boolean mQsExpandedWhenExpandingStarted;
private boolean mQsFullyExpanded;
private boolean mKeyguardShowing;
private boolean mDozing;
+ private boolean mDozingOnDown;
private int mStatusBarState;
private float mInitialHeightOnTouch;
private float mInitialTouchX;
@@ -163,7 +176,7 @@ public class NotificationPanelView extends PanelView implements
private Runnable mLaunchAnimationEndRunnable;
private boolean mOnlyAffordanceInThisMotion;
private boolean mKeyguardStatusViewAnimating;
- private boolean mHeaderAnimatingIn;
+ private boolean mHeaderAnimating;
private ObjectAnimator mQsContainerAnimator;
private ValueAnimator mQsSizeChangeAnimator;
@@ -176,6 +189,26 @@ public class NotificationPanelView extends PanelView implements
private float mKeyguardStatusBarAnimateAlpha = 1f;
private int mOldLayoutDirection;
+ private HeadsUpTouchHelper mHeadsUpTouchHelper;
+ private boolean mIsExpansionFromHeadsUp;
+ private boolean mListenForHeadsUp;
+ private int mNavigationBarBottomHeight;
+ private boolean mExpandingFromHeadsUp;
+ private boolean mCollapsedOnDown;
+ private int mPositionMinSideMargin;
+ private int mLastOrientation = -1;
+ private boolean mClosingWithAlphaFadeOut;
+
+ private Runnable mHeadsUpExistenceChangedRunnable = new Runnable() {
+ @Override
+ public void run() {
+ notifyBarPanelExpansionChanged();
+ }
+ };
+
+ /** Interpolator to be used for animations that respond directly to a touch */
+ private final Interpolator mTouchResponseInterpolator =
+ new PathInterpolator(0.3f, 0f, 0.1f, 1f);
public NotificationPanelView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -200,7 +233,8 @@ public class NotificationPanelView extends PanelView implements
mScrollView.setListener(this);
mScrollView.setFocusable(false);
mReserveNotificationSpace = findViewById(R.id.reserve_notification_space);
- mNotificationContainerParent = findViewById(R.id.notification_container_parent);
+ mNotificationContainerParent = (NotificationsQuickSettingsContainer)
+ findViewById(R.id.notification_container_parent);
mNotificationStackScroller = (NotificationStackScrollLayout)
findViewById(R.id.notification_stack_scroller);
mNotificationStackScroller.setOnHeightChangedListener(this);
@@ -218,13 +252,13 @@ public class NotificationPanelView extends PanelView implements
mAfforanceHelper = new KeyguardAffordanceHelper(this, getContext());
mSecureCameraLaunchManager =
new SecureCameraLaunchManager(getContext(), mKeyguardBottomArea);
+ mLastOrientation = getResources().getConfiguration().orientation;
// recompute internal state when qspanel height changes
mQsContainer.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) {
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft, int oldTop, int oldRight, int oldBottom) {
final int height = bottom - top;
final int oldHeight = oldBottom - oldTop;
if (height != oldHeight) {
@@ -251,6 +285,8 @@ public class NotificationPanelView extends PanelView implements
getResources().getDimensionPixelSize(R.dimen.notification_scrim_wait_distance);
mQsFalsingThreshold = getResources().getDimensionPixelSize(
R.dimen.qs_falsing_threshold);
+ mPositionMinSideMargin = getResources().getDimensionPixelSize(
+ R.dimen.notification_panel_min_side_margin);
}
public void updateResources() {
@@ -304,7 +340,7 @@ public class NotificationPanelView extends PanelView implements
} else if (!mQsExpanded) {
setQsExpansion(mQsMinExpansionHeight + mLastOverscroll);
}
- mNotificationStackScroller.setStackHeight(getExpandedHeight());
+ updateStackHeight(getExpandedHeight());
updateHeader();
mNotificationStackScroller.updateIsSmallScreen(
mHeader.getCollapsedHeight() + mQsPeekHeight);
@@ -317,6 +353,7 @@ public class NotificationPanelView extends PanelView implements
if (mQsSizeChangeAnimator == null) {
mQsContainer.setHeightOverride(mQsContainer.getDesiredHeight());
}
+ updateMaxHeadsUpTranslation();
}
@Override
@@ -450,10 +487,11 @@ public class NotificationPanelView extends PanelView implements
mStatusBar.dismissPopups();
mNotificationStackScroller.setOverScrollAmount(0f, true /* onTop */, false /* animate */,
true /* cancelAnimators */);
+ mNotificationStackScroller.resetScrollPosition();
}
public void closeQs() {
- cancelAnimation();
+ cancelQsAnimation();
setQsExpansion(mQsMinExpansionHeight);
}
@@ -470,7 +508,7 @@ public class NotificationPanelView extends PanelView implements
}
public void openQs() {
- cancelAnimation();
+ cancelQsAnimation();
if (mQsExpansionEnabled) {
setQsExpansion(mQsMaxExpansionHeight);
}
@@ -493,14 +531,21 @@ public class NotificationPanelView extends PanelView implements
}
@Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ protected void flingToHeight(float vel, boolean expand, float target,
+ float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) {
+ mHeadsUpTouchHelper.notifyFling(!expand);
+ setClosingWithAlphaFadeout(!expand && getFadeoutAlpha() == 1.0f);
+ super.flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing);
+ }
+
+ @Override
+ public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
event.getText().add(getKeyguardOrLockScreenString());
mLastAnnouncementWasQuickSettings = false;
return true;
}
-
- return super.dispatchPopulateAccessibilityEvent(event);
+ return super.dispatchPopulateAccessibilityEventInternal(event);
}
@Override
@@ -508,7 +553,20 @@ public class NotificationPanelView extends PanelView implements
if (mBlockTouches) {
return false;
}
- resetDownStates(event);
+ initDownStates(event);
+ if (mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
+ mIsExpansionFromHeadsUp = true;
+ MetricsLogger.count(mContext, COUNTER_PANEL_OPEN, 1);
+ MetricsLogger.count(mContext, COUNTER_PANEL_OPEN_PEEK, 1);
+ return true;
+ }
+ if (!isFullyCollapsed() && onQsIntercept(event)) {
+ return true;
+ }
+ return super.onInterceptTouchEvent(event);
+ }
+
+ private boolean onQsIntercept(MotionEvent event) {
int pointerIndex = event.findPointerIndex(mTrackingPointer);
if (pointerIndex < 0) {
pointerIndex = 0;
@@ -576,28 +634,30 @@ public class NotificationPanelView extends PanelView implements
case MotionEvent.ACTION_UP:
trackMovement(event);
if (mQsTracking) {
- flingQsWithCurrentVelocity(
+ flingQsWithCurrentVelocity(y,
event.getActionMasked() == MotionEvent.ACTION_CANCEL);
mQsTracking = false;
}
mIntercepting = false;
break;
}
- return super.onInterceptTouchEvent(event);
+ return false;
}
@Override
protected boolean isInContentBounds(float x, float y) {
- float yTransformed = y - mNotificationStackScroller.getY();
float stackScrollerX = mNotificationStackScroller.getX();
- return mNotificationStackScroller.isInContentBounds(yTransformed) && stackScrollerX < x
- && x < stackScrollerX + mNotificationStackScroller.getWidth();
+ return !mNotificationStackScroller.isBelowLastNotification(x - stackScrollerX, y)
+ && stackScrollerX < x && x < stackScrollerX + mNotificationStackScroller.getWidth();
}
- private void resetDownStates(MotionEvent event) {
+ private void initDownStates(MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
mOnlyAffordanceInThisMotion = false;
mQsTouchAboveFalsingThreshold = mQsFullyExpanded;
+ mDozingOnDown = isDozing();
+ mCollapsedOnDown = isFullyCollapsed();
+ mListenForHeadsUp = mCollapsedOnDown && mHeadsUpManager.hasPinnedHeadsUp();
}
}
@@ -612,9 +672,24 @@ public class NotificationPanelView extends PanelView implements
super.requestDisallowInterceptTouchEvent(disallowIntercept);
}
- private void flingQsWithCurrentVelocity(boolean isCancelMotionEvent) {
+ private void flingQsWithCurrentVelocity(float y, boolean isCancelMotionEvent) {
float vel = getCurrentVelocity();
- flingSettings(vel, flingExpandsQs(vel) && !isCancelMotionEvent);
+ final boolean expandsQs = flingExpandsQs(vel);
+ if (expandsQs) {
+ logQsSwipeDown(y);
+ }
+ flingSettings(vel, expandsQs && !isCancelMotionEvent);
+ }
+
+ private void logQsSwipeDown(float y) {
+ float vel = getCurrentVelocity();
+ final int gesture = mStatusBarState == StatusBarState.KEYGUARD
+ ? EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_SWIPE_DOWN_QS
+ : EventLogConstants.SYSUI_SHADE_GESTURE_SWIPE_DOWN_QS;
+ EventLogTags.writeSysuiLockscreenGesture(
+ gesture,
+ (int) ((y - mInitialTouchY) / mStatusBar.getDisplayDensity()),
+ (int) (vel / mStatusBar.getDisplayDensity()));
}
private boolean flingExpandsQs(float vel) {
@@ -642,7 +717,12 @@ public class NotificationPanelView extends PanelView implements
if (mBlockTouches) {
return false;
}
- resetDownStates(event);
+ initDownStates(event);
+ if (mListenForHeadsUp && !mHeadsUpTouchHelper.isTrackingHeadsUp()
+ && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
+ mIsExpansionFromHeadsUp = true;
+ MetricsLogger.count(mContext, COUNTER_PANEL_OPEN_PEEK, 1);
+ }
if ((!mIsExpanding || mHintAnimationRunning)
&& !mQsExpanded
&& mStatusBar.getBarState() != StatusBarState.SHADE) {
@@ -651,7 +731,21 @@ public class NotificationPanelView extends PanelView implements
if (mOnlyAffordanceInThisMotion) {
return true;
}
- if (event.getActionMasked() == MotionEvent.ACTION_DOWN && getExpandedFraction() == 1f
+ mHeadsUpTouchHelper.onTouchEvent(event);
+ if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && handleQsTouch(event)) {
+ return true;
+ }
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) {
+ MetricsLogger.count(mContext, COUNTER_PANEL_OPEN, 1);
+ updateVerticalPanelPosition(event.getX());
+ }
+ super.onTouchEvent(event);
+ return true;
+ }
+
+ private boolean handleQsTouch(MotionEvent event) {
+ final int action = event.getActionMasked();
+ if (action == MotionEvent.ACTION_DOWN && getExpandedFraction() == 1f
&& mStatusBar.getBarState() != StatusBarState.KEYGUARD && !mQsExpanded
&& mQsExpansionEnabled) {
@@ -663,7 +757,7 @@ public class NotificationPanelView extends PanelView implements
mInitialTouchY = event.getX();
mInitialTouchX = event.getY();
}
- if (mExpandedHeight != 0) {
+ if (!isFullyCollapsed()) {
handleQsDown(event);
}
if (!mQsExpandImmediate && mQsTracking) {
@@ -672,17 +766,23 @@ public class NotificationPanelView extends PanelView implements
return true;
}
}
- if (event.getActionMasked() == MotionEvent.ACTION_CANCEL
- || event.getActionMasked() == MotionEvent.ACTION_UP) {
+ if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
mConflictingQsExpansionGesture = false;
}
- if (event.getActionMasked() == MotionEvent.ACTION_DOWN && mExpandedHeight == 0
+ if (action == MotionEvent.ACTION_DOWN && isFullyCollapsed()
&& mQsExpansionEnabled) {
mTwoFingerQsExpandPossible = true;
}
- if (mTwoFingerQsExpandPossible && event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN
- && event.getPointerCount() == 2
+ final int pointerCount = event.getPointerCount();
+ final boolean twoFingerDrag = action == MotionEvent.ACTION_POINTER_DOWN
+ && pointerCount == 2;
+ final boolean stylusClickDrag = action == MotionEvent.ACTION_DOWN
+ && pointerCount == 1 && event.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS
+ && (event.isButtonPressed(MotionEvent.BUTTON_SECONDARY)
+ || event.isButtonPressed(MotionEvent.BUTTON_TERTIARY));
+ if (mTwoFingerQsExpandPossible && (twoFingerDrag || stylusClickDrag)
&& event.getY(event.getActionIndex()) < mStatusBarMinHeight) {
+ MetricsLogger.count(mContext, COUNTER_PANEL_OPEN_QS, 1);
mQsExpandImmediate = true;
requestPanelHeightUpdate();
@@ -690,12 +790,11 @@ public class NotificationPanelView extends PanelView implements
// earlier so the state is already up to date when dragging down.
setListening(true);
}
- super.onTouchEvent(event);
- return true;
+ return false;
}
private boolean isInQsArea(float x, float y) {
- return (x >= mScrollView.getLeft() && x <= mScrollView.getRight()) &&
+ return (x >= mScrollView.getX() && x <= mScrollView.getX() + mScrollView.getWidth()) &&
(y <= mNotificationStackScroller.getBottomMostNotificationBottom()
|| y <= mQsContainer.getY() + mQsContainer.getHeight());
}
@@ -717,8 +816,8 @@ public class NotificationPanelView extends PanelView implements
}
@Override
- protected boolean flingExpands(float vel, float vectorVel) {
- boolean expands = super.flingExpands(vel, vectorVel);
+ protected boolean flingExpands(float vel, float vectorVel, float x, float y) {
+ boolean expands = super.flingExpands(vel, vectorVel, x, y);
// If we are already running a QS expansion, make sure that we keep the panel open.
if (mQsExpansionAnimator != null) {
@@ -732,6 +831,11 @@ public class NotificationPanelView extends PanelView implements
return mStatusBar.getBarState() != StatusBarState.SHADE;
}
+ @Override
+ protected boolean shouldGestureIgnoreXTouchSlop(float x, float y) {
+ return !mAfforanceHelper.isOnAffordanceIcon(x, y);
+ }
+
private void onQsTouch(MotionEvent event) {
int pointerIndex = event.findPointerIndex(mTrackingPointer);
if (pointerIndex < 0) {
@@ -740,6 +844,7 @@ public class NotificationPanelView extends PanelView implements
}
final float y = event.getY(pointerIndex);
final float x = event.getX(pointerIndex);
+ final float h = y - mInitialTouchY;
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
@@ -767,7 +872,6 @@ public class NotificationPanelView extends PanelView implements
break;
case MotionEvent.ACTION_MOVE:
- final float h = y - mInitialTouchY;
setQsExpansion(h + mInitialHeightOnTouch);
if (h >= getFalsingThreshold()) {
mQsTouchAboveFalsingThreshold = true;
@@ -783,9 +887,10 @@ public class NotificationPanelView extends PanelView implements
float fraction = getQsExpansionFraction();
if ((fraction != 0f || y >= mInitialTouchY)
&& (fraction != 1f || y <= mInitialTouchY)) {
- flingQsWithCurrentVelocity(
+ flingQsWithCurrentVelocity(y,
event.getActionMasked() == MotionEvent.ACTION_CANCEL);
} else {
+ logQsSwipeDown(y);
mScrollYOverride = -1;
}
if (mVelocityTracker != null) {
@@ -815,7 +920,7 @@ public class NotificationPanelView extends PanelView implements
@Override
public void onOverscrollTopChanged(float amount, boolean isRubberbanded) {
- cancelAnimation();
+ cancelQsAnimation();
if (!mQsExpansionEnabled) {
amount = 0f;
}
@@ -833,13 +938,13 @@ public class NotificationPanelView extends PanelView implements
setQsExpansion(mQsExpansionHeight);
flingSettings(!mQsExpansionEnabled && open ? 0f : velocity, open && mQsExpansionEnabled,
new Runnable() {
- @Override
- public void run() {
- mStackScrollerOverscrolling = false;
- mQsExpansionFromOverscroll = false;
- updateQsState();
- }
- });
+ @Override
+ public void run() {
+ mStackScrollerOverscrolling = false;
+ mQsExpansionFromOverscroll = false;
+ updateQsState();
+ }
+ }, false /* isClick */);
}
private void onQsExpansionStarted() {
@@ -847,7 +952,8 @@ public class NotificationPanelView extends PanelView implements
}
private void onQsExpansionStarted(int overscrollAmount) {
- cancelAnimation();
+ cancelQsAnimation();
+ cancelHeightAnimator();
// Reset scroll position and apply that position to the expanded height.
float height = mQsExpansionHeight - mScrollView.getScrollY() - overscrollAmount;
@@ -868,31 +974,41 @@ public class NotificationPanelView extends PanelView implements
mNotificationStackScroller.setInterceptDelegateEnabled(expanded);
mStatusBar.setQsExpanded(expanded);
mQsPanel.setExpanded(expanded);
+ mNotificationContainerParent.setQsExpanded(expanded);
}
}
public void setBarState(int statusBarState, boolean keyguardFadingAway,
boolean goingToFullShade) {
- boolean keyguardShowing = statusBarState == StatusBarState.KEYGUARD
- || statusBarState == StatusBarState.SHADE_LOCKED;
- if (!mKeyguardShowing && keyguardShowing) {
- setQsTranslation(mQsExpansionHeight);
- mHeader.setTranslationY(0f);
- }
+ int oldState = mStatusBarState;
+ boolean keyguardShowing = statusBarState == StatusBarState.KEYGUARD;
setKeyguardStatusViewVisibility(statusBarState, keyguardFadingAway, goingToFullShade);
setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade);
- if (goingToFullShade) {
+
+ mStatusBarState = statusBarState;
+ mKeyguardShowing = keyguardShowing;
+
+ if (goingToFullShade || (oldState == StatusBarState.KEYGUARD
+ && statusBarState == StatusBarState.SHADE_LOCKED)) {
animateKeyguardStatusBarOut();
+ animateHeaderSlidingIn();
+ } else if (oldState == StatusBarState.SHADE_LOCKED
+ && statusBarState == StatusBarState.KEYGUARD) {
+ animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+ animateHeaderSlidingOut();
} else {
mKeyguardStatusBar.setAlpha(1f);
mKeyguardStatusBar.setVisibility(keyguardShowing ? View.VISIBLE : View.INVISIBLE);
+ if (keyguardShowing && oldState != mStatusBarState) {
+ mKeyguardBottomArea.updateLeftAffordance();
+ mAfforanceHelper.updatePreviews();
+ }
}
- mStatusBarState = statusBarState;
- mKeyguardShowing = keyguardShowing;
- updateQsState();
- if (goingToFullShade) {
- animateHeaderSlidingIn();
+ if (keyguardShowing) {
+ updateDozingVisibilities(false /* animate */);
}
+ resetVerticalPanelPosition();
+ updateQsState();
}
private final Runnable mAnimateKeyguardStatusViewInvisibleEndRunnable = new Runnable() {
@@ -914,7 +1030,7 @@ public class NotificationPanelView extends PanelView implements
= new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- mHeaderAnimatingIn = false;
+ mHeaderAnimating = false;
mQsContainerAnimator = null;
mQsContainer.removeOnLayoutChangeListener(mQsContainerAnimatorUpdater);
}
@@ -942,10 +1058,13 @@ public class NotificationPanelView extends PanelView implements
@Override
public boolean onPreDraw() {
getViewTreeObserver().removeOnPreDrawListener(this);
+ long delay = mStatusBarState == StatusBarState.SHADE_LOCKED
+ ? 0
+ : mStatusBar.calculateGoingToFullShadeDelay();
mHeader.setTranslationY(-mHeader.getCollapsedHeight() - mQsPeekHeight);
mHeader.animate()
.translationY(0f)
- .setStartDelay(mStatusBar.calculateGoingToFullShadeDelay())
+ .setStartDelay(delay)
.setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE)
.setInterpolator(mFastOutSlowInInterpolator)
.start();
@@ -954,7 +1073,7 @@ public class NotificationPanelView extends PanelView implements
mQsContainer.getTranslationY(),
mHeader.getCollapsedHeight() + mQsPeekHeight - mQsContainer.getHeight()
- mQsContainer.getTop());
- mQsContainerAnimator.setStartDelay(mStatusBar.calculateGoingToFullShadeDelay());
+ mQsContainerAnimator.setStartDelay(delay);
mQsContainerAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE);
mQsContainerAnimator.setInterpolator(mFastOutSlowInInterpolator);
mQsContainerAnimator.addListener(mAnimateHeaderSlidingInListener);
@@ -963,11 +1082,37 @@ public class NotificationPanelView extends PanelView implements
return true;
}
};
-
+
private void animateHeaderSlidingIn() {
- mHeaderAnimatingIn = true;
- getViewTreeObserver().addOnPreDrawListener(mStartHeaderSlidingIn);
+ // If the QS is already expanded we don't need to slide in the header as it's already
+ // visible.
+ if (!mQsExpanded) {
+ mHeaderAnimating = true;
+ getViewTreeObserver().addOnPreDrawListener(mStartHeaderSlidingIn);
+ }
+ }
+ private void animateHeaderSlidingOut() {
+ mHeaderAnimating = true;
+ mHeader.animate().y(-mHeader.getHeight())
+ .setStartDelay(0)
+ .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD)
+ .setInterpolator(mFastOutSlowInInterpolator)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mHeader.animate().setListener(null);
+ mHeaderAnimating = false;
+ updateQsState();
+ }
+ })
+ .start();
+ mQsContainer.animate()
+ .y(-mQsContainer.getHeight())
+ .setStartDelay(0)
+ .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD)
+ .setInterpolator(mFastOutSlowInInterpolator)
+ .start();
}
private final Runnable mAnimateKeyguardStatusBarInvisibleEndRunnable = new Runnable() {
@@ -980,34 +1125,41 @@ public class NotificationPanelView extends PanelView implements
};
private void animateKeyguardStatusBarOut() {
- mKeyguardStatusBar.animate()
- .alpha(0f)
- .setStartDelay(mStatusBar.getKeyguardFadingAwayDelay())
- .setDuration(mStatusBar.getKeyguardFadingAwayDuration()/2)
- .setInterpolator(PhoneStatusBar.ALPHA_OUT)
- .setUpdateListener(mStatusBarAnimateAlphaListener)
- .withEndAction(mAnimateKeyguardStatusBarInvisibleEndRunnable)
- .start();
+ ValueAnimator anim = ValueAnimator.ofFloat(mKeyguardStatusBar.getAlpha(), 0f);
+ anim.addUpdateListener(mStatusBarAnimateAlphaListener);
+ anim.setStartDelay(mStatusBar.isKeyguardFadingAway()
+ ? mStatusBar.getKeyguardFadingAwayDelay()
+ : 0);
+ anim.setDuration(mStatusBar.isKeyguardFadingAway()
+ ? mStatusBar.getKeyguardFadingAwayDuration() / 2
+ : StackStateAnimator.ANIMATION_DURATION_STANDARD);
+ anim.setInterpolator(mDozeAnimationInterpolator);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mAnimateKeyguardStatusBarInvisibleEndRunnable.run();
+ }
+ });
+ anim.start();
}
private final ValueAnimator.AnimatorUpdateListener mStatusBarAnimateAlphaListener =
new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
- mKeyguardStatusBarAnimateAlpha = mKeyguardStatusBar.getAlpha();
+ mKeyguardStatusBarAnimateAlpha = (float) animation.getAnimatedValue();
+ updateHeaderKeyguardAlpha();
}
};
- private void animateKeyguardStatusBarIn() {
+ private void animateKeyguardStatusBarIn(long duration) {
mKeyguardStatusBar.setVisibility(View.VISIBLE);
mKeyguardStatusBar.setAlpha(0f);
- mKeyguardStatusBar.animate()
- .alpha(1f)
- .setStartDelay(0)
- .setDuration(DOZE_ANIMATION_DURATION)
- .setInterpolator(mDozeAnimationInterpolator)
- .setUpdateListener(mStatusBarAnimateAlphaListener)
- .start();
+ ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
+ anim.addUpdateListener(mStatusBarAnimateAlphaListener);
+ anim.setDuration(duration);
+ anim.setInterpolator(mDozeAnimationInterpolator);
+ anim.start();
}
private final Runnable mAnimateKeyguardBottomAreaInvisibleEndRunnable = new Runnable() {
@@ -1024,14 +1176,16 @@ public class NotificationPanelView extends PanelView implements
mKeyguardBottomArea.animate()
.alpha(0f)
.setStartDelay(mStatusBar.getKeyguardFadingAwayDelay())
- .setDuration(mStatusBar.getKeyguardFadingAwayDuration()/2)
+ .setDuration(mStatusBar.getKeyguardFadingAwayDuration() / 2)
.setInterpolator(PhoneStatusBar.ALPHA_OUT)
.withEndAction(mAnimateKeyguardBottomAreaInvisibleEndRunnable)
.start();
} else if (statusBarState == StatusBarState.KEYGUARD
|| statusBarState == StatusBarState.SHADE_LOCKED) {
mKeyguardBottomArea.animate().cancel();
- mKeyguardBottomArea.setVisibility(View.VISIBLE);
+ if (!mDozing) {
+ mKeyguardBottomArea.setVisibility(View.VISIBLE);
+ }
mKeyguardBottomArea.setAlpha(1f);
} else {
mKeyguardBottomArea.animate().cancel();
@@ -1084,9 +1238,12 @@ public class NotificationPanelView extends PanelView implements
}
private void updateQsState() {
- boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling;
- mHeader.setVisibility((mQsExpanded || !mKeyguardShowing) ? View.VISIBLE : View.INVISIBLE);
- mHeader.setExpanded(mKeyguardShowing || (mQsExpanded && !mStackScrollerOverscrolling));
+ boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling || mHeaderAnimating;
+ mHeader.setVisibility((mQsExpanded || !mKeyguardShowing || mHeaderAnimating)
+ ? View.VISIBLE
+ : View.INVISIBLE);
+ mHeader.setExpanded((mKeyguardShowing && !mHeaderAnimating)
+ || (mQsExpanded && !mStackScrollerOverscrolling));
mNotificationStackScroller.setScrollingEnabled(
mStatusBarState != StatusBarState.KEYGUARD && (!mQsExpanded
|| mQsExpansionFromOverscroll));
@@ -1111,7 +1268,7 @@ public class NotificationPanelView extends PanelView implements
setQsExpanded(true);
} else if (height <= mQsMinExpansionHeight && mQsExpanded) {
setQsExpanded(false);
- if (mLastAnnouncementWasQuickSettings && !mTracking) {
+ if (mLastAnnouncementWasQuickSettings && !mTracking && !isCollapsing()) {
announceForAccessibility(getKeyguardOrLockScreenString());
mLastAnnouncementWasQuickSettings = false;
}
@@ -1124,6 +1281,10 @@ public class NotificationPanelView extends PanelView implements
if (mKeyguardShowing) {
updateHeaderKeyguard();
}
+ if (mStatusBarState == StatusBarState.SHADE_LOCKED
+ || mStatusBarState == StatusBarState.KEYGUARD) {
+ updateKeyguardBottomAreaAlpha();
+ }
if (mStatusBarState == StatusBarState.SHADE && mQsExpanded
&& !mStackScrollerOverscrolling && mQsScrimEnabled) {
mQsNavbarScrim.setAlpha(getQsExpansionFraction());
@@ -1164,10 +1325,10 @@ public class NotificationPanelView extends PanelView implements
}
private void setQsTranslation(float height) {
- if (!mHeaderAnimatingIn) {
+ if (!mHeaderAnimating) {
mQsContainer.setY(height - mQsContainer.getDesiredHeight() + getHeaderTranslation());
}
- if (mKeyguardShowing) {
+ if (mKeyguardShowing && !mHeaderAnimating) {
mHeader.setY(interpolate(getQsExpansionFraction(), -mHeader.getHeight(), 0));
}
}
@@ -1233,17 +1394,18 @@ public class NotificationPanelView extends PanelView implements
return mVelocityTracker.getYVelocity();
}
- private void cancelAnimation() {
+ private void cancelQsAnimation() {
if (mQsExpansionAnimator != null) {
mQsExpansionAnimator.cancel();
}
}
private void flingSettings(float vel, boolean expand) {
- flingSettings(vel, expand, null);
+ flingSettings(vel, expand, null, false /* isClick */);
}
- private void flingSettings(float vel, boolean expand, final Runnable onFinishRunnable) {
+ private void flingSettings(float vel, boolean expand, final Runnable onFinishRunnable,
+ boolean isClick) {
float target = expand ? mQsMaxExpansionHeight : mQsMinExpansionHeight;
if (target == mQsExpansionHeight) {
mScrollYOverride = -1;
@@ -1258,7 +1420,12 @@ public class NotificationPanelView extends PanelView implements
}
mScrollView.setBlockFlinging(true);
ValueAnimator animator = ValueAnimator.ofFloat(mQsExpansionHeight, target);
- mFlingAnimationUtils.apply(animator, mQsExpansionHeight, target, vel);
+ if (isClick) {
+ animator.setInterpolator(mTouchResponseInterpolator);
+ animator.setDuration(368);
+ } else {
+ mFlingAnimationUtils.apply(animator, mQsExpansionHeight, target, vel);
+ }
if (belowFalsingThreshold) {
animator.setDuration(350);
}
@@ -1288,11 +1455,11 @@ public class NotificationPanelView extends PanelView implements
* @return Whether we should intercept a gesture to open Quick Settings.
*/
private boolean shouldQuickSettingsIntercept(float x, float y, float yDiff) {
- if (!mQsExpansionEnabled) {
+ if (!mQsExpansionEnabled || mCollapsedOnDown) {
return false;
}
View header = mKeyguardShowing ? mKeyguardStatusBar : mHeader;
- boolean onHeader = x >= header.getLeft() && x <= header.getRight()
+ boolean onHeader = x >= header.getX() && x <= header.getX() + header.getWidth()
&& y >= header.getTop() && y <= header.getBottom();
if (mQsExpanded) {
return onHeader || (mScrollView.isScrolledToBottom() && yDiff < 0) && isInQsArea(x, y);
@@ -1359,15 +1526,26 @@ public class NotificationPanelView extends PanelView implements
setQsExpansion(mQsMinExpansionHeight
+ t * (getTempQsMaxExpansion() - mQsMinExpansionHeight));
}
- mNotificationStackScroller.setStackHeight(expandedHeight);
+ updateStackHeight(expandedHeight);
updateHeader();
updateUnlockIcon();
updateNotificationTranslucency();
+ updatePanelExpanded();
+ mNotificationStackScroller.setShadeExpanded(!isFullyCollapsed());
if (DEBUG) {
invalidate();
}
}
+ private void updatePanelExpanded() {
+ boolean isExpanded = !isFullyCollapsed();
+ if (mPanelExpanded != isExpanded) {
+ mHeadsUpManager.setIsExpanded(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
@@ -1432,18 +1610,20 @@ public class NotificationPanelView extends PanelView implements
}
}
private void updateNotificationTranslucency() {
+ float alpha = 1f;
+ if (mClosingWithAlphaFadeOut && !mExpandingFromHeadsUp && !mHeadsUpManager.hasPinnedHeadsUp()) {
+ alpha = getFadeoutAlpha();
+ }
+ mNotificationStackScroller.setAlpha(alpha);
+ }
+
+ private float getFadeoutAlpha() {
float alpha = (getNotificationsTopY() + mNotificationStackScroller.getItemHeight())
/ (mQsMinExpansionHeight + mNotificationStackScroller.getBottomStackPeekSize()
- - mNotificationStackScroller.getCollapseSecondCardPadding());
+ - mNotificationStackScroller.getCollapseSecondCardPadding());
alpha = Math.max(0, Math.min(alpha, 1));
alpha = (float) Math.pow(alpha, 0.75);
- if (alpha != 1f && mNotificationStackScroller.getLayerType() != LAYER_TYPE_HARDWARE) {
- mNotificationStackScroller.setLayerType(LAYER_TYPE_HARDWARE, null);
- } else if (alpha == 1f
- && mNotificationStackScroller.getLayerType() == LAYER_TYPE_HARDWARE) {
- mNotificationStackScroller.setLayerType(LAYER_TYPE_NONE, null);
- }
- mNotificationStackScroller.setAlpha(alpha);
+ return alpha;
}
@Override
@@ -1466,7 +1646,7 @@ public class NotificationPanelView extends PanelView implements
lockIcon.setImageScale(LOCK_ICON_ACTIVE_SCALE, true, 150,
mFastOutLinearInterpolator);
} else if (!active && mUnlockIconActive && mTracking) {
- lockIcon.setImageAlpha(KeyguardAffordanceHelper.SWIPE_RESTING_ALPHA_AMOUNT, true,
+ lockIcon.setImageAlpha(lockIcon.getRestingAlpha(), true /* animate */,
150, mFastOutLinearInterpolator, null);
lockIcon.setImageScale(1.0f, true, 150,
mFastOutLinearInterpolator);
@@ -1479,8 +1659,7 @@ public class NotificationPanelView extends PanelView implements
* Hides the header when notifications are colliding with it.
*/
private void updateHeader() {
- if (mStatusBar.getBarState() == StatusBarState.KEYGUARD
- || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) {
+ if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
updateHeaderKeyguard();
} else {
updateHeaderShade();
@@ -1489,15 +1668,14 @@ public class NotificationPanelView extends PanelView implements
}
private void updateHeaderShade() {
- if (!mHeaderAnimatingIn) {
+ if (!mHeaderAnimating) {
mHeader.setTranslationY(getHeaderTranslation());
}
setQsTranslation(mQsExpansionHeight);
}
private float getHeaderTranslation() {
- if (mStatusBar.getBarState() == StatusBarState.KEYGUARD
- || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) {
+ if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
return 0;
}
if (mNotificationStackScroller.getNotGoneChildCount() == 0) {
@@ -1507,33 +1685,60 @@ public class NotificationPanelView extends PanelView implements
return mExpandedHeight / HEADER_RUBBERBAND_FACTOR - mQsMinExpansionHeight;
}
}
- return Math.min(0, mNotificationStackScroller.getTranslationY()) / HEADER_RUBBERBAND_FACTOR;
+ float stackTranslation = mNotificationStackScroller.getStackTranslation();
+ float translation = stackTranslation / HEADER_RUBBERBAND_FACTOR;
+ if (mHeadsUpManager.hasPinnedHeadsUp() || mIsExpansionFromHeadsUp) {
+ translation = mNotificationStackScroller.getTopPadding() + stackTranslation
+ - mNotificationTopPadding - mQsMinExpansionHeight;
+ }
+ return Math.min(0, translation);
}
- private void updateHeaderKeyguard() {
- float alphaNotifications;
+ /**
+ * @return the alpha to be used to fade out the contents on Keyguard (status bar, bottom area)
+ * during swiping up
+ */
+ private float getKeyguardContentsAlpha() {
+ float alpha;
if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
// When on Keyguard, we hide the header as soon as the top card of the notification
// stack scroller is close enough (collision distance) to the bottom of the header.
- alphaNotifications = getNotificationsTopY()
+ alpha = getNotificationsTopY()
/
(mKeyguardStatusBar.getHeight() + mNotificationsHeaderCollideDistance);
} else {
// In SHADE_LOCKED, the top card is already really close to the header. Hide it as
// soon as we start translating the stack.
- alphaNotifications = getNotificationsTopY() / mKeyguardStatusBar.getHeight();
+ alpha = getNotificationsTopY() / mKeyguardStatusBar.getHeight();
}
- alphaNotifications = MathUtils.constrain(alphaNotifications, 0, 1);
- alphaNotifications = (float) Math.pow(alphaNotifications, 0.75);
+ alpha = MathUtils.constrain(alpha, 0, 1);
+ alpha = (float) Math.pow(alpha, 0.75);
+ return alpha;
+ }
+
+ private void updateHeaderKeyguardAlpha() {
float alphaQsExpansion = 1 - Math.min(1, getQsExpansionFraction() * 2);
- mKeyguardStatusBar.setAlpha(Math.min(alphaNotifications, alphaQsExpansion)
+ mKeyguardStatusBar.setAlpha(Math.min(getKeyguardContentsAlpha(), alphaQsExpansion)
* mKeyguardStatusBarAnimateAlpha);
- mKeyguardBottomArea.setAlpha(Math.min(1 - getQsExpansionFraction(), alphaNotifications));
+ mKeyguardStatusBar.setVisibility(mKeyguardStatusBar.getAlpha() != 0f
+ && !mDozing ? VISIBLE : INVISIBLE);
+ }
+
+ private void updateHeaderKeyguard() {
+ updateHeaderKeyguardAlpha();
setQsTranslation(mQsExpansionHeight);
}
+ private void updateKeyguardBottomAreaAlpha() {
+ float alpha = Math.min(getKeyguardContentsAlpha(), 1 - getQsExpansionFraction());
+ mKeyguardBottomArea.setAlpha(alpha);
+ mKeyguardBottomArea.setImportantForAccessibility(alpha == 0f
+ ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+ : IMPORTANT_FOR_ACCESSIBILITY_AUTO);
+ }
+
private float getNotificationsTopY() {
if (mNotificationStackScroller.getNotGoneChildCount() == 0) {
return getExpandedHeight();
@@ -1556,15 +1761,20 @@ public class NotificationPanelView extends PanelView implements
protected void onExpandingFinished() {
super.onExpandingFinished();
mNotificationStackScroller.onExpansionStopped();
+ mHeadsUpManager.onExpandingFinished();
mIsExpanding = false;
mScrollYOverride = -1;
- if (mExpandedHeight == 0f) {
+ if (isFullyCollapsed()) {
setListening(false);
} else {
setListening(true);
}
mQsExpandImmediate = false;
mTwoFingerQsExpandPossible = false;
+ mIsExpansionFromHeadsUp = false;
+ mNotificationStackScroller.setTrackingHeadsUp(false);
+ mExpandingFromHeadsUp = false;
+ setPanelScrimMinFraction(0.0f);
}
private void setListening(boolean listening) {
@@ -1607,6 +1817,7 @@ public class NotificationPanelView extends PanelView implements
|| mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) {
mAfforanceHelper.animateHideLeftRightIcon();
}
+ mNotificationStackScroller.onPanelTrackingStarted();
}
@Override
@@ -1616,6 +1827,7 @@ public class NotificationPanelView extends PanelView implements
mNotificationStackScroller.setOverScrolledPixels(
0.0f, true /* onTop */, true /* animate */);
}
+ mNotificationStackScroller.onPanelTrackingStopped();
if (expand && (mStatusBar.getBarState() == StatusBarState.KEYGUARD
|| mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED)) {
if (!mHintAnimationRunning) {
@@ -1631,7 +1843,7 @@ public class NotificationPanelView extends PanelView implements
}
@Override
- public void onHeightChanged(ExpandableView view) {
+ public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
// Block update if we are in quick settings and just the top padding changed
// (i.e. view == null).
@@ -1657,6 +1869,21 @@ public class NotificationPanelView extends PanelView implements
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
mAfforanceHelper.onConfigurationChanged();
+ if (newConfig.orientation != mLastOrientation) {
+ resetVerticalPanelPosition();
+ }
+ mLastOrientation = newConfig.orientation;
+ }
+
+ @Override
+ public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+ mNavigationBarBottomHeight = insets.getSystemWindowInsetBottom();
+ updateMaxHeadsUpTranslation();
+ return insets;
+ }
+
+ private void updateMaxHeadsUpTranslation() {
+ mNotificationStackScroller.setHeadsUpBoundaries(getHeight(), mNavigationBarBottomHeight);
}
@Override
@@ -1672,9 +1899,12 @@ public class NotificationPanelView extends PanelView implements
if (v == mHeader) {
onQsExpansionStarted();
if (mQsExpanded) {
- flingSettings(0 /* vel */, false /* expand */);
+ flingSettings(0 /* vel */, false /* expand */, null, true /* isClick */);
} else if (mQsExpansionEnabled) {
- flingSettings(0 /* vel */, true /* expand */);
+ EventLogTags.writeSysuiLockscreenGesture(
+ EventLogConstants.SYSUI_TAP_TO_OPEN_QS,
+ 0, 0);
+ flingSettings(0 /* vel */, true /* expand */, null, true /* isClick */);
}
}
}
@@ -1690,7 +1920,7 @@ public class NotificationPanelView extends PanelView implements
if (start) {
EventLogTags.writeSysuiLockscreenGesture(
EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_SWIPE_DIALER, lengthDp, velocityDp);
- mKeyguardBottomArea.launchPhone();
+ mKeyguardBottomArea.launchLeftAffordance();
} else {
EventLogTags.writeSysuiLockscreenGesture(
EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_SWIPE_CAMERA, lengthDp, velocityDp);
@@ -1711,29 +1941,6 @@ public class NotificationPanelView extends PanelView implements
}
@Override
- protected void onEdgeClicked(boolean right) {
- if ((right && getRightIcon().getVisibility() != View.VISIBLE)
- || (!right && getLeftIcon().getVisibility() != View.VISIBLE)
- || isDozing()) {
- return;
- }
- mHintAnimationRunning = true;
- mAfforanceHelper.startHintAnimation(right, new Runnable() {
- @Override
- public void run() {
- mHintAnimationRunning = false;
- mStatusBar.onHintFinished();
- }
- });
- boolean start = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? right : !right;
- if (start) {
- mStatusBar.onPhoneHintStarted();
- } else {
- mStatusBar.onCameraHintStarted();
- }
- }
-
- @Override
protected void startUnlockHintAnimation() {
super.startUnlockHintAnimation();
startHighlightIconAnimation(getCenterIcon());
@@ -1747,30 +1954,66 @@ public class NotificationPanelView extends PanelView implements
mFastOutSlowInInterpolator, new Runnable() {
@Override
public void run() {
- icon.setImageAlpha(KeyguardAffordanceHelper.SWIPE_RESTING_ALPHA_AMOUNT,
- true, KeyguardAffordanceHelper.HINT_PHASE1_DURATION,
+ icon.setImageAlpha(icon.getRestingAlpha(),
+ true /* animate */, KeyguardAffordanceHelper.HINT_PHASE1_DURATION,
mFastOutSlowInInterpolator, null);
}
});
}
@Override
- public float getPageWidth() {
- return getWidth();
+ public float getMaxTranslationDistance() {
+ return (float) Math.hypot(getWidth(), getHeight());
}
@Override
- public void onSwipingStarted() {
- mSecureCameraLaunchManager.onSwipingStarted();
+ public void onSwipingStarted(boolean rightIcon) {
+ boolean camera = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? !rightIcon
+ : rightIcon;
+ if (camera) {
+ mSecureCameraLaunchManager.onSwipingStarted();
+ mKeyguardBottomArea.bindCameraPrewarmService();
+ }
requestDisallowInterceptTouchEvent(true);
mOnlyAffordanceInThisMotion = true;
+ mQsTracking = false;
+ }
+
+ @Override
+ public void onSwipingAborted() {
+ mKeyguardBottomArea.unbindCameraPrewarmService(false /* launched */);
+ }
+
+ @Override
+ public void onIconClicked(boolean rightIcon) {
+ if (mHintAnimationRunning) {
+ return;
+ }
+ mHintAnimationRunning = true;
+ mAfforanceHelper.startHintAnimation(rightIcon, new Runnable() {
+ @Override
+ public void run() {
+ mHintAnimationRunning = false;
+ mStatusBar.onHintFinished();
+ }
+ });
+ rightIcon = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? !rightIcon : rightIcon;
+ if (rightIcon) {
+ mStatusBar.onCameraHintStarted();
+ } else {
+ if (mKeyguardBottomArea.isLeftVoiceAssist()) {
+ mStatusBar.onVoiceAssistHintStarted();
+ } else {
+ mStatusBar.onPhoneHintStarted();
+ }
+ }
}
@Override
public KeyguardAffordanceView getLeftIcon() {
return getLayoutDirection() == LAYOUT_DIRECTION_RTL
- ? mKeyguardBottomArea.getCameraView()
- : mKeyguardBottomArea.getPhoneView();
+ ? mKeyguardBottomArea.getRightView()
+ : mKeyguardBottomArea.getLeftView();
}
@Override
@@ -1781,22 +2024,22 @@ public class NotificationPanelView extends PanelView implements
@Override
public KeyguardAffordanceView getRightIcon() {
return getLayoutDirection() == LAYOUT_DIRECTION_RTL
- ? mKeyguardBottomArea.getPhoneView()
- : mKeyguardBottomArea.getCameraView();
+ ? mKeyguardBottomArea.getLeftView()
+ : mKeyguardBottomArea.getRightView();
}
@Override
public View getLeftPreview() {
return getLayoutDirection() == LAYOUT_DIRECTION_RTL
- ? mKeyguardBottomArea.getCameraPreview()
- : mKeyguardBottomArea.getPhonePreview();
+ ? mKeyguardBottomArea.getRightPreview()
+ : mKeyguardBottomArea.getLeftPreview();
}
@Override
public View getRightPreview() {
return getLayoutDirection() == LAYOUT_DIRECTION_RTL
- ? mKeyguardBottomArea.getPhonePreview()
- : mKeyguardBottomArea.getCameraPreview();
+ ? mKeyguardBottomArea.getLeftPreview()
+ : mKeyguardBottomArea.getRightPreview();
}
@Override
@@ -1898,6 +2141,12 @@ public class NotificationPanelView extends PanelView implements
public void setDozing(boolean dozing, boolean animate) {
if (dozing == mDozing) return;
mDozing = dozing;
+ if (mStatusBarState == StatusBarState.KEYGUARD) {
+ updateDozingVisibilities(animate);
+ }
+ }
+
+ private void updateDozingVisibilities(boolean animate) {
if (mDozing) {
mKeyguardStatusBar.setVisibility(View.INVISIBLE);
mKeyguardBottomArea.setVisibility(View.INVISIBLE);
@@ -1905,7 +2154,7 @@ public class NotificationPanelView extends PanelView implements
mKeyguardBottomArea.setVisibility(View.VISIBLE);
mKeyguardStatusBar.setVisibility(View.VISIBLE);
if (animate) {
- animateKeyguardStatusBarIn();
+ animateKeyguardStatusBarIn(DOZE_ANIMATION_DURATION);
mKeyguardBottomArea.startFinishDozeAnimation();
}
}
@@ -1955,6 +2204,32 @@ public class NotificationPanelView extends PanelView implements
onEmptySpaceClick(x);
}
+ protected boolean onMiddleClicked() {
+ switch (mStatusBar.getBarState()) {
+ case StatusBarState.KEYGUARD:
+ if (!mDozingOnDown) {
+ EventLogTags.writeSysuiLockscreenGesture(
+ EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_TAP_UNLOCK_HINT,
+ 0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
+ startUnlockHintAnimation();
+ }
+ return true;
+ case StatusBarState.SHADE_LOCKED:
+ if (!mQsExpanded) {
+ mStatusBar.goToKeyguard();
+ }
+ return true;
+ case StatusBarState.SHADE:
+
+ // This gets called in the middle of the touch handling, where the state is still
+ // that we are tracking the panel. Collapse the panel after this is done.
+ post(mPostCollapseRunnable);
+ return false;
+ default:
+ return true;
+ }
+ }
+
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
@@ -1980,4 +2255,101 @@ public class NotificationPanelView extends PanelView implements
mNotificationStackScroller.getTopPadding(), p);
}
}
+
+ @Override
+ public void onHeadsUpPinnedModeChanged(final boolean inPinnedMode) {
+ if (inPinnedMode) {
+ mHeadsUpExistenceChangedRunnable.run();
+ updateNotificationTranslucency();
+ } else {
+ mNotificationStackScroller.runAfterAnimationFinished(
+ mHeadsUpExistenceChangedRunnable);
+ }
+ }
+
+ @Override
+ public void onHeadsUpPinned(ExpandableNotificationRow headsUp) {
+ mNotificationStackScroller.generateHeadsUpAnimation(headsUp, true);
+ }
+
+ @Override
+ public void onHeadsUpUnPinned(ExpandableNotificationRow headsUp) {
+ }
+
+ @Override
+ public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) {
+ mNotificationStackScroller.generateHeadsUpAnimation(entry.row, isHeadsUp);
+ }
+
+ @Override
+ public void setHeadsUpManager(HeadsUpManager headsUpManager) {
+ super.setHeadsUpManager(headsUpManager);
+ mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager, mNotificationStackScroller,
+ this);
+ }
+
+ public void setTrackingHeadsUp(boolean tracking) {
+ if (tracking) {
+ mNotificationStackScroller.setTrackingHeadsUp(true);
+ mExpandingFromHeadsUp = true;
+ }
+ // otherwise we update the state when the expansion is finished
+ }
+
+ @Override
+ protected void onClosingFinished() {
+ super.onClosingFinished();
+ resetVerticalPanelPosition();
+ setClosingWithAlphaFadeout(false);
+ }
+
+ private void setClosingWithAlphaFadeout(boolean closing) {
+ mClosingWithAlphaFadeOut = closing;
+ mNotificationStackScroller.forceNoOverlappingRendering(closing);
+ }
+
+ /**
+ * Updates the vertical position of the panel so it is positioned closer to the touch
+ * responsible for opening the panel.
+ *
+ * @param x the x-coordinate the touch event
+ */
+ private void updateVerticalPanelPosition(float x) {
+ if (mNotificationStackScroller.getWidth() * 1.75f > getWidth()) {
+ resetVerticalPanelPosition();
+ return;
+ }
+ float leftMost = mPositionMinSideMargin + mNotificationStackScroller.getWidth() / 2;
+ float rightMost = getWidth() - mPositionMinSideMargin
+ - mNotificationStackScroller.getWidth() / 2;
+ if (Math.abs(x - getWidth() / 2) < mNotificationStackScroller.getWidth() / 4) {
+ x = getWidth() / 2;
+ }
+ x = Math.min(rightMost, Math.max(leftMost, x));
+ setVerticalPanelTranslation(x -
+ (mNotificationStackScroller.getLeft() + mNotificationStackScroller.getWidth() / 2));
+ }
+
+ private void resetVerticalPanelPosition() {
+ setVerticalPanelTranslation(0f);
+ }
+
+ private void setVerticalPanelTranslation(float translation) {
+ mNotificationStackScroller.setTranslationX(translation);
+ mScrollView.setTranslationX(translation);
+ mHeader.setTranslationX(translation);
+ }
+
+ private void updateStackHeight(float stackHeight) {
+ mNotificationStackScroller.setStackHeight(stackHeight);
+ updateKeyguardBottomAreaAlpha();
+ }
+
+ public void setPanelScrimMinFraction(float minFraction) {
+ mBar.panelScrimMinFractionChanged(minFraction);
+ }
+
+ public void clearNotificattonEffects() {
+ mStatusBar.clearNotificationEffects();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
index e7b0c4c..cbb71c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
@@ -18,7 +18,6 @@ package com.android.systemui.statusbar.phone;
import android.content.Context;
import android.graphics.Canvas;
-import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewStub;
@@ -38,6 +37,7 @@ public class NotificationsQuickSettingsContainer extends FrameLayout
private View mStackScroller;
private View mKeyguardStatusBar;
private boolean mInflated;
+ private boolean mQsExpanded;
public NotificationsQuickSettingsContainer(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -65,26 +65,29 @@ public class NotificationsQuickSettingsContainer extends FrameLayout
boolean userSwitcherVisible = mInflated && mUserSwitcher.getVisibility() == View.VISIBLE;
boolean statusBarVisible = mKeyguardStatusBar.getVisibility() == View.VISIBLE;
+ View stackQsTop = mQsExpanded ? mStackScroller : mScrollView;
+ View stackQsBottom = !mQsExpanded ? mStackScroller : mScrollView;
// Invert the order of the scroll view and user switcher such that the notifications receive
// touches first but the panel gets drawn above.
if (child == mScrollView) {
- return super.drawChild(canvas, mStackScroller, drawingTime);
- } else if (child == mStackScroller) {
- return super.drawChild(canvas,
- userSwitcherVisible && statusBarVisible ? mUserSwitcher
+ return super.drawChild(canvas, userSwitcherVisible && statusBarVisible ? mUserSwitcher
: statusBarVisible ? mKeyguardStatusBar
: userSwitcherVisible ? mUserSwitcher
- : mScrollView,
+ : stackQsBottom, drawingTime);
+ } else if (child == mStackScroller) {
+ return super.drawChild(canvas,
+ userSwitcherVisible && statusBarVisible ? mKeyguardStatusBar
+ : statusBarVisible || userSwitcherVisible ? stackQsBottom
+ : stackQsTop,
drawingTime);
} else if (child == mUserSwitcher) {
return super.drawChild(canvas,
- userSwitcherVisible && statusBarVisible ? mKeyguardStatusBar
- : mScrollView,
+ userSwitcherVisible && statusBarVisible ? stackQsBottom
+ : stackQsTop,
drawingTime);
} else if (child == mKeyguardStatusBar) {
return super.drawChild(canvas,
- userSwitcherVisible && statusBarVisible ? mScrollView
- : mScrollView,
+ stackQsTop,
drawingTime);
}else {
return super.drawChild(canvas, child, drawingTime);
@@ -98,4 +101,11 @@ public class NotificationsQuickSettingsContainer extends FrameLayout
mInflated = true;
}
}
+
+ public void setQsExpanded(boolean expanded) {
+ if (mQsExpanded != expanded) {
+ mQsExpanded = expanded;
+ invalidate();
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ObservableScrollView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ObservableScrollView.java
index 1186a33..9e5cefd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ObservableScrollView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ObservableScrollView.java
@@ -99,7 +99,7 @@ public class ObservableScrollView extends ScrollView {
} else if (!mTouchEnabled) {
MotionEvent cancel = MotionEvent.obtain(ev);
cancel.setAction(MotionEvent.ACTION_CANCEL);
- super.dispatchTouchEvent(ev);
+ super.dispatchTouchEvent(cancel);
cancel.recycle();
mTouchCancelled = true;
return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
index 3efaaff..e1a400d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
@@ -25,9 +25,11 @@ import android.widget.FrameLayout;
import java.util.ArrayList;
-public class PanelBar extends FrameLayout {
+public abstract class PanelBar extends FrameLayout {
public static final boolean DEBUG = false;
public static final String TAG = PanelBar.class.getSimpleName();
+ private static final boolean SPEW = false;
+
public static final void LOG(String fmt, Object... args) {
if (!DEBUG) return;
Log.v(TAG, String.format(fmt, args));
@@ -80,6 +82,17 @@ public class PanelBar extends FrameLayout {
}
}
+ public void setBouncerShowing(boolean showing) {
+ int important = showing ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+ : IMPORTANT_FOR_ACCESSIBILITY_AUTO;
+
+ setImportantForAccessibility(important);
+
+ if (mPanelHolder != null) {
+ mPanelHolder.setImportantForAccessibility(important);
+ }
+ }
+
public float getBarHeight() {
return getMeasuredHeight();
}
@@ -140,11 +153,13 @@ public class PanelBar extends FrameLayout {
mPanelHolder.setSelectedPanel(mTouchingPanel);
for (PanelView pv : mPanels) {
if (pv != panel) {
- pv.collapse(false /* delayed */);
+ pv.collapse(false /* delayed */, 1.0f /* speedUpFactor */);
}
}
}
+ public abstract void panelScrimMinFractionChanged(float minFraction);
+
/**
* @param panel the panel which changed its expansion state
* @param frac the fraction from the expansion in [0, 1]
@@ -154,11 +169,10 @@ public class PanelBar extends FrameLayout {
public void panelExpansionChanged(PanelView panel, float frac, boolean expanded) {
boolean fullyClosed = true;
PanelView fullyOpenedPanel = null;
- if (DEBUG) LOG("panelExpansionChanged: start state=%d panel=%s", mState, panel.getName());
+ if (SPEW) LOG("panelExpansionChanged: start state=%d panel=%s", mState, panel.getName());
mPanelExpandedFractionSum = 0f;
for (PanelView pv : mPanels) {
- boolean visible = pv.getExpandedHeight() > 0;
- pv.setVisibility(visible ? View.VISIBLE : View.GONE);
+ pv.setVisibility(expanded ? View.VISIBLE : View.INVISIBLE);
// adjust any other panels that may be partially visible
if (expanded) {
if (mState == STATE_CLOSED) {
@@ -167,8 +181,8 @@ public class PanelBar extends FrameLayout {
}
fullyClosed = false;
final float thisFrac = pv.getExpandedFraction();
- mPanelExpandedFractionSum += (visible ? thisFrac : 0);
- if (DEBUG) LOG("panelExpansionChanged: -> %s: f=%.1f", pv.getName(), thisFrac);
+ mPanelExpandedFractionSum += thisFrac;
+ if (SPEW) LOG("panelExpansionChanged: -> %s: f=%.1f", pv.getName(), thisFrac);
if (panel == pv) {
if (thisFrac == 1f) fullyOpenedPanel = panel;
}
@@ -183,20 +197,19 @@ public class PanelBar extends FrameLayout {
onAllPanelsCollapsed();
}
- if (DEBUG) LOG("panelExpansionChanged: end state=%d [%s%s ]", mState,
+ if (SPEW) LOG("panelExpansionChanged: end state=%d [%s%s ]", mState,
(fullyOpenedPanel!=null)?" fullyOpened":"", fullyClosed?" fullyClosed":"");
}
- public void collapseAllPanels(boolean animate) {
+ public void collapseAllPanels(boolean animate, boolean delayed, float speedUpFactor) {
boolean waiting = false;
for (PanelView pv : mPanels) {
if (animate && !pv.isFullyCollapsed()) {
- pv.collapse(true /* delayed */);
+ pv.collapse(delayed, speedUpFactor);
waiting = true;
} else {
pv.resetViews();
pv.setExpandedFraction(0); // just in case
- pv.setVisibility(View.GONE);
pv.cancelPeek();
}
}
@@ -223,10 +236,6 @@ public class PanelBar extends FrameLayout {
public void onTrackingStarted(PanelView panel) {
mTracking = true;
- if (DEBUG && panel != mTouchingPanel) {
- LOG("shouldn't happen: onTrackingStarted(%s) != mTouchingPanel(%s)",
- panel, mTouchingPanel);
- }
}
public void onTrackingStopped(PanelView panel, boolean expand) {
@@ -234,7 +243,7 @@ public class PanelBar extends FrameLayout {
}
public void onExpandingFinished() {
-
+ if (DEBUG) LOG("onExpandingFinished");
}
public void onClosingFinished() {
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 d86ccee..9343172 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -38,6 +38,7 @@ import com.android.systemui.R;
import com.android.systemui.doze.DozeLog;
import com.android.systemui.statusbar.FlingAnimationUtils;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -51,10 +52,13 @@ public abstract class PanelView extends FrameLayout {
}
protected PhoneStatusBar mStatusBar;
+ protected HeadsUpManager mHeadsUpManager;
+
private float mPeekHeight;
private float mHintDistance;
private int mEdgeTapAreaWidth;
private float mInitialOffsetOnTouch;
+ private boolean mCollapsedAndHeadsUpOnDown;
private float mExpandedFraction = 0;
protected float mExpandedHeight = 0;
private boolean mPanelClosedOnDown;
@@ -73,6 +77,9 @@ public abstract class PanelView extends FrameLayout {
private boolean mTouchAboveFalsingThreshold;
private int mUnlockFalsingThreshold;
private boolean mTouchStartedInEmptyArea;
+ private boolean mMotionAborted;
+ private boolean mUpwardsWhenTresholdReached;
+ private boolean mAnimatingOnDown;
private ValueAnimator mHeightAnimator;
private ObjectAnimator mPeekAnimator;
@@ -98,9 +105,15 @@ public abstract class PanelView extends FrameLayout {
private boolean mPeekPending;
private boolean mCollapseAfterPeek;
+
+ /**
+ * Speed-up factor to be used when {@link #mFlingCollapseRunnable} runs the next time.
+ */
+ private float mNextCollapseSpeedUpFactor = 1.0f;
+
private boolean mExpanding;
private boolean mGestureWaitForTouchSlop;
- private boolean mDozingOnDown;
+ private boolean mIgnoreXTouchSlop;
private Runnable mPeekRunnable = new Runnable() {
@Override
public void run() {
@@ -159,12 +172,7 @@ public abstract class PanelView extends FrameLayout {
public void onAnimationEnd(Animator animation) {
mPeekAnimator = null;
if (mCollapseAfterPeek && !mCancelled) {
- postOnAnimation(new Runnable() {
- @Override
- public void run() {
- collapse(false /* delayed */);
- }
- });
+ postOnAnimation(mPostCollapseRunnable);
}
mCollapseAfterPeek = false;
}
@@ -209,7 +217,8 @@ public abstract class PanelView extends FrameLayout {
@Override
public boolean onTouchEvent(MotionEvent event) {
- if (mInstantExpanding || mTouchDisabled) {
+ if (mInstantExpanding || mTouchDisabled
+ || (mMotionAborted && event.getActionMasked() != MotionEvent.ACTION_DOWN)) {
return false;
}
@@ -227,32 +236,31 @@ public abstract class PanelView extends FrameLayout {
pointerIndex = 0;
mTrackingPointer = event.getPointerId(pointerIndex);
}
- final float y = event.getY(pointerIndex);
final float x = event.getX(pointerIndex);
+ final float y = event.getY(pointerIndex);
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
- mGestureWaitForTouchSlop = mExpandedHeight == 0f;
+ mGestureWaitForTouchSlop = isFullyCollapsed() || hasConflictingGestures();
+ mIgnoreXTouchSlop = isFullyCollapsed() || shouldGestureIgnoreXTouchSlop(x, y);
}
- boolean waitForTouchSlop = hasConflictingGestures() || mGestureWaitForTouchSlop;
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
- mInitialTouchY = y;
- mInitialTouchX = x;
- mInitialOffsetOnTouch = mExpandedHeight;
- mTouchSlopExceeded = false;
+ startExpandMotion(x, y, false /* startTracking */, mExpandedHeight);
mJustPeeked = false;
- mPanelClosedOnDown = mExpandedHeight == 0.0f;
+ mPanelClosedOnDown = isFullyCollapsed();
mHasLayoutedSinceDown = false;
mUpdateFlingOnLayout = false;
+ mMotionAborted = false;
mPeekTouching = mPanelClosedOnDown;
mTouchAboveFalsingThreshold = false;
- mDozingOnDown = isDozing();
+ mCollapsedAndHeadsUpOnDown = isFullyCollapsed()
+ && mHeadsUpManager.hasPinnedHeadsUp();
if (mVelocityTracker == null) {
initVelocityTracker();
}
trackMovement(event);
- if (!waitForTouchSlop || (mHeightAnimator != null && !mHintAnimationRunning) ||
+ if (!mGestureWaitForTouchSlop || (mHeightAnimator != null && !mHintAnimationRunning) ||
mPeekPending || mPeekAnimator != null) {
cancelHeightAnimator();
cancelPeek();
@@ -260,7 +268,7 @@ public abstract class PanelView extends FrameLayout {
|| mPeekPending || mPeekAnimator != null;
onTrackingStarted();
}
- if (mExpandedHeight == 0) {
+ if (isFullyCollapsed() && !mHeadsUpManager.hasPinnedHeadsUp()) {
schedulePeek();
}
break;
@@ -273,12 +281,16 @@ public abstract class PanelView extends FrameLayout {
final float newY = event.getY(newIndex);
final float newX = event.getX(newIndex);
mTrackingPointer = event.getPointerId(newIndex);
- mInitialOffsetOnTouch = mExpandedHeight;
- mInitialTouchY = newY;
- mInitialTouchX = newX;
+ startExpandMotion(newX, newY, true /* startTracking */, mExpandedHeight);
+ }
+ break;
+ case MotionEvent.ACTION_POINTER_DOWN:
+ if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
+ mMotionAborted = true;
+ endMotionEvent(event, x, y, true /* forceCancel */);
+ return false;
}
break;
-
case MotionEvent.ACTION_MOVE:
float h = y - mInitialTouchY;
@@ -286,13 +298,11 @@ public abstract class PanelView extends FrameLayout {
// y-component of the gesture, as we have no conflicting horizontal gesture.
if (Math.abs(h) > mTouchSlop
&& (Math.abs(h) > Math.abs(x - mInitialTouchX)
- || mInitialOffsetOnTouch == 0f)) {
+ || mIgnoreXTouchSlop)) {
mTouchSlopExceeded = true;
- if (waitForTouchSlop && !mTracking) {
+ if (mGestureWaitForTouchSlop && !mTracking && !mCollapsedAndHeadsUpOnDown) {
if (!mJustPeeked && mInitialOffsetOnTouch != 0f) {
- mInitialOffsetOnTouch = mExpandedHeight;
- mInitialTouchX = x;
- mInitialTouchY = y;
+ startExpandMotion(x, y, false /* startTracking */, mExpandedHeight);
h = 0;
}
cancelHeightAnimator();
@@ -310,8 +320,9 @@ public abstract class PanelView extends FrameLayout {
}
if (-h >= getFalsingThreshold()) {
mTouchAboveFalsingThreshold = true;
+ mUpwardsWhenTresholdReached = isDirectionUpwards(x, y);
}
- if (!mJustPeeked && (!waitForTouchSlop || mTracking) && !isTrackingBlocked()) {
+ if (!mJustPeeked && (!mGestureWaitForTouchSlop || mTracking) && !isTrackingBlocked()) {
setExpandedHeightInternal(newHeight);
}
@@ -320,26 +331,58 @@ public abstract class PanelView extends FrameLayout {
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
- mTrackingPointer = -1;
trackMovement(event);
- if ((mTracking && mTouchSlopExceeded)
- || Math.abs(x - mInitialTouchX) > mTouchSlop
- || Math.abs(y - mInitialTouchY) > mTouchSlop
- || event.getActionMasked() == MotionEvent.ACTION_CANCEL) {
- float vel = 0f;
- float vectorVel = 0f;
- if (mVelocityTracker != null) {
- mVelocityTracker.computeCurrentVelocity(1000);
- vel = mVelocityTracker.getYVelocity();
- vectorVel = (float) Math.hypot(
- mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
- }
- boolean expand = flingExpands(vel, vectorVel)
- || event.getActionMasked() == MotionEvent.ACTION_CANCEL;
- onTrackingStopped(expand);
- DozeLog.traceFling(expand, mTouchAboveFalsingThreshold,
- mStatusBar.isFalsingThresholdNeeded(),
- mStatusBar.isScreenOnComingFromTouch());
+ endMotionEvent(event, x, y, false /* forceCancel */);
+ break;
+ }
+ return !mGestureWaitForTouchSlop || mTracking;
+ }
+
+ /**
+ * @return whether the swiping direction is upwards and above a 45 degree angle compared to the
+ * horizontal direction
+ */
+ private boolean isDirectionUpwards(float x, float y) {
+ float xDiff = x - mInitialTouchX;
+ float yDiff = y - mInitialTouchY;
+ if (yDiff >= 0) {
+ return false;
+ }
+ return Math.abs(yDiff) >= Math.abs(xDiff);
+ }
+
+ protected void startExpandMotion(float newX, float newY, boolean startTracking,
+ float expandedHeight) {
+ mInitialOffsetOnTouch = expandedHeight;
+ mInitialTouchY = newY;
+ mInitialTouchX = newX;
+ if (startTracking) {
+ mTouchSlopExceeded = true;
+ onTrackingStarted();
+ }
+ }
+
+ private void endMotionEvent(MotionEvent event, float x, float y, boolean forceCancel) {
+ mTrackingPointer = -1;
+ if ((mTracking && mTouchSlopExceeded)
+ || Math.abs(x - mInitialTouchX) > mTouchSlop
+ || Math.abs(y - mInitialTouchY) > mTouchSlop
+ || event.getActionMasked() == MotionEvent.ACTION_CANCEL
+ || forceCancel) {
+ float vel = 0f;
+ float vectorVel = 0f;
+ if (mVelocityTracker != null) {
+ mVelocityTracker.computeCurrentVelocity(1000);
+ vel = mVelocityTracker.getYVelocity();
+ vectorVel = (float) Math.hypot(
+ mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
+ }
+ boolean expand = flingExpands(vel, vectorVel, x, y)
+ || event.getActionMasked() == MotionEvent.ACTION_CANCEL
+ || forceCancel;
+ DozeLog.traceFling(expand, mTouchAboveFalsingThreshold,
+ mStatusBar.isFalsingThresholdNeeded(),
+ mStatusBar.isScreenOnComingFromTouch());
// Log collapse gesture if on lock screen.
if (!expand && mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
float displayDensity = mStatusBar.getDisplayDensity();
@@ -349,24 +392,22 @@ public abstract class PanelView extends FrameLayout {
EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_SWIPE_UP_UNLOCK,
heightDp, velocityDp);
}
- fling(vel, expand);
- mUpdateFlingOnLayout = expand && mPanelClosedOnDown && !mHasLayoutedSinceDown;
- if (mUpdateFlingOnLayout) {
- mUpdateFlingVelocity = vel;
- }
- } else {
- boolean expands = onEmptySpaceClick(mInitialTouchX);
- onTrackingStopped(expands);
- }
+ fling(vel, expand, isFalseTouch(x, y));
+ onTrackingStopped(expand);
+ mUpdateFlingOnLayout = expand && mPanelClosedOnDown && !mHasLayoutedSinceDown;
+ if (mUpdateFlingOnLayout) {
+ mUpdateFlingVelocity = vel;
+ }
+ } else {
+ boolean expands = onEmptySpaceClick(mInitialTouchX);
+ onTrackingStopped(expands);
+ }
- if (mVelocityTracker != null) {
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- }
- mPeekTouching = false;
- break;
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
}
- return !waitForTouchSlop || mTracking;
+ mPeekTouching = false;
}
private int getFalsingThreshold() {
@@ -376,9 +417,12 @@ public abstract class PanelView extends FrameLayout {
protected abstract boolean hasConflictingGestures();
+ protected abstract boolean shouldGestureIgnoreXTouchSlop(float x, float y);
+
protected void onTrackingStopped(boolean expand) {
mTracking = false;
mBar.onTrackingStopped(PanelView.this, expand);
+ notifyBarPanelExpansionChanged();
}
protected void onTrackingStarted() {
@@ -387,11 +431,13 @@ public abstract class PanelView extends FrameLayout {
mCollapseAfterPeek = false;
mBar.onTrackingStarted(PanelView.this);
notifyExpandingStarted();
+ notifyBarPanelExpansionChanged();
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
- if (mInstantExpanding) {
+ if (mInstantExpanding
+ || (mMotionAborted && event.getActionMasked() != MotionEvent.ACTION_DOWN)) {
return false;
}
@@ -415,8 +461,8 @@ public abstract class PanelView extends FrameLayout {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
mStatusBar.userActivity();
- if (mHeightAnimator != null && !mHintAnimationRunning ||
- mPeekPending || mPeekAnimator != null) {
+ mAnimatingOnDown = mHeightAnimator != null;
+ if (mAnimatingOnDown && mClosing && !mHintAnimationRunning || mPeekPending || mPeekAnimator != null) {
cancelHeightAnimator();
cancelPeek();
mTouchSlopExceeded = true;
@@ -427,11 +473,12 @@ public abstract class PanelView extends FrameLayout {
mTouchStartedInEmptyArea = !isInContentBounds(x, y);
mTouchSlopExceeded = false;
mJustPeeked = false;
- mPanelClosedOnDown = mExpandedHeight == 0.0f;
+ mMotionAborted = false;
+ mPanelClosedOnDown = isFullyCollapsed();
+ mCollapsedAndHeadsUpOnDown = false;
mHasLayoutedSinceDown = false;
mUpdateFlingOnLayout = false;
mTouchAboveFalsingThreshold = false;
- mDozingOnDown = isDozing();
initVelocityTracker();
trackMovement(event);
break;
@@ -445,25 +492,34 @@ public abstract class PanelView extends FrameLayout {
mInitialTouchY = event.getY(newIndex);
}
break;
-
+ case MotionEvent.ACTION_POINTER_DOWN:
+ if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
+ mMotionAborted = true;
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ }
+ break;
case MotionEvent.ACTION_MOVE:
final float h = y - mInitialTouchY;
trackMovement(event);
- if (scrolledToBottom || mTouchStartedInEmptyArea) {
- if (h < -mTouchSlop && h < -Math.abs(x - mInitialTouchX)) {
+ if (scrolledToBottom || mTouchStartedInEmptyArea || mAnimatingOnDown) {
+ float hAbs = Math.abs(h);
+ if ((h < -mTouchSlop || (mAnimatingOnDown && hAbs > mTouchSlop))
+ && hAbs > Math.abs(x - mInitialTouchX)) {
cancelHeightAnimator();
- mInitialOffsetOnTouch = mExpandedHeight;
- mInitialTouchY = y;
- mInitialTouchX = x;
- mTracking = true;
- mTouchSlopExceeded = true;
- onTrackingStarted();
+ startExpandMotion(x, y, true /* startTracking */, mExpandedHeight);
return true;
}
}
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
break;
}
return false;
@@ -474,7 +530,7 @@ public abstract class PanelView extends FrameLayout {
*/
protected abstract boolean isInContentBounds(float x, float y);
- private void cancelHeightAnimator() {
+ protected void cancelHeightAnimator() {
if (mHeightAnimator != null) {
mHeightAnimator.cancel();
}
@@ -520,8 +576,8 @@ public abstract class PanelView extends FrameLayout {
* @param vectorVel the length of the vectorial velocity
* @return whether a fling should expands the panel; contracts otherwise
*/
- protected boolean flingExpands(float vel, float vectorVel) {
- if (isBelowFalsingThreshold()) {
+ protected boolean flingExpands(float vel, float vectorVel, float x, float y) {
+ if (isFalseTouch(x, y)) {
return true;
}
if (Math.abs(vectorVel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
@@ -531,14 +587,44 @@ public abstract class PanelView extends FrameLayout {
}
}
- private boolean isBelowFalsingThreshold() {
- return !mTouchAboveFalsingThreshold && mStatusBar.isFalsingThresholdNeeded();
+ /**
+ * @param x the final x-coordinate when the finger was lifted
+ * @param y the final y-coordinate when the finger was lifted
+ * @return whether this motion should be regarded as a false touch
+ */
+ private boolean isFalseTouch(float x, float y) {
+ if (!mStatusBar.isFalsingThresholdNeeded()) {
+ return false;
+ }
+ if (!mTouchAboveFalsingThreshold) {
+ return true;
+ }
+ if (mUpwardsWhenTresholdReached) {
+ return false;
+ }
+ return !isDirectionUpwards(x, y);
}
protected void fling(float vel, boolean expand) {
+ fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, false);
+ }
+
+ protected void fling(float vel, boolean expand, boolean expandBecauseOfFalsing) {
+ fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, expandBecauseOfFalsing);
+ }
+
+ protected void fling(float vel, boolean expand, float collapseSpeedUpFactor,
+ boolean expandBecauseOfFalsing) {
cancelPeek();
float target = expand ? getMaxPanelHeight() : 0.0f;
+ if (!expand) {
+ mClosing = true;
+ }
+ flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing);
+ }
+ protected void flingToHeight(float vel, boolean expand, float target,
+ float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) {
// Hack to make the expand transition look nice when clear all button is visible - we make
// the animation only to the last notification, and then jump to the maximum panel height so
// clear all just fades in and the decelerating motion is towards the last notification.
@@ -555,12 +641,11 @@ public abstract class PanelView extends FrameLayout {
mOverExpandedBeforeFling = getOverExpansionAmount() > 0f;
ValueAnimator animator = createHeightAnimator(target);
if (expand) {
- boolean belowFalsingThreshold = isBelowFalsingThreshold();
- if (belowFalsingThreshold) {
+ if (expandBecauseOfFalsing) {
vel = 0;
}
mFlingAnimationUtils.apply(animator, mExpandedHeight, target, vel, getHeight());
- if (belowFalsingThreshold) {
+ if (expandBecauseOfFalsing) {
animator.setDuration(350);
}
} else {
@@ -570,7 +655,13 @@ public abstract class PanelView extends FrameLayout {
// Make it shorter if we run a canned animation
if (vel == 0) {
animator.setDuration((long)
- (animator.getDuration() * getCannedFlingDurationFactor()));
+ (animator.getDuration() * getCannedFlingDurationFactor()
+ / collapseSpeedUpFactor));
+ }
+ if (PhoneStatusBar.DEBUG_EMPTY_KEYGUARD
+ && mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
+ Log.i(PhoneStatusBar.TAG, "Panel collapsed! Stacktrace: "
+ + Log.getStackTraceString(new Throwable()));
}
}
animator.addListener(new AnimatorListenerAdapter() {
@@ -590,6 +681,7 @@ public abstract class PanelView extends FrameLayout {
if (!mCancelled) {
notifyExpandingFinished();
}
+ notifyBarPanelExpansionChanged();
}
});
mHeightAnimator = animator;
@@ -618,7 +710,7 @@ public abstract class PanelView extends FrameLayout {
mHasLayoutedSinceDown = true;
if (mUpdateFlingOnLayout) {
abortAnimations();
- fling(mUpdateFlingVelocity, true);
+ fling(mUpdateFlingVelocity, true /* expands */);
mUpdateFlingOnLayout = false;
}
}
@@ -629,7 +721,7 @@ public abstract class PanelView extends FrameLayout {
// If the user isn't actively poking us, let's update the height
if ((!mTracking || isTrackingBlocked())
&& mHeightAnimator == null
- && mExpandedHeight > 0
+ && !isFullyCollapsed()
&& currentMaxPanelHeight != mExpandedHeight
&& !mPeekPending
&& mPeekAnimator == null
@@ -685,6 +777,15 @@ public abstract class PanelView extends FrameLayout {
public void setExpandedFraction(float frac) {
setExpandedHeight(getMaxPanelHeight() * frac);
+ if (PhoneStatusBar.DEBUG_EMPTY_KEYGUARD
+ && mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
+ if (frac == 0.0f) {
+ Log.i(PhoneStatusBar.TAG, "Panel collapsed! Stacktrace: "
+ + Log.getStackTraceString(new Throwable()));
+ } else if (frac == 1.0f) {
+ mStatusBar.endWindowManagerLogging();
+ }
+ }
}
public float getExpandedHeight() {
@@ -715,7 +816,12 @@ public abstract class PanelView extends FrameLayout {
mBar = panelBar;
}
- public void collapse(boolean delayed) {
+ public void collapse(boolean delayed, float speedUpFactor) {
+ if (PhoneStatusBar.DEBUG_EMPTY_KEYGUARD
+ && mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
+ Log.i(PhoneStatusBar.TAG, "Panel collapsed! Stacktrace: "
+ + Log.getStackTraceString(new Throwable()));
+ }
if (DEBUG) logf("collapse: " + this);
if (mPeekPending || mPeekAnimator != null) {
mCollapseAfterPeek = true;
@@ -728,12 +834,15 @@ public abstract class PanelView extends FrameLayout {
}
} else if (!isFullyCollapsed() && !mTracking && !mClosing) {
cancelHeightAnimator();
- mClosing = true;
notifyExpandingStarted();
+
+ // Set after notifyExpandingStarted, as notifyExpandingStarted resets the closing state.
+ mClosing = true;
if (delayed) {
+ mNextCollapseSpeedUpFactor = speedUpFactor;
postDelayed(mFlingCollapseRunnable, 120);
} else {
- fling(0, false /* expand */);
+ fling(0, false /* expand */, speedUpFactor, false /* expandBecauseOfFalsing */);
}
}
}
@@ -741,7 +850,8 @@ public abstract class PanelView extends FrameLayout {
private final Runnable mFlingCollapseRunnable = new Runnable() {
@Override
public void run() {
- fling(0, false /* expand */);
+ fling(0, false /* expand */, mNextCollapseSpeedUpFactor,
+ false /* expandBecauseOfFalsing */);
}
};
@@ -779,7 +889,7 @@ public abstract class PanelView extends FrameLayout {
if (mExpanding) {
notifyExpandingFinished();
}
- setVisibility(VISIBLE);
+ notifyBarPanelExpansionChanged();
// Wait for window manager to pickup the change, so we know the maximum height of the panel
// then.
@@ -898,6 +1008,7 @@ public abstract class PanelView extends FrameLayout {
public void onAnimationEnd(Animator animation) {
mHeightAnimator = null;
onAnimationFinished.run();
+ notifyBarPanelExpansionChanged();
}
});
animator.start();
@@ -915,9 +1026,10 @@ public abstract class PanelView extends FrameLayout {
return animator;
}
- private void notifyBarPanelExpansionChanged() {
+ protected void notifyBarPanelExpansionChanged() {
mBar.panelExpansionChanged(this, mExpandedFraction, mExpandedFraction > 0f || mPeekPending
- || mPeekAnimator != null);
+ || mPeekAnimator != null || mInstantExpanding || mHeadsUpManager.hasPinnedHeadsUp()
+ || mTracking || mHeightAnimator != null);
}
/**
@@ -929,50 +1041,17 @@ public abstract class PanelView extends FrameLayout {
if (mHintAnimationRunning) {
return true;
}
- if (x < mEdgeTapAreaWidth
- && mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
- onEdgeClicked(false /* right */);
- return true;
- } else if (x > getWidth() - mEdgeTapAreaWidth
- && mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
- onEdgeClicked(true /* right */);
- return true;
- } else {
- return onMiddleClicked();
- }
+ return onMiddleClicked();
}
- private final Runnable mPostCollapseRunnable = new Runnable() {
+ protected final Runnable mPostCollapseRunnable = new Runnable() {
@Override
public void run() {
- collapse(false /* delayed */);
+ collapse(false /* delayed */, 1.0f /* speedUpFactor */);
}
};
- private boolean onMiddleClicked() {
- switch (mStatusBar.getBarState()) {
- case StatusBarState.KEYGUARD:
- if (!mDozingOnDown) {
- EventLogTags.writeSysuiLockscreenGesture(
- EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_TAP_UNLOCK_HINT,
- 0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
- startUnlockHintAnimation();
- }
- return true;
- case StatusBarState.SHADE_LOCKED:
- mStatusBar.goToKeyguard();
- return true;
- case StatusBarState.SHADE:
- // This gets called in the middle of the touch handling, where the state is still
- // that we are tracking the panel. Collapse the panel after this is done.
- post(mPostCollapseRunnable);
- return false;
- default:
- return true;
- }
- }
-
- protected abstract void onEdgeClicked(boolean right);
+ protected abstract boolean onMiddleClicked();
protected abstract boolean isDozing();
@@ -1009,4 +1088,8 @@ public abstract class PanelView extends FrameLayout {
* @return the height of the clear all button, in pixels
*/
protected abstract int getClearAllHeight();
+
+ public void setHeadsUpManager(HeadsUpManager headsUpManager) {
+ mHeadsUpManager = headsUpManager;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index f227107..a637e24 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -17,22 +17,8 @@
package com.android.systemui.statusbar.phone;
-import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT;
-import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
-import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
-import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
-import static android.app.StatusBarManager.windowStateToString;
-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;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSLUCENT;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_WARNING;
-
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.animation.TimeInterpolator;
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.ActivityManagerNative;
@@ -45,6 +31,7 @@ import android.content.ComponentCallbacks2;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.IPackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.ContentObserver;
@@ -75,6 +62,7 @@ import android.os.Message;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
@@ -82,40 +70,33 @@ import android.provider.Settings;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.StatusBarNotification;
-import android.text.TextUtils;
import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
import android.view.Display;
-import android.view.Gravity;
-import android.view.HardwareCanvas;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
+import android.view.ThreadedRenderer;
import android.view.VelocityTracker;
import android.view.View;
-import android.view.ViewGroup;
+import android.view.ViewConfiguration;
import android.view.ViewGroup.LayoutParams;
-import android.view.ViewPropertyAnimator;
import android.view.ViewStub;
-import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AccelerateInterpolator;
-import android.view.animation.Animation;
-import android.view.animation.AnimationUtils;
-import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.view.animation.PathInterpolator;
-import android.widget.FrameLayout;
import android.widget.ImageView;
-import android.widget.LinearLayout;
import android.widget.TextView;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.statusbar.NotificationVisibility;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.keyguard.KeyguardHostView.OnDismissAction;
import com.android.keyguard.ViewMediatorCallback;
@@ -123,13 +104,15 @@ import com.android.systemui.BatteryMeterView;
import com.android.systemui.DemoMode;
import com.android.systemui.EventLogConstants;
import com.android.systemui.EventLogTags;
-import com.android.systemui.FontSizeUtils;
+import com.android.systemui.Prefs;
import com.android.systemui.R;
+import com.android.systemui.SwipeHelper;
+import com.android.systemui.assist.AssistManager;
import com.android.systemui.doze.DozeHost;
import com.android.systemui.doze.DozeLog;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.qs.QSPanel;
-import com.android.systemui.recent.ScreenPinningRequest;
+import com.android.systemui.recents.ScreenPinningRequest;
import com.android.systemui.statusbar.ActivatableNotificationView;
import com.android.systemui.statusbar.BackDropView;
import com.android.systemui.statusbar.BaseStatusBar;
@@ -146,7 +129,6 @@ import com.android.systemui.statusbar.NotificationOverflowContainer;
import com.android.systemui.statusbar.ScrimView;
import com.android.systemui.statusbar.SignalClusterView;
import com.android.systemui.statusbar.SpeedBumpView;
-import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.UnlockMethodCache.OnUnlockMethodChangedListener;
import com.android.systemui.statusbar.policy.AccessibilityController;
@@ -156,7 +138,7 @@ import com.android.systemui.statusbar.policy.BluetoothControllerImpl;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.statusbar.policy.CastControllerImpl;
import com.android.systemui.statusbar.policy.FlashlightController;
-import com.android.systemui.statusbar.policy.HeadsUpNotificationView;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.HotspotControllerImpl;
import com.android.systemui.statusbar.policy.KeyButtonView;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
@@ -172,8 +154,7 @@ import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout.OnChildLocationsChangedListener;
-import com.android.systemui.statusbar.stack.StackScrollAlgorithm;
-import com.android.systemui.statusbar.stack.StackScrollState.ViewState;
+import com.android.systemui.statusbar.stack.StackViewState;
import com.android.systemui.volume.VolumeComponent;
import java.io.FileDescriptor;
@@ -181,13 +162,31 @@ import java.io.PrintWriter;
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.Map;
+import java.util.TreeSet;
+
+import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT;
+import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
+import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
+import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
+import static android.app.StatusBarManager.windowStateToString;
+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;
+import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT;
+import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSLUCENT;
+import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
+import static com.android.systemui.statusbar.phone.BarTransitions.MODE_WARNING;
public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
- DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener {
+ DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener,
+ HeadsUpManager.OnHeadsUpChangedListener {
static final String TAG = "PhoneStatusBar";
public static final boolean DEBUG = BaseStatusBar.DEBUG;
+ public static final boolean DEBUG_EMPTY_KEYGUARD = true;
public static final boolean SPEW = false;
public static final boolean DUMPTRUCK = true; // extra dumpsys info
public static final boolean DEBUG_GESTURES = false;
@@ -199,9 +198,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
// additional instrumentation for testing purposes; intended to be left on during development
public static final boolean CHATTY = DEBUG;
- public static final String ACTION_STATUSBAR_START
- = "com.android.internal.policy.statusbar.START";
-
public static final boolean SHOW_LOCKSCREEN_MEDIA_ARTWORK = true;
private static final int MSG_OPEN_NOTIFICATION_PANEL = 1000;
@@ -215,9 +211,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
private static final boolean CLOSE_PANEL_WHEN_EMPTIED = true;
- private static final int NOTIFICATION_PRIORITY_MULTIPLIER = 10; // see NotificationManagerService
- private static final int HIDE_ICONS_BELOW_SCORE = Notification.PRIORITY_LOW * NOTIFICATION_PRIORITY_MULTIPLIER;
-
private static final int STATUS_OR_NAV_TRANSIENT =
View.STATUS_BAR_TRANSIENT | View.NAVIGATION_BAR_TRANSIENT;
private static final long AUTOHIDE_TIMEOUT_MS = 3000;
@@ -241,6 +234,21 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
/** Allow some time inbetween the long press for back and recents. */
private static final int LOCK_TO_APP_GESTURE_TOLERENCE = 200;
+ /** If true, the system is in the half-boot-to-decryption-screen state.
+ * Prudently disable QS and notifications. */
+ private static final boolean ONLY_CORE_APPS;
+
+ static {
+ boolean onlyCoreApps;
+ try {
+ onlyCoreApps = IPackageManager.Stub.asInterface(ServiceManager.getService("package"))
+ .isOnlyCoreApps();
+ } catch (RemoteException e) {
+ onlyCoreApps = false;
+ }
+ ONLY_CORE_APPS = onlyCoreApps;
+ }
+
PhoneStatusBarPolicy mIconPolicy;
// These are no longer handled by the policy, because we need custom strategies for them
@@ -264,8 +272,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
AccessibilityController mAccessibilityController;
int mNaturalBarHeight = -1;
- int mIconSize = -1;
- int mIconHPadding = -1;
+
Display mDisplay;
Point mCurrentDisplaySize = new Point();
@@ -281,34 +288,14 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
int mPixelFormat;
Object mQueueLock = new Object();
- // viewgroup containing the normal contents of the statusbar
- LinearLayout mStatusBarContents;
-
- // right-hand icons
- LinearLayout mSystemIconArea;
- LinearLayout mSystemIcons;
-
- // left-hand icons
- LinearLayout mStatusIcons;
- LinearLayout mStatusIconsKeyguard;
-
- // the icons themselves
- IconMerger mNotificationIcons;
- View mNotificationIconArea;
-
- // [+>
- View mMoreIcon;
+ StatusBarIconController mIconController;
// expanded notifications
NotificationPanelView mNotificationPanel; // the sliding/resizing panel within the notification window
View mExpandedContents;
- int mNotificationPanelGravity;
- int mNotificationPanelMarginBottomPx;
- float mNotificationPanelMinHeightFrac;
TextView mNotificationPanelDebugText;
// settings
- View mFlipSettingsView;
private QSPanel mQSPanel;
// top bar
@@ -325,16 +312,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
int mKeyguardMaxNotificationCount;
- // carrier/wifi label
- private TextView mCarrierLabel;
- private boolean mCarrierLabelVisible = false;
- private int mCarrierLabelHeight;
- private int mStatusBarHeaderHeight;
-
- private boolean mShowCarrierInPanel = false;
-
- // position
- int[] mPositionTmp = new int[2];
boolean mExpandedVisible;
private int mNavigationBarWindowState = WINDOW_STATE_SHOWING;
@@ -342,14 +319,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
// the tracker view
int mTrackingPosition; // the position of the top of the tracking view.
- // ticker
- private boolean mTickerEnabled;
- private Ticker mTicker;
- private View mTickerView;
- private boolean mTicking;
-
// Tracking finger for opening/closing.
- int mEdgeBorder; // corresponds to R.dimen.status_bar_edge_ignore
boolean mTracking;
VelocityTracker mVelocityTracker;
@@ -357,11 +327,15 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>();
// for disabling the status bar
- int mDisabled = 0;
+ int mDisabled1 = 0;
+ int mDisabled2 = 0;
// tracking calls to View.setSystemUiVisibility()
int mSystemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE;
+ // last value sent to window manager
+ private int mLastDispatchedSystemUiVisibility = ~View.SYSTEM_UI_FLAG_VISIBLE;
+
DisplayMetrics mDisplayMetrics = new DisplayMetrics();
// XXX: gesture research
@@ -393,6 +367,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
if (!mUserSetup && mStatusBarView != null)
animateCollapseQuickSettings();
}
+ if (mIconPolicy != null) {
+ mIconPolicy.setCurrentUserSetup(mUserSetup);
+ }
}
};
@@ -410,11 +387,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
if (wasUsing != mUseHeadsUp) {
if (!mUseHeadsUp) {
Log.d(TAG, "dismissing any existing heads up notification on disable event");
- setHeadsUpVisibility(false);
- mHeadsUpNotificationView.release();
- removeHeadsUpView();
- } else {
- addHeadsUpView();
+ mHeadsUpManager.releaseAllImmediately();
}
}
}
@@ -440,9 +413,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
private boolean mWaitingForKeyguardExit;
private boolean mDozing;
+ private boolean mDozingRequested;
private boolean mScrimSrcModeEnabled;
- private Interpolator mLinearOutSlowIn;
private Interpolator mLinearInterpolator = new LinearInterpolator();
private Interpolator mBackdropInterpolator = new AccelerateDecelerateInterpolator();
public static final Interpolator ALPHA_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
@@ -482,10 +455,12 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
};
- private int mDisabledUnmodified;
+ private int mDisabledUnmodified1;
+ private int mDisabledUnmodified2;
/** Keys of notifications currently visible to the user. */
- private final ArraySet<String> mCurrentlyVisibleNotifications = new ArraySet<String>();
+ private final ArraySet<NotificationVisibility> mCurrentlyVisibleNotifications =
+ new ArraySet<>();
private long mLastVisibilityReportUptimeMs;
private final ShadeUpdates mShadeUpdates = new ShadeUpdates();
@@ -498,10 +473,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
// Fingerprint (as computed by getLoggingFingerprint() of the last logged state.
private int mLastLoggedStateFingerprint;
- private static final int VISIBLE_LOCATIONS = ViewState.LOCATION_FIRST_CARD
- | ViewState.LOCATION_TOP_STACK_PEEKING
- | ViewState.LOCATION_MAIN_AREA
- | ViewState.LOCATION_BOTTOM_STACK_PEEKING;
+ private static final int VISIBLE_LOCATIONS = StackViewState.LOCATION_FIRST_CARD
+ | StackViewState.LOCATION_MAIN_AREA;
private final OnChildLocationsChangedListener mNotificationLocationsChangedListener =
new OnChildLocationsChangedListener() {
@@ -526,12 +499,17 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
// Tracks notifications currently visible in mNotificationStackScroller and
// emits visibility events via NoMan on changes.
private final Runnable mVisibilityReporter = new Runnable() {
- private final ArrayList<String> mTmpNewlyVisibleNotifications = new ArrayList<String>();
- private final ArrayList<String> mTmpCurrentlyVisibleNotifications = new ArrayList<String>();
+ 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();
+ final String mediaKey = getCurrentMediaNotificationKey();
// 1. Loop over mNotificationData entries:
// A. Keep list of visible notifications.
@@ -546,37 +524,56 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
for (int i = 0; i < N; i++) {
Entry entry = activeNotifications.get(i);
String key = entry.notification.getKey();
- boolean previouslyVisible = mCurrentlyVisibleNotifications.contains(key);
- boolean currentlyVisible =
+ boolean isVisible =
(mStackScroller.getChildLocation(entry.row) & VISIBLE_LOCATIONS) != 0;
- if (currentlyVisible) {
+ NotificationVisibility visObj = NotificationVisibility.obtain(key, i, isVisible);
+ boolean previouslyVisible = mCurrentlyVisibleNotifications.contains(visObj);
+ if (isVisible) {
// Build new set of visible notifications.
- mTmpCurrentlyVisibleNotifications.add(key);
- }
- if (!previouslyVisible && currentlyVisible) {
- mTmpNewlyVisibleNotifications.add(key);
+ mTmpCurrentlyVisibleNotifications.add(visObj);
+ if (!previouslyVisible) {
+ mTmpNewlyVisibleNotifications.add(visObj);
+ }
+ } else {
+ // release object
+ visObj.recycle();
}
}
- ArraySet<String> noLongerVisibleNotifications = mCurrentlyVisibleNotifications;
- noLongerVisibleNotifications.removeAll(mTmpCurrentlyVisibleNotifications);
+ mTmpNoLongerVisibleNotifications.addAll(mCurrentlyVisibleNotifications);
+ mTmpNoLongerVisibleNotifications.removeAll(mTmpCurrentlyVisibleNotifications);
logNotificationVisibilityChanges(
- mTmpNewlyVisibleNotifications, noLongerVisibleNotifications);
+ mTmpNewlyVisibleNotifications, mTmpNoLongerVisibleNotifications);
- mCurrentlyVisibleNotifications.clear();
+ recycleAllVisibilityObjects(mCurrentlyVisibleNotifications);
mCurrentlyVisibleNotifications.addAll(mTmpCurrentlyVisibleNotifications);
- mTmpNewlyVisibleNotifications.clear();
+ recycleAllVisibilityObjects(mTmpNoLongerVisibleNotifications);
mTmpCurrentlyVisibleNotifications.clear();
+ mTmpNewlyVisibleNotifications.clear();
+ mTmpNoLongerVisibleNotifications.clear();
}
};
+ 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 mOverflowClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
goToLockedShade(null);
}
};
+ private HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>> mTmpChildOrderMap
+ = new HashMap<>();
+ private HashSet<Entry> mHeadsUpEntriesToRemoveOnSwitch = new HashSet<>();
+ private RankingMap mLatestRankingMap;
+ private boolean mNoAnimationOnNextBarModeChange;
@Override
public void start() {
@@ -585,6 +582,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
updateDisplaySize();
mScrimSrcModeEnabled = mContext.getResources().getBoolean(
R.bool.config_status_bar_scrim_behind_use_src);
+
super.start(); // calls createAndAddWindows()
mMediaSessionManager
@@ -596,6 +594,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
// Lastly, call to the icon policy to install/update all the icons.
mIconPolicy = new PhoneStatusBarPolicy(mContext, mCastController, mHotspotController);
+ mIconPolicy.setCurrentUserSetup(mUserSetup);
mSettingsObserver.onChange(false); // set up
mHeadsUpObserver.onChange(true); // set up
@@ -633,11 +632,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
updateDisplaySize(); // populates mDisplayMetrics
updateResources();
- mIconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size);
-
mStatusBarWindow = (StatusBarWindowView) View.inflate(context,
R.layout.super_status_bar, null);
- mStatusBarWindow.mService = this;
+ mStatusBarWindow.setService(this);
mStatusBarWindow.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
@@ -648,7 +645,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
}
return mStatusBarWindow.onTouchEvent(event);
- }});
+ }
+ });
mStatusBarView = (PhoneStatusBarView) mStatusBarWindow.findViewById(R.id.status_bar);
mStatusBarView.setBar(this);
@@ -662,23 +660,23 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
if (!ActivityManager.isHighEndGfx()) {
mStatusBarWindow.setBackground(null);
- mNotificationPanel.setBackground(new FastColorDrawable(context.getResources().getColor(
+ mNotificationPanel.setBackground(new FastColorDrawable(context.getColor(
R.color.notification_panel_solid_background)));
}
- if (ENABLE_HEADS_UP) {
- mHeadsUpNotificationView =
- (HeadsUpNotificationView) View.inflate(context, R.layout.heads_up, null);
- mHeadsUpNotificationView.setVisibility(View.GONE);
- mHeadsUpNotificationView.setBar(this);
- }
+
+ mHeadsUpManager = new HeadsUpManager(context, mStatusBarWindow);
+ mHeadsUpManager.setBar(this);
+ mHeadsUpManager.addListener(this);
+ mHeadsUpManager.addListener(mNotificationPanel);
+ mNotificationPanel.setHeadsUpManager(mHeadsUpManager);
+ mNotificationData.setHeadsUpManager(mHeadsUpManager);
+
if (MULTIUSER_DEBUG) {
mNotificationPanelDebugText = (TextView) mNotificationPanel.findViewById(
R.id.header_debug_info);
mNotificationPanelDebugText.setVisibility(View.VISIBLE);
}
- updateShowSearchHoldoff();
-
try {
boolean showNav = mWindowManagerService.hasNavigationBar();
if (DEBUG) Log.v(TAG, "hasNavigationBar=" + showNav);
@@ -686,14 +684,14 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mNavigationBarView =
(NavigationBarView) View.inflate(context, R.layout.navigation_bar, null);
- mNavigationBarView.setDisabledFlags(mDisabled);
+ mNavigationBarView.setDisabledFlags(mDisabled1);
mNavigationBarView.setBar(this);
mNavigationBarView.setOnVerticalChangedListener(
new NavigationBarView.OnVerticalChangedListener() {
@Override
public void onVerticalChanged(boolean isVertical) {
- if (mSearchPanelView != null) {
- mSearchPanelView.setHorizontal(isVertical);
+ if (mAssistManager != null) {
+ mAssistManager.onConfigurationChanged();
}
mNotificationPanel.setQsScrimEnabled(!isVertical);
}
@@ -709,29 +707,25 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
// no window manager? good luck with that
}
+ mAssistManager = new AssistManager(this, context);
+
// figure out which pixel-format to use for the status bar.
mPixelFormat = PixelFormat.OPAQUE;
- mSystemIconArea = (LinearLayout) mStatusBarView.findViewById(R.id.system_icon_area);
- mSystemIcons = (LinearLayout) mStatusBarView.findViewById(R.id.system_icons);
- mStatusIcons = (LinearLayout)mStatusBarView.findViewById(R.id.statusIcons);
- mNotificationIconArea = mStatusBarView.findViewById(R.id.notification_icon_area_inner);
- mNotificationIcons = (IconMerger)mStatusBarView.findViewById(R.id.notificationIcons);
- mMoreIcon = mStatusBarView.findViewById(R.id.moreIcon);
- mNotificationIcons.setOverflowIndicator(mMoreIcon);
- mStatusBarContents = (LinearLayout)mStatusBarView.findViewById(R.id.status_bar_contents);
-
mStackScroller = (NotificationStackScrollLayout) mStatusBarWindow.findViewById(
R.id.notification_stack_scroller);
mStackScroller.setLongPressListener(getNotificationLongClicker());
mStackScroller.setPhoneStatusBar(this);
+ mStackScroller.setGroupManager(mGroupManager);
+ mStackScroller.setHeadsUpManager(mHeadsUpManager);
+ mGroupManager.setOnGroupChangeListener(mStackScroller);
mKeyguardIconOverflowContainer =
(NotificationOverflowContainer) LayoutInflater.from(mContext).inflate(
R.layout.status_bar_notification_keyguard_overflow, mStackScroller, false);
mKeyguardIconOverflowContainer.setOnActivatedListener(this);
mKeyguardIconOverflowContainer.setOnClickListener(mOverflowClickListener);
- mStackScroller.addView(mKeyguardIconOverflowContainer);
+ mStackScroller.setOverflowContainer(mKeyguardIconOverflowContainer);
SpeedBumpView speedBump = (SpeedBumpView) LayoutInflater.from(mContext).inflate(
R.layout.status_bar_notification_speed_bump, mStackScroller, false);
@@ -744,6 +738,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mDismissView.setOnButtonClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
+ MetricsLogger.action(mContext, MetricsLogger.ACTION_DISMISS_ALL_NOTES);
clearAllNotifications();
}
});
@@ -756,7 +751,11 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
ScrimView scrimBehind = (ScrimView) mStatusBarWindow.findViewById(R.id.scrim_behind);
ScrimView scrimInFront = (ScrimView) mStatusBarWindow.findViewById(R.id.scrim_in_front);
- mScrimController = new ScrimController(scrimBehind, scrimInFront, mScrimSrcModeEnabled);
+ View headsUpScrim = mStatusBarWindow.findViewById(R.id.heads_up_scrim);
+ mScrimController = new ScrimController(scrimBehind, scrimInFront, headsUpScrim,
+ mScrimSrcModeEnabled);
+ mHeadsUpManager.addListener(mScrimController);
+ mStackScroller.setScrimController(mScrimController);
mScrimController.setBackDropView(mBackdrop);
mStatusBarView.setScrimController(mScrimController);
mDozeScrimController = new DozeScrimController(mScrimController, context);
@@ -764,39 +763,29 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mHeader = (StatusBarHeaderView) mStatusBarWindow.findViewById(R.id.header);
mHeader.setActivityStarter(this);
mKeyguardStatusBar = (KeyguardStatusBarView) mStatusBarWindow.findViewById(R.id.keyguard_header);
- mStatusIconsKeyguard = (LinearLayout) mKeyguardStatusBar.findViewById(R.id.statusIcons);
mKeyguardStatusView = mStatusBarWindow.findViewById(R.id.keyguard_status_view);
mKeyguardBottomArea =
(KeyguardBottomAreaView) mStatusBarWindow.findViewById(R.id.keyguard_bottom_area);
mKeyguardBottomArea.setActivityStarter(this);
+ mKeyguardBottomArea.setAssistManager(mAssistManager);
mKeyguardIndicationController = new KeyguardIndicationController(mContext,
(KeyguardIndicationTextView) mStatusBarWindow.findViewById(
R.id.keyguard_indication_text));
mKeyguardBottomArea.setKeyguardIndicationController(mKeyguardIndicationController);
- mTickerEnabled = res.getBoolean(R.bool.enable_ticker);
- if (mTickerEnabled) {
- final ViewStub tickerStub = (ViewStub) mStatusBarView.findViewById(R.id.ticker_stub);
- if (tickerStub != null) {
- mTickerView = tickerStub.inflate();
- mTicker = new MyTicker(context, mStatusBarView);
-
- TickerView tickerView = (TickerView) mStatusBarView.findViewById(R.id.tickerText);
- tickerView.mTicker = mTicker;
- }
- }
-
- mEdgeBorder = res.getDimensionPixelSize(R.dimen.status_bar_edge_ignore);
-
// set the inital view visibility
setAreThereNotifications();
+ mIconController = new StatusBarIconController(
+ mContext, mStatusBarView, mKeyguardStatusBar, this);
+
// Background thread for any controllers that need it.
mHandlerThread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND);
mHandlerThread.start();
// Other icons
- mLocationController = new LocationControllerImpl(mContext); // will post a notification
+ mLocationController = new LocationControllerImpl(mContext,
+ mHandlerThread.getLooper()); // will post a notification
mBatteryController = new BatteryController(mContext);
mBatteryController.addStateChangedCallback(new BatteryStateChangeCallback() {
@Override
@@ -811,7 +800,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
// noop
}
});
- mNetworkController = new NetworkControllerImpl(mContext);
+ mNetworkController = new NetworkControllerImpl(mContext, mHandlerThread.getLooper());
mHotspotController = new HotspotControllerImpl(mContext);
mBluetoothController = new BluetoothControllerImpl(mContext, mHandlerThread.getLooper());
mSecurityController = new SecurityControllerImpl(mContext);
@@ -830,9 +819,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
(SignalClusterView) mKeyguardStatusBar.findViewById(R.id.signal_cluster);
final SignalClusterView signalClusterQs =
(SignalClusterView) mHeader.findViewById(R.id.signal_cluster);
- mNetworkController.addSignalCluster(signalCluster);
- mNetworkController.addSignalCluster(signalClusterKeyguard);
- mNetworkController.addSignalCluster(signalClusterQs);
+ mNetworkController.addSignalCallback(signalCluster);
+ mNetworkController.addSignalCallback(signalClusterKeyguard);
+ mNetworkController.addSignalCallback(signalClusterQs);
signalCluster.setSecurityController(mSecurityController);
signalCluster.setNetworkController(mNetworkController);
signalClusterKeyguard.setSecurityController(mSecurityController);
@@ -841,33 +830,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
signalClusterQs.setNetworkController(mNetworkController);
final boolean isAPhone = mNetworkController.hasVoiceCallingFeature();
if (isAPhone) {
- mNetworkController.addEmergencyListener(new NetworkControllerImpl.EmergencyListener() {
- @Override
- public void setEmergencyCallsOnly(boolean emergencyOnly) {
- mHeader.setShowEmergencyCallsOnly(emergencyOnly);
- }
- });
- }
-
- mCarrierLabel = (TextView)mStatusBarWindow.findViewById(R.id.carrier_label);
- mShowCarrierInPanel = (mCarrierLabel != null);
- if (DEBUG) Log.v(TAG, "carrierlabel=" + mCarrierLabel + " show=" + mShowCarrierInPanel);
- if (mShowCarrierInPanel) {
- mCarrierLabel.setVisibility(mCarrierLabelVisible ? View.VISIBLE : View.INVISIBLE);
-
- mNetworkController.addCarrierLabel(new NetworkControllerImpl.CarrierLabelListener() {
- @Override
- public void setCarrierLabel(String label) {
- mCarrierLabel.setText(label);
- if (mNetworkController.hasMobileDataFeature()) {
- if (TextUtils.isEmpty(label)) {
- mCarrierLabel.setVisibility(View.GONE);
- } else {
- mCarrierLabel.setVisibility(View.VISIBLE);
- }
- }
- }
- });
+ mNetworkController.addEmergencyListener(mHeader);
}
mFlashlightController = new FlashlightController(mContext);
@@ -876,7 +839,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mAccessibilityController = new AccessibilityController(mContext);
mKeyguardBottomArea.setAccessibilityController(mAccessibilityController);
mNextAlarmController = new NextAlarmController(mContext);
- mKeyguardMonitor = new KeyguardMonitor();
+ mKeyguardMonitor = new KeyguardMonitor(mContext);
if (UserSwitcherController.isUserSwitcherAvailable(UserManager.get(mContext))) {
mUserSwitcherController = new UserSwitcherController(mContext, mKeyguardMonitor);
}
@@ -910,6 +873,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
// User info. Trigger first load.
mHeader.setUserInfoController(mUserInfoController);
mKeyguardStatusBar.setUserInfoController(mUserInfoController);
+ mKeyguardStatusBar.setUserSwitcherController(mUserSwitcherController);
mUserInfoController.reloadUserInfo();
mHeader.setBatteryController(mBatteryController);
@@ -922,6 +886,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mBroadcastReceiver.onReceive(mContext,
new Intent(pm.isScreenOn() ? Intent.ACTION_SCREEN_ON : Intent.ACTION_SCREEN_OFF));
+
// receive broadcasts
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
@@ -936,7 +901,12 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
// listen for USER_SETUP_COMPLETE setting (per-user)
resetUserSetupObserver();
- startGlyphRasterizeHack();
+ // disable profiling bars, since they overlap and clutter the output on app windows
+ ThreadedRenderer.overrideProperty("disableProfileBars", "true");
+
+ // Private API call to make the shadows look better for Recents
+ ThreadedRenderer.overrideProperty("ambientRatio", String.valueOf(1.5f));
+
return mStatusBarView;
}
@@ -948,9 +918,20 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
final ArrayList<View> viewsToHide = new ArrayList<View>(numChildren);
for (int i = 0; i < numChildren; i++) {
final View child = mStackScroller.getChildAt(i);
- if (mStackScroller.canChildBeDismissed(child)) {
- if (child.getVisibility() == View.VISIBLE) {
- viewsToHide.add(child);
+ if (child instanceof ExpandableNotificationRow) {
+ if (mStackScroller.canChildBeDismissed(child)) {
+ if (child.getVisibility() == View.VISIBLE) {
+ viewsToHide.add(child);
+ }
+ }
+ ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ List<ExpandableNotificationRow> children = row.getNotificationChildren();
+ if (row.areChildrenExpanded() && children != null) {
+ for (ExpandableNotificationRow childRow : children) {
+ if (childRow.getVisibility() == View.VISIBLE) {
+ viewsToHide.add(childRow);
+ }
+ }
}
}
}
@@ -1007,30 +988,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
}
- /**
- * Hack to improve glyph rasterization for scaled text views.
- */
- private void startGlyphRasterizeHack() {
- mStatusBarView.getViewTreeObserver().addOnPreDrawListener(
- new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- if (mDrawCount == 1) {
- mStatusBarView.getViewTreeObserver().removeOnPreDrawListener(this);
- HardwareCanvas.setProperty("extraRasterBucket",
- Float.toString(StackScrollAlgorithm.DIMMED_SCALE));
- HardwareCanvas.setProperty("extraRasterBucket", Float.toString(
- mContext.getResources().getDimensionPixelSize(
- R.dimen.qs_time_collapsed_size)
- / mContext.getResources().getDimensionPixelSize(
- R.dimen.qs_time_expanded_size)));
- }
- mDrawCount++;
- return true;
- }
- });
- }
-
@Override
protected void setZenMode(int mode) {
super.setZenMode(mode);
@@ -1055,62 +1012,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
return mStatusBarWindow;
}
- @Override
- protected WindowManager.LayoutParams getSearchLayoutParams(LayoutParams layoutParams) {
- boolean opaque = false;
- WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
- LayoutParams.MATCH_PARENT,
- LayoutParams.MATCH_PARENT,
- WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
- WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
- | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
- | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
- (opaque ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT));
- if (ActivityManager.isHighEndGfx()) {
- lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
- }
- lp.gravity = Gravity.BOTTOM | Gravity.START;
- lp.setTitle("SearchPanel");
- lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED
- | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
- return lp;
- }
-
- @Override
- protected void updateSearchPanel() {
- super.updateSearchPanel();
- if (mNavigationBarView != null) {
- mNavigationBarView.setDelegateView(mSearchPanelView);
- }
- }
-
- @Override
- public void showSearchPanel() {
- super.showSearchPanel();
- mHandler.removeCallbacks(mShowSearchPanel);
-
- // we want to freeze the sysui state wherever it is
- mSearchPanelView.setSystemUiVisibility(mSystemUiVisibility);
-
- if (mNavigationBarView != null) {
- WindowManager.LayoutParams lp =
- (android.view.WindowManager.LayoutParams) mNavigationBarView.getLayoutParams();
- lp.flags &= ~WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
- mWindowManager.updateViewLayout(mNavigationBarView, lp);
- }
- }
-
- @Override
- public void hideSearchPanel() {
- super.hideSearchPanel();
- if (mNavigationBarView != null) {
- WindowManager.LayoutParams lp =
- (android.view.WindowManager.LayoutParams) mNavigationBarView.getLayoutParams();
- lp.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
- mWindowManager.updateViewLayout(mNavigationBarView, lp);
- }
- }
-
public int getStatusBarHeight() {
if (mNaturalBarHeight < 0) {
final Resources res = mContext.getResources();
@@ -1137,31 +1038,32 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
};
- private int mShowSearchHoldoff = 0;
- private Runnable mShowSearchPanel = new Runnable() {
- public void run() {
- showSearchPanel();
+ private final View.OnLongClickListener mLongPressHomeListener
+ = new View.OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View v) {
+ if (shouldDisableNavbarGestures()) {
+ return false;
+ }
+ mAssistManager.prepareBeforeInvocation();
+ mAssistManager.onGestureInvoked();
awakenDreams();
+ if (mNavigationBarView != null) {
+ mNavigationBarView.abortCurrentGesture();
+ }
+ return true;
}
};
- View.OnTouchListener mHomeActionListener = new View.OnTouchListener() {
+ private final View.OnTouchListener mHomeActionListener = new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
- switch(event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- if (!shouldDisableNavbarGestures()) {
- mHandler.removeCallbacks(mShowSearchPanel);
- mHandler.postDelayed(mShowSearchPanel, mShowSearchHoldoff);
- }
- break;
-
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- mHandler.removeCallbacks(mShowSearchPanel);
- awakenDreams();
- break;
- }
- return false;
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ awakenDreams();
+ break;
+ }
+ return false;
}
};
@@ -1185,7 +1087,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mNavigationBarView.getBackButton().setLongClickable(true);
mNavigationBarView.getBackButton().setOnLongClickListener(mLongPressBackRecentsListener);
mNavigationBarView.getHomeButton().setOnTouchListener(mHomeActionListener);
- updateSearchPanel();
+ mNavigationBarView.getHomeButton().setOnLongClickListener(mLongPressHomeListener);
+ mAssistManager.onConfigurationChanged();
}
// For small-screen devices (read: phones) that lack hardware navigation buttons
@@ -1232,75 +1135,17 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
return lp;
}
- private void addHeadsUpView() {
- int headsUpHeight = mContext.getResources()
- .getDimensionPixelSize(R.dimen.heads_up_window_height);
- WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
- LayoutParams.MATCH_PARENT, headsUpHeight,
- WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, // above the status bar!
- WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
- | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
- | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
- | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
- | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
- PixelFormat.TRANSLUCENT);
- lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
- lp.gravity = Gravity.TOP;
- lp.setTitle("Heads Up");
- lp.packageName = mContext.getPackageName();
- lp.windowAnimations = R.style.Animation_StatusBar_HeadsUp;
-
- mWindowManager.addView(mHeadsUpNotificationView, lp);
- }
-
- private void removeHeadsUpView() {
- mWindowManager.removeView(mHeadsUpNotificationView);
- }
-
- public void refreshAllStatusBarIcons() {
- refreshAllIconsForLayout(mStatusIcons);
- refreshAllIconsForLayout(mStatusIconsKeyguard);
- refreshAllIconsForLayout(mNotificationIcons);
- }
-
- private void refreshAllIconsForLayout(LinearLayout ll) {
- final int count = ll.getChildCount();
- for (int n = 0; n < count; n++) {
- View child = ll.getChildAt(n);
- if (child instanceof StatusBarIconView) {
- ((StatusBarIconView) child).updateDrawable();
- }
- }
- }
-
public void addIcon(String slot, int index, int viewIndex, StatusBarIcon icon) {
- if (SPEW) Log.d(TAG, "addIcon slot=" + slot + " index=" + index + " viewIndex=" + viewIndex
- + " icon=" + icon);
- StatusBarIconView view = new StatusBarIconView(mContext, slot, null);
- view.set(icon);
- mStatusIcons.addView(view, viewIndex, new LinearLayout.LayoutParams(
- LayoutParams.WRAP_CONTENT, mIconSize));
- view = new StatusBarIconView(mContext, slot, null);
- view.set(icon);
- mStatusIconsKeyguard.addView(view, viewIndex, new LinearLayout.LayoutParams(
- LayoutParams.WRAP_CONTENT, mIconSize));
+ mIconController.addSystemIcon(slot, index, viewIndex, icon);
}
public void updateIcon(String slot, int index, int viewIndex,
StatusBarIcon old, StatusBarIcon icon) {
- if (SPEW) Log.d(TAG, "updateIcon slot=" + slot + " index=" + index + " viewIndex=" + viewIndex
- + " old=" + old + " icon=" + icon);
- StatusBarIconView view = (StatusBarIconView) mStatusIcons.getChildAt(viewIndex);
- view.set(icon);
- view = (StatusBarIconView) mStatusIconsKeyguard.getChildAt(viewIndex);
- view.set(icon);
+ mIconController.updateSystemIcon(slot, index, viewIndex, old, icon);
}
public void removeIcon(String slot, int index, int viewIndex) {
- if (SPEW) Log.d(TAG, "removeIcon slot=" + slot + " index=" + index + " viewIndex=" + viewIndex);
- mStatusIcons.removeViewAt(viewIndex);
- mStatusIconsKeyguard.removeViewAt(viewIndex);
+ mIconController.removeSystemIcon(slot, index, viewIndex);
}
public UserHandle getCurrentUserHandle() {
@@ -1308,27 +1153,22 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
@Override
- public void addNotification(StatusBarNotification notification, RankingMap ranking) {
+ public void addNotification(StatusBarNotification notification, RankingMap ranking,
+ Entry oldEntry) {
if (DEBUG) Log.d(TAG, "addNotification key=" + notification.getKey());
- if (mUseHeadsUp && shouldInterrupt(notification)) {
- if (DEBUG) Log.d(TAG, "launching notification in heads up mode");
- Entry interruptionCandidate = new Entry(notification, null);
- ViewGroup holder = mHeadsUpNotificationView.getHolder();
- if (inflateViewsForHeadsUp(interruptionCandidate, holder)) {
- // 1. Populate mHeadsUpNotificationView
- mHeadsUpNotificationView.showNotification(interruptionCandidate);
-
- // do not show the notification in the shade, yet.
- return;
- }
- }
Entry shadeEntry = createNotificationViews(notification);
if (shadeEntry == null) {
return;
}
+ boolean isHeadsUped = mUseHeadsUp && shouldInterrupt(shadeEntry);
+ if (isHeadsUped) {
+ mHeadsUpManager.showNotification(shadeEntry);
+ // Mark as seen immediately
+ setNotificationShown(notification);
+ }
- if (notification.getNotification().fullScreenIntent != null) {
+ if (!isHeadsUped && notification.getNotification().fullScreenIntent != null) {
// Stop screensaver if the notification has a full-screen intent.
// (like an incoming phone call)
awakenDreams();
@@ -1339,60 +1179,14 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION,
notification.getKey());
notification.getNotification().fullScreenIntent.send();
+ shadeEntry.notifyFullScreenIntentLaunched();
+ MetricsLogger.count(mContext, "note_fullscreen", 1);
} catch (PendingIntent.CanceledException e) {
}
- } else {
- // usual case: status bar visible & not immersive
-
- // show the ticker if there isn't already a heads up
- if (mHeadsUpNotificationView.getEntry() == null) {
- tick(notification, true);
- }
}
addNotificationViews(shadeEntry, ranking);
// Recalculate the position of the sliding windows and the titles.
setAreThereNotifications();
- updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
- }
-
- public void displayNotificationFromHeadsUp(StatusBarNotification notification) {
- NotificationData.Entry shadeEntry = createNotificationViews(notification);
- if (shadeEntry == null) {
- return;
- }
- shadeEntry.setInterruption();
-
- addNotificationViews(shadeEntry, null);
- // Recalculate the position of the sliding windows and the titles.
- setAreThereNotifications();
- updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
- }
-
- @Override
- public void resetHeadsUpDecayTimer() {
- mHandler.removeMessages(MSG_DECAY_HEADS_UP);
- if (mUseHeadsUp && mHeadsUpNotificationDecay > 0
- && mHeadsUpNotificationView.isClearable()) {
- mHandler.sendEmptyMessageDelayed(MSG_DECAY_HEADS_UP, mHeadsUpNotificationDecay);
- }
- }
-
- @Override
- public void scheduleHeadsUpOpen() {
- mHandler.removeMessages(MSG_SHOW_HEADS_UP);
- mHandler.sendEmptyMessage(MSG_SHOW_HEADS_UP);
- }
-
- @Override
- public void scheduleHeadsUpClose() {
- mHandler.removeMessages(MSG_HIDE_HEADS_UP);
- mHandler.sendEmptyMessage(MSG_HIDE_HEADS_UP);
- }
-
- @Override
- public void scheduleHeadsUpEscalation() {
- mHandler.removeMessages(MSG_ESCALATE_HEADS_UP);
- mHandler.sendEmptyMessage(MSG_ESCALATE_HEADS_UP);
}
@Override
@@ -1403,23 +1197,19 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
@Override
public void removeNotification(String key, RankingMap ranking) {
- if (ENABLE_HEADS_UP && mHeadsUpNotificationView.getEntry() != null
- && key.equals(mHeadsUpNotificationView.getEntry().notification.getKey())) {
- mHeadsUpNotificationView.clear();
+ boolean deferRemoval = false;
+ if (mHeadsUpManager.isHeadsUp(key)) {
+ deferRemoval = !mHeadsUpManager.removeNotification(key);
+ }
+ if (deferRemoval) {
+ mLatestRankingMap = ranking;
+ mHeadsUpEntriesToRemoveOnSwitch.add(mHeadsUpManager.getEntry(key));
+ return;
}
-
StatusBarNotification old = removeNotificationViews(key, ranking);
if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old);
if (old != null) {
- // Cancel the ticker if it's still running
- if (mTickerEnabled) {
- mTicker.removeEntry(old);
- }
-
- // Recalculate the position of the sliding windows and the titles.
- updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
-
if (CLOSE_PANEL_WHEN_EMPTIED && !hasActiveNotifications()
&& !mNotificationPanel.isTracking() && !mNotificationPanel.isQsExpanded()) {
if (mState == StatusBarState.SHADE) {
@@ -1437,12 +1227,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
if (mNavigationBarView != null) {
mNavigationBarView.setLayoutDirection(layoutDirection);
}
- refreshAllStatusBarIcons();
- }
-
- private void updateShowSearchHoldoff() {
- mShowSearchHoldoff = mContext.getResources().getInteger(
- R.integer.config_show_search_delay);
}
private void updateNotificationShade() {
@@ -1483,10 +1267,23 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
ent.row.setShowingLegacyBackground(true);
}
}
- toShow.add(ent.row);
+ 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<View> toRemove = new ArrayList<View>();
+ ArrayList<View> toRemove = new ArrayList<>();
for (int i=0; i< mStackScroller.getChildCount(); i++) {
View child = mStackScroller.getChildAt(i);
if (!toShow.contains(child) && child instanceof ExpandableNotificationRow) {
@@ -1515,29 +1312,88 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
continue;
}
- if (child == toShow.get(j)) {
- // Everything is well, advance both lists.
- j++;
- continue;
+ ExpandableNotificationRow targetChild = toShow.get(j);
+ if (child != targetChild) {
+ // Oops, wrong notification at this position. Put the right one
+ // here and advance both lists.
+ mStackScroller.changeViewPosition(targetChild, i);
}
-
- // Oops, wrong notification at this position. Put the right one
- // here and advance both lists.
- mStackScroller.changeViewPosition(toShow.get(j), i);
j++;
+
}
+
+ // lets handle the child notifications now
+ updateNotificationShadeForChildren();
+
+ // clear the map again for the next usage
+ mTmpChildOrderMap.clear();
+
updateRowStates();
updateSpeedbump();
updateClearAll();
updateEmptyShadeView();
- // 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.
+ updateQsExpansionEnabled();
+ mShadeUpdates.check();
+ }
+
+ /**
+ * 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.
+ */
+ private void updateQsExpansionEnabled() {
mNotificationPanel.setQsExpansionEnabled(isDeviceProvisioned()
&& (mUserSetup || mUserSwitcherController == null
- || !mUserSwitcherController.isSimpleUserSwitcher()));
- mShadeUpdates.check();
+ || !mUserSwitcherController.isSimpleUserSwitcher())
+ && ((mDisabled2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) == 0)
+ && !ONLY_CORE_APPS);
+ }
+
+ private void updateNotificationShadeForChildren() {
+ ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>();
+ 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);
+
+ // lets first remove all undesired children
+ if (children != null) {
+ toRemove.clear();
+ for (ExpandableNotificationRow childRow : children) {
+ if (orderedChildren == null || !orderedChildren.contains(childRow)) {
+ toRemove.add(childRow);
+ }
+ }
+ for (ExpandableNotificationRow remove : toRemove) {
+ parent.removeChildNotification(remove);
+ mStackScroller.notifyGroupChildRemoved(remove);
+ }
+ }
+
+ // We now add all the children which are not in there already
+ for (int childIndex = 0; orderedChildren != null && childIndex < orderedChildren.size();
+ childIndex++) {
+ ExpandableNotificationRow childView = orderedChildren.get(childIndex);
+ if (children == null || !children.contains(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);
+ }
+ if (orderChanged) {
+ mStackScroller.generateChildOrderChangedEvent();
+ }
}
private boolean packageHasVisibilityOverride(String key) {
@@ -1566,6 +1422,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
final int N = activeNotifications.size();
for (int i = 0; i < N; i++) {
Entry entry = activeNotifications.get(i);
+ boolean isChild = !isTopLevelChild(entry);
+ if (isChild) {
+ continue;
+ }
if (entry.row.getVisibility() != View.GONE &&
mNotificationData.isAmbient(entry.key)) {
speedbumpIndex = currentIndex;
@@ -1576,71 +1436,16 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mStackScroller.updateSpeedBumpIndex(speedbumpIndex);
}
+ public static boolean isTopLevelChild(Entry entry) {
+ return entry.row.getParent() instanceof NotificationStackScrollLayout;
+ }
+
@Override
protected void updateNotifications() {
- // TODO: Move this into updateNotificationIcons()?
- if (mNotificationIcons == null) return;
-
mNotificationData.filterAndSort();
updateNotificationShade();
- updateNotificationIcons();
- }
-
- private void updateNotificationIcons() {
- final LinearLayout.LayoutParams params
- = new LinearLayout.LayoutParams(mIconSize + 2*mIconHPadding, mNaturalBarHeight);
-
- ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
- final int N = activeNotifications.size();
- ArrayList<StatusBarIconView> toShow = new ArrayList<>(N);
-
- // Filter out notifications with low scores.
- for (int i = 0; i < N; i++) {
- Entry ent = activeNotifications.get(i);
- if (ent.notification.getScore() < HIDE_ICONS_BELOW_SCORE &&
- !NotificationData.showNotificationEvenIfUnprovisioned(ent.notification)) {
- continue;
- }
- toShow.add(ent.icon);
- }
-
- if (DEBUG) {
- Log.d(TAG, "refreshing icons: " + toShow.size() +
- " notifications, mNotificationIcons=" + mNotificationIcons);
- }
-
- ArrayList<View> toRemove = new ArrayList<View>();
- for (int i=0; i<mNotificationIcons.getChildCount(); i++) {
- View child = mNotificationIcons.getChildAt(i);
- if (!toShow.contains(child)) {
- toRemove.add(child);
- }
- }
-
- final int toRemoveCount = toRemove.size();
- for (int i = 0; i < toRemoveCount; i++) {
- mNotificationIcons.removeView(toRemove.get(i));
- }
-
- for (int i=0; i<toShow.size(); i++) {
- View v = toShow.get(i);
- if (v.getParent() == null) {
- mNotificationIcons.addView(v, i, params);
- }
- }
-
- // Resort notification icons
- final int childCount = mNotificationIcons.getChildCount();
- for (int i = 0; i < childCount; i++) {
- View actual = mNotificationIcons.getChildAt(i);
- StatusBarIconView expected = toShow.get(i);
- if (actual == expected) {
- continue;
- }
- mNotificationIcons.removeView(expected);
- mNotificationIcons.addView(expected, i);
- }
+ mIconController.updateNotificationIcons(mNotificationData);
}
@Override
@@ -1649,53 +1454,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mNotificationPanel.notifyVisibleChildrenChanged();
}
- protected void updateCarrierLabelVisibility(boolean force) {
- // TODO: Handle this for the notification stack scroller as well
- if (!mShowCarrierInPanel) return;
- // The idea here is to only show the carrier label when there is enough room to see it,
- // i.e. when there aren't enough notifications to fill the panel.
- if (SPEW) {
- Log.d(TAG, String.format("stackScrollerh=%d scrollh=%d carrierh=%d",
- mStackScroller.getHeight(), mStackScroller.getHeight(),
- mCarrierLabelHeight));
- }
-
- // Emergency calls only is shown in the expanded header now.
- final boolean emergencyCallsShownElsewhere = true;
- final boolean makeVisible =
- !(emergencyCallsShownElsewhere && mNetworkController.isEmergencyOnly())
- && mStackScroller.getHeight() < (mNotificationPanel.getHeight()
- - mCarrierLabelHeight - mStatusBarHeaderHeight)
- && mStackScroller.getVisibility() == View.VISIBLE
- && mState != StatusBarState.KEYGUARD;
-
- if (force || mCarrierLabelVisible != makeVisible) {
- mCarrierLabelVisible = makeVisible;
- if (DEBUG) {
- Log.d(TAG, "making carrier label " + (makeVisible?"visible":"invisible"));
- }
- mCarrierLabel.animate().cancel();
- if (makeVisible) {
- mCarrierLabel.setVisibility(View.VISIBLE);
- }
- mCarrierLabel.animate()
- .alpha(makeVisible ? 1f : 0f)
- //.setStartDelay(makeVisible ? 500 : 0)
- //.setDuration(makeVisible ? 750 : 100)
- .setDuration(150)
- .setListener(makeVisible ? null : new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- if (!mCarrierLabelVisible) { // race
- mCarrierLabel.setVisibility(View.INVISIBLE);
- mCarrierLabel.setAlpha(0f);
- }
- }
- })
- .start();
- }
- }
-
@Override
protected void setAreThereNotifications() {
@@ -1728,8 +1486,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
findAndUpdateMediaNotifications();
-
- updateCarrierLabelVisibility(false);
}
public void findAndUpdateMediaNotifications() {
@@ -1873,11 +1629,16 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
if (mBackdrop == null) return; // called too early
+ if (mLaunchTransitionFadingAway) {
+ mBackdrop.setVisibility(View.INVISIBLE);
+ return;
+ }
+
if (DEBUG_MEDIA) {
Log.v(TAG, "DEBUG_MEDIA: updating album art for notification " + mMediaNotificationKey
- + " metadata=" + mMediaMetadata
- + " metaDataChanged=" + metaDataChanged
- + " state=" + mState);
+ + " metadata=" + mMediaMetadata
+ + " metaDataChanged=" + metaDataChanged
+ + " state=" + mState);
}
Bitmap artworkBitmap = null;
@@ -1973,14 +1734,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
}
- public void showClock(boolean show) {
- if (mStatusBarView == null) return;
- View clock = mStatusBarView.findViewById(R.id.clock);
- if (clock != null) {
- clock.setVisibility(show ? View.VISIBLE : View.GONE);
- }
- }
-
private int adjustDisableFlags(int state) {
if (!mLaunchTransitionFadingAway
&& (mExpandedVisible || mBouncerShowing || mWaitingForKeyguardExit)) {
@@ -1993,143 +1746,100 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
/**
* State is one or more of the DISABLE constants from StatusBarManager.
*/
- public void disable(int state, boolean animate) {
- mDisabledUnmodified = state;
- state = adjustDisableFlags(state);
- final int old = mDisabled;
- final int diff = state ^ old;
- mDisabled = state;
+ public void disable(int state1, int state2, boolean animate) {
+ animate &= mStatusBarWindowState != WINDOW_STATE_HIDDEN;
+ mDisabledUnmodified1 = state1;
+ mDisabledUnmodified2 = state2;
+ state1 = adjustDisableFlags(state1);
+ final int old1 = mDisabled1;
+ final int diff1 = state1 ^ old1;
+ mDisabled1 = state1;
+
+ final int old2 = mDisabled2;
+ final int diff2 = state2 ^ old2;
+ mDisabled2 = state2;
if (DEBUG) {
- Log.d(TAG, String.format("disable: 0x%08x -> 0x%08x (diff: 0x%08x)",
- old, state, diff));
+ Log.d(TAG, String.format("disable1: 0x%08x -> 0x%08x (diff1: 0x%08x)",
+ old1, state1, diff1));
+ Log.d(TAG, String.format("disable2: 0x%08x -> 0x%08x (diff2: 0x%08x)",
+ old2, state2, diff2));
}
StringBuilder flagdbg = new StringBuilder();
flagdbg.append("disable: < ");
- flagdbg.append(((state & StatusBarManager.DISABLE_EXPAND) != 0) ? "EXPAND" : "expand");
- flagdbg.append(((diff & StatusBarManager.DISABLE_EXPAND) != 0) ? "* " : " ");
- flagdbg.append(((state & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) ? "ICONS" : "icons");
- flagdbg.append(((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) ? "* " : " ");
- flagdbg.append(((state & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) ? "ALERTS" : "alerts");
- flagdbg.append(((diff & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) ? "* " : " ");
- flagdbg.append(((state & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) ? "SYSTEM_INFO" : "system_info");
- flagdbg.append(((diff & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) ? "* " : " ");
- flagdbg.append(((state & StatusBarManager.DISABLE_BACK) != 0) ? "BACK" : "back");
- flagdbg.append(((diff & StatusBarManager.DISABLE_BACK) != 0) ? "* " : " ");
- flagdbg.append(((state & StatusBarManager.DISABLE_HOME) != 0) ? "HOME" : "home");
- flagdbg.append(((diff & StatusBarManager.DISABLE_HOME) != 0) ? "* " : " ");
- flagdbg.append(((state & StatusBarManager.DISABLE_RECENT) != 0) ? "RECENT" : "recent");
- flagdbg.append(((diff & StatusBarManager.DISABLE_RECENT) != 0) ? "* " : " ");
- flagdbg.append(((state & StatusBarManager.DISABLE_CLOCK) != 0) ? "CLOCK" : "clock");
- flagdbg.append(((diff & StatusBarManager.DISABLE_CLOCK) != 0) ? "* " : " ");
- flagdbg.append(((state & StatusBarManager.DISABLE_SEARCH) != 0) ? "SEARCH" : "search");
- flagdbg.append(((diff & StatusBarManager.DISABLE_SEARCH) != 0) ? "* " : " ");
+ flagdbg.append(((state1 & StatusBarManager.DISABLE_EXPAND) != 0) ? "EXPAND" : "expand");
+ flagdbg.append(((diff1 & StatusBarManager.DISABLE_EXPAND) != 0) ? "* " : " ");
+ flagdbg.append(((state1 & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) ? "ICONS" : "icons");
+ flagdbg.append(((diff1 & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) ? "* " : " ");
+ flagdbg.append(((state1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) ? "ALERTS" : "alerts");
+ flagdbg.append(((diff1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) ? "* " : " ");
+ flagdbg.append(((state1 & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) ? "SYSTEM_INFO" : "system_info");
+ flagdbg.append(((diff1 & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) ? "* " : " ");
+ flagdbg.append(((state1 & StatusBarManager.DISABLE_BACK) != 0) ? "BACK" : "back");
+ flagdbg.append(((diff1 & StatusBarManager.DISABLE_BACK) != 0) ? "* " : " ");
+ flagdbg.append(((state1 & StatusBarManager.DISABLE_HOME) != 0) ? "HOME" : "home");
+ flagdbg.append(((diff1 & StatusBarManager.DISABLE_HOME) != 0) ? "* " : " ");
+ flagdbg.append(((state1 & StatusBarManager.DISABLE_RECENT) != 0) ? "RECENT" : "recent");
+ flagdbg.append(((diff1 & StatusBarManager.DISABLE_RECENT) != 0) ? "* " : " ");
+ flagdbg.append(((state1 & StatusBarManager.DISABLE_CLOCK) != 0) ? "CLOCK" : "clock");
+ flagdbg.append(((diff1 & StatusBarManager.DISABLE_CLOCK) != 0) ? "* " : " ");
+ flagdbg.append(((state1 & StatusBarManager.DISABLE_SEARCH) != 0) ? "SEARCH" : "search");
+ flagdbg.append(((diff1 & StatusBarManager.DISABLE_SEARCH) != 0) ? "* " : " ");
+ flagdbg.append(((state2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) != 0) ? "QUICK_SETTINGS"
+ : "quick_settings");
+ flagdbg.append(((diff2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) != 0) ? "* " : " ");
flagdbg.append(">");
Log.d(TAG, flagdbg.toString());
- if ((diff & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) {
- mSystemIconArea.animate().cancel();
- if ((state & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) {
- animateStatusBarHide(mSystemIconArea, animate);
+ if ((diff1 & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) {
+ if ((state1 & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) {
+ mIconController.hideSystemIconArea(animate);
} else {
- animateStatusBarShow(mSystemIconArea, animate);
+ mIconController.showSystemIconArea(animate);
}
}
- if ((diff & StatusBarManager.DISABLE_CLOCK) != 0) {
- boolean show = (state & StatusBarManager.DISABLE_CLOCK) == 0;
- showClock(show);
+ if ((diff1 & StatusBarManager.DISABLE_CLOCK) != 0) {
+ boolean visible = (state1 & StatusBarManager.DISABLE_CLOCK) == 0;
+ mIconController.setClockVisibility(visible);
}
- if ((diff & StatusBarManager.DISABLE_EXPAND) != 0) {
- if ((state & StatusBarManager.DISABLE_EXPAND) != 0) {
+ if ((diff1 & StatusBarManager.DISABLE_EXPAND) != 0) {
+ if ((state1 & StatusBarManager.DISABLE_EXPAND) != 0) {
animateCollapsePanels();
}
}
- if ((diff & (StatusBarManager.DISABLE_HOME
+ if ((diff1 & (StatusBarManager.DISABLE_HOME
| StatusBarManager.DISABLE_RECENT
| StatusBarManager.DISABLE_BACK
| StatusBarManager.DISABLE_SEARCH)) != 0) {
// the nav bar will take care of these
- if (mNavigationBarView != null) mNavigationBarView.setDisabledFlags(state);
+ if (mNavigationBarView != null) mNavigationBarView.setDisabledFlags(state1);
- if ((state & StatusBarManager.DISABLE_RECENT) != 0) {
+ if ((state1 & StatusBarManager.DISABLE_RECENT) != 0) {
// close recents if it's visible
mHandler.removeMessages(MSG_HIDE_RECENT_APPS);
mHandler.sendEmptyMessage(MSG_HIDE_RECENT_APPS);
}
}
- if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
- if ((state & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
- if (mTicking) {
- haltTicker();
- }
- animateStatusBarHide(mNotificationIconArea, animate);
+ if ((diff1 & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
+ if ((state1 & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
+ mIconController.hideNotificationIconArea(animate);
} else {
- animateStatusBarShow(mNotificationIconArea, animate);
+ mIconController.showNotificationIconArea(animate);
}
}
- if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) {
+ if ((diff1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) {
mDisableNotificationAlerts =
- (state & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0;
+ (state1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0;
mHeadsUpObserver.onChange(true);
}
- }
-
- /**
- * Animates {@code v}, a view that is part of the status bar, out.
- */
- private void animateStatusBarHide(final View v, boolean animate) {
- v.animate().cancel();
- if (!animate) {
- v.setAlpha(0f);
- v.setVisibility(View.INVISIBLE);
- return;
- }
- v.animate()
- .alpha(0f)
- .setDuration(160)
- .setStartDelay(0)
- .setInterpolator(ALPHA_OUT)
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- v.setVisibility(View.INVISIBLE);
- }
- });
- }
-
- /**
- * Animates {@code v}, a view that is part of the status bar, in.
- */
- private void animateStatusBarShow(View v, boolean animate) {
- v.animate().cancel();
- v.setVisibility(View.VISIBLE);
- if (!animate) {
- v.setAlpha(1f);
- return;
- }
- v.animate()
- .alpha(1f)
- .setDuration(320)
- .setInterpolator(ALPHA_IN)
- .setStartDelay(50)
- // We need to clean up any pending end action from animateStatusBarHide if we call
- // both hide and show in the same frame before the animation actually gets started.
- // cancel() doesn't really remove the end action.
- .withEndAction(null);
-
- // Synchronize the motion with the Keyguard fading if necessary.
- if (mKeyguardFadingAway) {
- v.animate()
- .setDuration(mKeyguardFadingAwayDuration)
- .setInterpolator(mLinearOutSlowIn)
- .setStartDelay(mKeyguardFadingAwayDelay)
- .start();
+ if ((diff2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) != 0) {
+ updateQsExpansionEnabled();
}
}
@@ -2143,12 +1853,21 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
startActivityDismissingKeyguard(intent, false, dismissShade);
}
- public ScrimController getScrimController() {
- return mScrimController;
+ @Override
+ public void startActivity(Intent intent, boolean dismissShade, Callback callback) {
+ startActivityDismissingKeyguard(intent, false, dismissShade, callback);
+ }
+
+ @Override
+ public void preventNextAnimation() {
+ overrideActivityPendingAppTransition(true /* keyguardShowing */);
}
public void setQsExpanded(boolean expanded) {
mStatusBarWindowManager.setQsExpanded(expanded);
+ mKeyguardStatusView.setImportantForAccessibility(expanded
+ ? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+ : View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
}
public boolean isGoingToNotificationShade() {
@@ -2164,9 +1883,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
public boolean isFalsingThresholdNeeded() {
- boolean onKeyguard = getBarState() == StatusBarState.KEYGUARD;
- boolean isCurrentlyInsecure = mUnlockMethodCache.isCurrentlyInsecure();
- return onKeyguard && (isCurrentlyInsecure || mDozing || mScreenOnComingFromTouch);
+ return getBarState() == StatusBarState.KEYGUARD;
}
public boolean isDozing() {
@@ -2194,6 +1911,112 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
logStateToEventlog();
}
+ @Override
+ public void onHeadsUpPinnedModeChanged(boolean inPinnedMode) {
+ if (inPinnedMode) {
+ mStatusBarWindowManager.setHeadsUpShowing(true);
+ mStatusBarWindowManager.setForceStatusBarVisible(true);
+ if (mNotificationPanel.isFullyCollapsed()) {
+ // We need to ensure that the touchable region is updated before the window will be
+ // resized, in order to not catch any touches. A layout will ensure that
+ // onComputeInternalInsets will be called and after that we can resize the layout. Let's
+ // 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);
+ }
+ });
+ }
+ } else {
+ if (!mNotificationPanel.isFullyCollapsed() || mNotificationPanel.isTracking()) {
+ // We are currently tracking or is open and the shade doesn't need to be kept
+ // open artificially.
+ mStatusBarWindowManager.setHeadsUpShowing(false);
+ } else {
+ // 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);
+ }
+ }
+ });
+ }
+ }
+ }
+
+ @Override
+ public void onHeadsUpPinned(ExpandableNotificationRow headsUp) {
+ dismissVolumeDialog();
+ }
+
+ @Override
+ public void onHeadsUpUnPinned(ExpandableNotificationRow headsUp) {
+ }
+
+ @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);
+ }
+
+ }
+
+ protected void updateHeadsUp(String key, Entry entry, boolean shouldInterrupt,
+ boolean alertAgain) {
+ final boolean wasHeadsUp = isHeadsUp(key);
+ if (wasHeadsUp) {
+ if (!shouldInterrupt) {
+ // We don't want this to be interrupting anymore, lets remove it
+ mHeadsUpManager.removeNotification(key);
+ } else {
+ mHeadsUpManager.updateNotification(entry, alertAgain);
+ }
+ } else if (shouldInterrupt && alertAgain) {
+ // This notification was updated to be a heads-up, show it!
+ mHeadsUpManager.showNotification(entry);
+ }
+ }
+
+ protected void setHeadsUpUser(int newUserId) {
+ if (mHeadsUpManager != null) {
+ mHeadsUpManager.setUser(newUserId);
+ }
+ }
+
+ public boolean isHeadsUp(String key) {
+ return mHeadsUpManager.isHeadsUp(key);
+ }
+
+ protected boolean isSnoozedPackage(StatusBarNotification sbn) {
+ return mHeadsUpManager.isSnoozed(sbn.getPackageName());
+ }
+
+ public boolean isKeyguardCurrentlySecure() {
+ return !mUnlockMethodCache.isCurrentlyInsecure();
+ }
+
+ public void setPanelExpanded(boolean isExpanded) {
+ mStatusBarWindowManager.setPanelExpanded(isExpanded);
+ }
+
+ public void endWindowManagerLogging() {
+ mStatusBarWindowManager.setLogState(false);
+ }
+
/**
* All changes to the status bar and notifications funnel through here and are batched.
*/
@@ -2210,21 +2033,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
case MSG_CLOSE_PANELS:
animateCollapsePanels();
break;
- case MSG_SHOW_HEADS_UP:
- setHeadsUpVisibility(true);
- break;
- case MSG_DECAY_HEADS_UP:
- mHeadsUpNotificationView.release();
- setHeadsUpVisibility(false);
- break;
- case MSG_HIDE_HEADS_UP:
- mHeadsUpNotificationView.release();
- setHeadsUpVisibility(false);
- break;
- case MSG_ESCALATE_HEADS_UP:
- escalateHeadsUp();
- setHeadsUpVisibility(false);
- break;
case MSG_LAUNCH_TRANSITION_TIMEOUT:
onLaunchTransitionTimeout();
break;
@@ -2232,35 +2040,30 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
}
- /** if the interrupting notification had a fullscreen intent, fire it now. */
- private void escalateHeadsUp() {
- if (mHeadsUpNotificationView.getEntry() != null) {
- final StatusBarNotification sbn = mHeadsUpNotificationView.getEntry().notification;
- mHeadsUpNotificationView.release();
+ @Override
+ public void maybeEscalateHeadsUp() {
+ TreeSet<HeadsUpManager.HeadsUpEntry> entries = mHeadsUpManager.getSortedEntries();
+ for (HeadsUpManager.HeadsUpEntry entry : entries) {
+ final StatusBarNotification sbn = entry.entry.notification;
final Notification notification = sbn.getNotification();
if (notification.fullScreenIntent != null) {
- if (DEBUG)
+ if (DEBUG) {
Log.d(TAG, "converting a heads up to fullScreen");
+ }
try {
EventLog.writeEvent(EventLogTags.SYSUI_HEADS_UP_ESCALATION,
sbn.getKey());
notification.fullScreenIntent.send();
+ entry.entry.notifyFullScreenIntentLaunched();
} catch (PendingIntent.CanceledException e) {
}
}
}
+ mHeadsUpManager.releaseAllImmediately();
}
- View.OnFocusChangeListener mFocusChangeListener = new View.OnFocusChangeListener() {
- public void onFocusChange(View v, boolean hasFocus) {
- // Because 'v' is a ViewGroup, all its children will be (un)selected
- // too, which allows marqueeing to work.
- v.setSelected(hasFocus);
- }
- };
-
boolean panelsEnabled() {
- return (mDisabled & StatusBarManager.DISABLE_EXPAND) == 0;
+ return (mDisabled1 & StatusBarManager.DISABLE_EXPAND) == 0 && !ONLY_CORE_APPS;
}
void makeExpandedVisible(boolean force) {
@@ -2273,18 +2076,14 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
if (mNavigationBarView != null)
mNavigationBarView.setSlippery(true);
- updateCarrierLabelVisibility(true);
-
- updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
-
// Expand the window to encompass the full screen in anticipation of the drag.
// This is only possible to do atomically because the status bar is at the top of the screen!
- mStatusBarWindowManager.setStatusBarExpanded(true);
+ mStatusBarWindowManager.setPanelVisible(true);
mStatusBarView.setFocusable(false);
visibilityChanged(true);
mWaitingForKeyguardExit = false;
- disable(mDisabledUnmodified, !force /* animate */);
+ disable(mDisabledUnmodified1, mDisabledUnmodified2, !force /* animate */);
setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true);
}
@@ -2304,10 +2103,20 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
public void animateCollapsePanels(int flags) {
- animateCollapsePanels(flags, false /* force */);
+ animateCollapsePanels(flags, false /* force */, false /* delayed */,
+ 1.0f /* speedUpFactor */);
}
public void animateCollapsePanels(int flags, boolean force) {
+ animateCollapsePanels(flags, force, false /* delayed */, 1.0f /* speedUpFactor */);
+ }
+
+ public void animateCollapsePanels(int flags, boolean force, boolean delayed) {
+ animateCollapsePanels(flags, force, delayed, 1.0f /* speedUpFactor */);
+ }
+
+ public void animateCollapsePanels(int flags, boolean force, boolean delayed,
+ float speedUpFactor) {
if (!force &&
(mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED)) {
runPostCollapseRunnables();
@@ -2326,17 +2135,12 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
}
- if ((flags & CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL) == 0) {
- mHandler.removeMessages(MSG_CLOSE_SEARCH_PANEL);
- mHandler.sendEmptyMessage(MSG_CLOSE_SEARCH_PANEL);
- }
-
if (mStatusBarWindow != null) {
// release focus immediately to kick off focus change transition
mStatusBarWindowManager.setStatusBarFocusable(false);
mStatusBarWindow.cancelExpandHelper();
- mStatusBarView.collapseAllPanels(true);
+ mStatusBarView.collapseAllPanels(true /* animate */, delayed, speedUpFactor);
}
}
@@ -2348,50 +2152,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mPostCollapseRunnables.clear();
}
- public ViewPropertyAnimator setVisibilityWhenDone(
- final ViewPropertyAnimator a, final View v, final int vis) {
- a.setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- v.setVisibility(vis);
- a.setListener(null); // oneshot
- }
- });
- return a;
- }
-
- public Animator setVisibilityWhenDone(
- final Animator a, final View v, final int vis) {
- a.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- v.setVisibility(vis);
- }
- });
- return a;
- }
-
- public Animator interpolator(TimeInterpolator ti, Animator a) {
- a.setInterpolator(ti);
- return a;
- }
-
- public Animator startDelay(int d, Animator a) {
- a.setStartDelay(d);
- return a;
- }
-
- public Animator start(Animator a) {
- a.start();
- return a;
- }
-
- final TimeInterpolator mAccelerateInterpolator = new AccelerateInterpolator();
- final TimeInterpolator mDecelerateInterpolator = new DecelerateInterpolator();
- final int FLIP_DURATION_OUT = 125;
- final int FLIP_DURATION_IN = 225;
- final int FLIP_DURATION = (FLIP_DURATION_IN + FLIP_DURATION_OUT);
-
Animator mScrollViewAnim, mClearButtonAnim;
@Override
@@ -2423,7 +2183,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
public void animateCollapseQuickSettings() {
if (mState == StatusBarState.SHADE) {
- mStatusBarView.collapseAllPanels(true);
+ mStatusBarView.collapseAllPanels(true, false /* delayed */, 1.0f /* speedUpFactor */);
}
}
@@ -2436,14 +2196,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
// Ensure the panel is fully collapsed (just in case; bug 6765842, 7260868)
- mStatusBarView.collapseAllPanels(/*animate=*/ false);
-
- // reset things to their proper state
- if (mScrollViewAnim != null) mScrollViewAnim.cancel();
- if (mClearButtonAnim != null) mClearButtonAnim.cancel();
-
- mStackScroller.setVisibility(View.VISIBLE);
- mNotificationPanel.setVisibility(View.GONE);
+ mStatusBarView.collapseAllPanels(/*animate=*/ false, false /* delayed*/,
+ 1.0f /* speedUpFactor */);
mNotificationPanel.closeQs();
@@ -2453,7 +2207,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
visibilityChanged(false);
// Shrink the window to the size of the status bar only
- mStatusBarWindowManager.setStatusBarExpanded(false);
+ mStatusBarWindowManager.setPanelVisible(false);
+ mStatusBarWindowManager.setForceStatusBarVisible(false);
mStatusBarView.setFocusable(true);
// Close any "App info" popups that might have snuck on-screen
@@ -2462,7 +2217,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
runPostCollapseRunnables();
setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false);
showBouncer();
- disable(mDisabledUnmodified, true /* animate */);
+ disable(mDisabledUnmodified1, mDisabledUnmodified2, true /* animate */);
// Trimming will happen later if Keyguard is showing - doing it here might cause a jank in
// the bouncer appear animation.
@@ -2475,20 +2230,21 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
if (DEBUG_GESTURES) {
if (event.getActionMasked() != MotionEvent.ACTION_MOVE) {
EventLog.writeEvent(EventLogTags.SYSUI_STATUSBAR_TOUCH,
- event.getActionMasked(), (int) event.getX(), (int) event.getY(), mDisabled);
+ event.getActionMasked(), (int) event.getX(), (int) event.getY(),
+ mDisabled1, mDisabled2);
}
}
if (SPEW) {
- Log.d(TAG, "Touch: rawY=" + event.getRawY() + " event=" + event + " mDisabled="
- + mDisabled + " mTracking=" + mTracking);
+ Log.d(TAG, "Touch: rawY=" + event.getRawY() + " event=" + event + " mDisabled1="
+ + mDisabled1 + " mDisabled2=" + mDisabled2 + " mTracking=" + mTracking);
} else if (CHATTY) {
if (event.getAction() != MotionEvent.ACTION_MOVE) {
Log.d(TAG, String.format(
- "panel: %s at (%f, %f) mDisabled=0x%08x",
+ "panel: %s at (%f, %f) mDisabled1=0x%08x mDisabled2=0x%08x",
MotionEvent.actionToString(event.getAction()),
- event.getRawX(), event.getRawY(), mDisabled));
+ event.getRawX(), event.getRawY(), mDisabled1, mDisabled2));
}
}
@@ -2533,7 +2289,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mStatusBarWindowState = state;
if (DEBUG_WINDOW_STATE) Log.d(TAG, "Status bar " + windowStateToString(state));
if (!showing && mState == StatusBarState.SHADE) {
- mStatusBarView.collapseAllPanels(false);
+ mStatusBarView.collapseAllPanels(false /* animate */, false /* delayed */,
+ 1.0f /* speedUpFactor */);
}
}
if (mNavigationBarView != null
@@ -2587,14 +2344,17 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
final boolean lightsOut = (vis & View.SYSTEM_UI_FLAG_LOW_PROFILE) != 0;
if (lightsOut) {
animateCollapsePanels();
- if (mTicking) {
- haltTicker();
- }
}
setAreThereNotifications();
}
+ // ready to unhide
+ if ((vis & View.STATUS_BAR_UNHIDE) != 0) {
+ mSystemUiVisibility &= ~View.STATUS_BAR_UNHIDE;
+ mNoAnimationOnNextBarModeChange = true;
+ }
+
// update status bar mode
final int sbMode = computeBarMode(oldVal, newVal, mStatusBarView.getBarTransitions(),
View.STATUS_BAR_TRANSIENT, View.STATUS_BAR_TRANSLUCENT);
@@ -2626,14 +2386,18 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
}
- // ready to unhide
- if ((vis & View.STATUS_BAR_UNHIDE) != 0) {
- mSystemUiVisibility &= ~View.STATUS_BAR_UNHIDE;
- }
if ((vis & View.NAVIGATION_BAR_UNHIDE) != 0) {
mSystemUiVisibility &= ~View.NAVIGATION_BAR_UNHIDE;
}
+ if ((diff & View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) != 0 || sbModeChanged) {
+ boolean isTransparentBar = (mStatusBarMode == MODE_TRANSPARENT
+ || mStatusBarMode == MODE_LIGHTS_OUT_TRANSPARENT);
+ boolean allowLight = isTransparentBar && !mBatteryController.isPowerSave();
+ boolean light = (vis & View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) != 0;
+
+ mIconController.setIconsDark(allowLight && light);
+ }
// restore the recents bit
if (wasRecentsVisible) {
mSystemUiVisibility |= View.RECENT_APPS_VISIBLE;
@@ -2666,17 +2430,21 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
private void checkBarModes() {
if (mDemoMode) return;
- checkBarMode(mStatusBarMode, mStatusBarWindowState, mStatusBarView.getBarTransitions());
+ checkBarMode(mStatusBarMode, mStatusBarWindowState, mStatusBarView.getBarTransitions(),
+ mNoAnimationOnNextBarModeChange);
if (mNavigationBarView != null) {
checkBarMode(mNavigationBarMode,
- mNavigationBarWindowState, mNavigationBarView.getBarTransitions());
+ mNavigationBarWindowState, mNavigationBarView.getBarTransitions(),
+ mNoAnimationOnNextBarModeChange);
}
+ mNoAnimationOnNextBarModeChange = false;
}
- private void checkBarMode(int mode, int windowState, BarTransitions transitions) {
+ private void checkBarMode(int mode, int windowState, BarTransitions transitions,
+ boolean noAnimation) {
final boolean powerSave = mBatteryController.isPowerSave();
- final boolean anim = (mScreenOn == null || mScreenOn) && windowState != WINDOW_STATE_HIDDEN
- && !powerSave;
+ final boolean anim = !noAnimation && (mScreenOn == null || mScreenOn)
+ && windowState != WINDOW_STATE_HIDDEN && !powerSave;
if (powerSave && getBarState() == StatusBarState.SHADE) {
mode = MODE_WARNING;
}
@@ -2710,13 +2478,17 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
// manually dismiss the volume panel when interacting with the nav bar
if (changing && interacting && barWindow == StatusBarManager.WINDOW_NAVIGATION_BAR) {
- if (mVolumeComponent != null) {
- mVolumeComponent.dismissNow();
- }
+ dismissVolumeDialog();
}
checkBarModes();
}
+ private void dismissVolumeDialog() {
+ if (mVolumeComponent != null) {
+ mVolumeComponent.dismissNow();
+ }
+ }
+
private void resumeSuspendedAutohide() {
if (mAutohideSuspended) {
scheduleAutohide();
@@ -2769,7 +2541,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
private void notifyUiVisibilityChanged(int vis) {
try {
- mWindowManagerService.statusBarVisibilityChanged(vis);
+ if (mLastDispatchedSystemUiVisibility != vis) {
+ mWindowManagerService.statusBarVisibilityChanged(vis);
+ mLastDispatchedSystemUiVisibility = vis;
+ }
} catch (RemoteException ex) {
}
}
@@ -2805,90 +2580,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
setNavigationIconHints(flags);
}
- @Override
- protected void tick(StatusBarNotification n, boolean firstTime) {
- if (!mTickerEnabled) return;
-
- // no ticking in lights-out mode
- if (!areLightsOn()) return;
-
- // no ticking in Setup
- if (!isDeviceProvisioned()) return;
-
- // not for you
- if (!isNotificationForCurrentProfiles(n)) return;
-
- // Show the ticker if one is requested. Also don't do this
- // until status bar window is attached to the window manager,
- // because... well, what's the point otherwise? And trying to
- // run a ticker without being attached will crash!
- if (n.getNotification().tickerText != null && mStatusBarWindow != null
- && mStatusBarWindow.getWindowToken() != null) {
- if (0 == (mDisabled & (StatusBarManager.DISABLE_NOTIFICATION_ICONS
- | StatusBarManager.DISABLE_NOTIFICATION_TICKER))) {
- mTicker.addEntry(n);
- }
- }
- }
-
- private class MyTicker extends Ticker {
- MyTicker(Context context, View sb) {
- super(context, sb);
- if (!mTickerEnabled) {
- Log.w(TAG, "MyTicker instantiated with mTickerEnabled=false", new Throwable());
- }
- }
-
- @Override
- public void tickerStarting() {
- if (!mTickerEnabled) return;
- mTicking = true;
- mStatusBarContents.setVisibility(View.GONE);
- mTickerView.setVisibility(View.VISIBLE);
- mTickerView.startAnimation(loadAnim(com.android.internal.R.anim.push_up_in, null));
- mStatusBarContents.startAnimation(loadAnim(com.android.internal.R.anim.push_up_out, null));
- }
-
- @Override
- public void tickerDone() {
- if (!mTickerEnabled) return;
- mStatusBarContents.setVisibility(View.VISIBLE);
- mTickerView.setVisibility(View.GONE);
- mStatusBarContents.startAnimation(loadAnim(com.android.internal.R.anim.push_down_in, null));
- mTickerView.startAnimation(loadAnim(com.android.internal.R.anim.push_down_out,
- mTickingDoneListener));
- }
-
- public void tickerHalting() {
- if (!mTickerEnabled) return;
- if (mStatusBarContents.getVisibility() != View.VISIBLE) {
- mStatusBarContents.setVisibility(View.VISIBLE);
- mStatusBarContents
- .startAnimation(loadAnim(com.android.internal.R.anim.fade_in, null));
- }
- mTickerView.setVisibility(View.GONE);
- // we do not animate the ticker away at this point, just get rid of it (b/6992707)
- }
- }
-
- Animation.AnimationListener mTickingDoneListener = new Animation.AnimationListener() {;
- public void onAnimationEnd(Animation animation) {
- mTicking = false;
- }
- public void onAnimationRepeat(Animation animation) {
- }
- public void onAnimationStart(Animation animation) {
- }
- };
-
- private Animation loadAnim(int id, Animation.AnimationListener listener) {
- Animation anim = AnimationUtils.loadAnimation(mContext, id);
- if (listener != null) {
- anim.setAnimationListener(listener);
- }
- return anim;
- }
-
public static String viewInfo(View v) {
return "[(" + v.getLeft() + "," + v.getTop() + ")(" + v.getRight() + "," + v.getBottom()
+ ") " + v.getWidth() + "x" + v.getHeight() + "]";
@@ -2899,11 +2590,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
pw.println("Current Status Bar state:");
pw.println(" mExpandedVisible=" + mExpandedVisible
+ ", mTrackingPosition=" + mTrackingPosition);
- pw.println(" mTickerEnabled=" + mTickerEnabled);
- if (mTickerEnabled) {
- pw.println(" mTicking=" + mTicking);
- pw.println(" mTickerView: " + viewInfo(mTickerView));
- }
pw.println(" mTracking=" + mTracking);
pw.println(" mDisplayMetrics=" + mDisplayMetrics);
pw.println(" mStackScroller: " + viewInfo(mStackScroller));
@@ -2922,8 +2608,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
pw.println(Settings.Global.zenModeToString(mZenMode));
pw.print(" mUseHeadsUp=");
pw.println(mUseHeadsUp);
- pw.print(" interrupting package: ");
- pw.println(hunStateToString(mHeadsUpNotificationView.getEntry()));
dumpBarTransitions(pw, "mStatusBarView", mStatusBarView.getBarTransitions());
if (mNavigationBarView != null) {
pw.print(" mNavigationBarWindowState=");
@@ -2972,12 +2656,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mNotificationData.dump(pw, " ");
}
- int N = mStatusIcons.getChildCount();
- pw.println(" system icons: " + N);
- for (int i=0; i<N; i++) {
- StatusBarIconView ic = (StatusBarIconView) mStatusIcons.getChildAt(i);
- pw.println(" [" + i + "] icon=" + ic);
- }
+ mIconController.dump(pw);
if (false) {
pw.println("see the logcat for a dump of the views we have created.");
@@ -3017,12 +2696,20 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
if (mNextAlarmController != null) {
mNextAlarmController.dump(fd, pw, args);
}
+ if (mAssistManager != null) {
+ mAssistManager.dump(fd, pw, args);
+ }
if (mSecurityController != null) {
mSecurityController.dump(fd, pw, args);
}
+ if (mHeadsUpManager != null) {
+ mHeadsUpManager.dump(fd, pw, args);
+ } else {
+ pw.println(" mHeadsUpManager: null");
+ }
+
pw.println("SharedPreferences:");
- for (Map.Entry<String, ?> entry : mContext.getSharedPreferences(mContext.getPackageName(),
- Context.MODE_PRIVATE).getAll().entrySet()) {
+ for (Map.Entry<String, ?> entry : Prefs.getAll(mContext).entrySet()) {
pw.print(" "); pw.print(entry.getKey()); pw.print("="); pw.println(entry.getValue());
}
}
@@ -3049,25 +2736,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());
}
- static final float saturate(float a) {
- return a < 0f ? 0f : (a > 1f ? 1f : a);
- }
-
- @Override
- public void updateExpandedViewPos(int thingy) {
- if (SPEW) Log.v(TAG, "updateExpandedViewPos");
-
- // on larger devices, the notification panel is propped open a bit
- mNotificationPanel.setMinimumHeight(
- (int)(mNotificationPanelMinHeightFrac * mCurrentDisplaySize.y));
-
- FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mNotificationPanel.getLayoutParams();
- lp.gravity = mNotificationPanelGravity;
- mNotificationPanel.setLayoutParams(lp);
-
- updateCarrierLabelVisibility(false);
- }
-
// called by makeStatusbar and also by PhoneStatusBarView
void updateDisplaySize() {
mDisplay.getMetrics(mDisplayMetrics);
@@ -3083,12 +2751,57 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
public void startActivityDismissingKeyguard(final Intent intent, boolean onlyProvisioned,
- final boolean dismissShade) {
+ boolean dismissShade) {
+ startActivityDismissingKeyguard(intent, onlyProvisioned, dismissShade, null /* callback */);
+ }
+
+ public void startActivityDismissingKeyguard(final Intent intent, boolean onlyProvisioned,
+ final boolean dismissShade, final Callback callback) {
if (onlyProvisioned && !isDeviceProvisioned()) return;
final boolean afterKeyguardGone = PreviewInflater.wouldLaunchResolverActivity(
mContext, intent, mCurrentUserId);
final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing();
+ Runnable runnable = new Runnable() {
+ public void run() {
+ mAssistManager.hideAssist();
+ intent.setFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ int result = ActivityManager.START_CANCELED;
+ try {
+ result = ActivityManagerNative.getDefault().startActivityAsUser(
+ null, mContext.getBasePackageName(),
+ intent,
+ intent.resolveTypeIfNeeded(mContext.getContentResolver()),
+ null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null, null,
+ UserHandle.CURRENT.getIdentifier());
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unable to start activity", e);
+ }
+ overrideActivityPendingAppTransition(
+ keyguardShowing && !afterKeyguardGone);
+ if (callback != null) {
+ callback.onActivityStarted(result);
+ }
+ }
+ };
+ Runnable cancelRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (callback != null) {
+ callback.onActivityStarted(ActivityManager.START_CANCELED);
+ }
+ }
+ };
+ executeRunnableDismissingKeyguard(runnable, cancelRunnable, dismissShade,
+ afterKeyguardGone);
+ }
+
+ public void executeRunnableDismissingKeyguard(final Runnable runnable,
+ final Runnable cancelAction,
+ final boolean dismissShade,
+ final boolean afterKeyguardGone) {
+ final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing();
dismissKeyguardThenExecute(new OnDismissAction() {
@Override
public boolean onDismiss() {
@@ -3099,23 +2812,20 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
ActivityManagerNative.getDefault()
.keyguardWaitingForActivityDrawn();
}
- intent.setFlags(
- Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
- mContext.startActivityAsUser(
- intent, new UserHandle(UserHandle.USER_CURRENT));
- overrideActivityPendingAppTransition(
- keyguardShowing && !afterKeyguardGone);
+ if (runnable != null) {
+ runnable.run();
+ }
} catch (RemoteException e) {
}
}
});
if (dismissShade) {
- animateCollapsePanels(
- CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
+ animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */,
+ true /* delayed*/);
}
return true;
}
- }, afterKeyguardGone);
+ }, cancelAction, afterKeyguardGone);
}
private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@@ -3135,7 +2845,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
mScreenOn = false;
notifyNavigationBarScreenOn(false);
- notifyHeadsUpScreenOn(false);
+ notifyHeadsUpScreenOff();
finishBarAnimations();
resetUserExpandedStates();
}
@@ -3175,10 +2885,15 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
@Override
- protected void dismissKeyguardThenExecute(final OnDismissAction action,
+ protected void dismissKeyguardThenExecute(OnDismissAction action, boolean afterKeyguardGone) {
+ dismissKeyguardThenExecute(action, null /* cancelRunnable */, afterKeyguardGone);
+ }
+
+ private void dismissKeyguardThenExecute(OnDismissAction action, Runnable cancelAction,
boolean afterKeyguardGone) {
if (mStatusBarKeyguardViewManager.isShowing()) {
- mStatusBarKeyguardViewManager.dismissWithAction(action, afterKeyguardGone);
+ mStatusBarKeyguardViewManager.dismissWithAction(action, cancelAction,
+ afterKeyguardGone);
} else {
action.onDismiss();
}
@@ -3195,11 +2910,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
updateDisplaySize(); // populates mDisplayMetrics
updateResources();
- updateClockSize();
repositionNavigationBar();
- updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
- updateShowSearchHoldoff();
updateRowStates();
+ mIconController.updateResources();
mScreenPinningRequest.onConfigurationChanged();
}
@@ -3212,12 +2925,16 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
updateNotifications();
resetUserSetupObserver();
setControllerUsers();
+ mAssistManager.onUserSwitched(newUserId);
}
private void setControllerUsers() {
if (mZenModeController != null) {
mZenModeController.setUserId(mCurrentUserId);
}
+ if (mSecurityController != null) {
+ mSecurityController.onUserSwitched(mCurrentUserId);
+ }
}
private void resetUserSetupObserver() {
@@ -3225,21 +2942,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mUserSetupObserver.onChange(false);
mContext.getContentResolver().registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE), true,
- mUserSetupObserver,
- mCurrentUserId);
- }
-
- private void setHeadsUpVisibility(boolean vis) {
- if (!ENABLE_HEADS_UP) return;
- if (DEBUG) Log.v(TAG, (vis ? "showing" : "hiding") + " heads up window");
- EventLog.writeEvent(EventLogTags.SYSUI_HEADS_UP_STATUS,
- vis ? mHeadsUpNotificationView.getKey() : "",
- vis ? 1 : 0);
- mHeadsUpNotificationView.setVisibility(vis ? View.VISIBLE : View.GONE);
- }
-
- public void onHeadsUpDismissed() {
- mHeadsUpNotificationView.dismiss();
+ mUserSetupObserver, mCurrentUserId);
}
/**
@@ -3256,61 +2959,21 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
loadDimens();
- mLinearOutSlowIn = AnimationUtils.loadInterpolator(
- mContext, android.R.interpolator.linear_out_slow_in);
if (mNotificationPanel != null) {
mNotificationPanel.updateResources();
}
- if (mHeadsUpNotificationView != null) {
- mHeadsUpNotificationView.updateResources();
- }
if (mBrightnessMirrorController != null) {
mBrightnessMirrorController.updateResources();
}
}
- private void updateClockSize() {
- if (mStatusBarView == null) return;
- TextView clock = (TextView) mStatusBarView.findViewById(R.id.clock);
- if (clock != null) {
- FontSizeUtils.updateFontSize(clock, R.dimen.status_bar_clock_size);
- }
- }
protected void loadDimens() {
final Resources res = mContext.getResources();
mNaturalBarHeight = res.getDimensionPixelSize(
com.android.internal.R.dimen.status_bar_height);
- int newIconSize = res.getDimensionPixelSize(
- com.android.internal.R.dimen.status_bar_icon_size);
- int newIconHPadding = res.getDimensionPixelSize(
- R.dimen.status_bar_icon_padding);
-
- if (newIconHPadding != mIconHPadding || newIconSize != mIconSize) {
-// Log.d(TAG, "size=" + newIconSize + " padding=" + newIconHPadding);
- mIconHPadding = newIconHPadding;
- mIconSize = newIconSize;
- //reloadAllNotificationIcons(); // reload the tray
- }
-
- mEdgeBorder = res.getDimensionPixelSize(R.dimen.status_bar_edge_ignore);
-
- mNotificationPanelGravity = res.getInteger(R.integer.notification_panel_layout_gravity);
- if (mNotificationPanelGravity <= 0) {
- mNotificationPanelGravity = Gravity.START | Gravity.TOP;
- }
-
- mCarrierLabelHeight = res.getDimensionPixelSize(R.dimen.carrier_label_height);
- mStatusBarHeaderHeight = res.getDimensionPixelSize(R.dimen.status_bar_header_height);
-
- mNotificationPanelMinHeightFrac = res.getFraction(R.dimen.notification_panel_min_height_frac, 1, 1);
- if (mNotificationPanelMinHeightFrac < 0f || mNotificationPanelMinHeightFrac > 1f) {
- mNotificationPanelMinHeightFrac = 0f;
- }
-
- mHeadsUpNotificationDecay = res.getInteger(R.integer.heads_up_notification_decay);
mRowMinHeight = res.getDimensionPixelSize(R.dimen.notification_min_height);
mRowMaxHeight = res.getDimensionPixelSize(R.dimen.notification_max_height);
@@ -3336,9 +2999,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
// Report all notifications as invisible and turn down the
// reporter.
if (!mCurrentlyVisibleNotifications.isEmpty()) {
- logNotificationVisibilityChanges(
- Collections.<String>emptyList(), mCurrentlyVisibleNotifications);
- mCurrentlyVisibleNotifications.clear();
+ logNotificationVisibilityChanges(Collections.<NotificationVisibility>emptyList(),
+ mCurrentlyVisibleNotifications);
+ recycleAllVisibilityObjects(mCurrentlyVisibleNotifications);
}
mHandler.removeCallbacks(mVisibilityReporter);
mStackScroller.setChildLocationsChangedListener(null);
@@ -3356,17 +3019,27 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
private void logNotificationVisibilityChanges(
- Collection<String> newlyVisible, Collection<String> noLongerVisible) {
+ Collection<NotificationVisibility> newlyVisible,
+ Collection<NotificationVisibility> noLongerVisible) {
if (newlyVisible.isEmpty() && noLongerVisible.isEmpty()) {
return;
}
- String[] newlyVisibleAr = newlyVisible.toArray(new String[newlyVisible.size()]);
- String[] noLongerVisibleAr = noLongerVisible.toArray(new String[noLongerVisible.size()]);
+ 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.
}
+
+ final int N = newlyVisible.size();
+ String[] newlyVisibleKeyAr = new String[N];
+ for (int i = 0; i < N; i++) {
+ newlyVisibleKeyAr[i] = newlyVisibleAr[i].key;
+ }
+ setNotificationsShown(newlyVisibleKeyAr);
}
// State logging
@@ -3443,29 +3116,22 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
};
@Override
- protected void haltTicker() {
- if (mTickerEnabled) {
- mTicker.halt();
- }
- }
-
- @Override
- protected boolean shouldDisableNavbarGestures() {
+ public boolean shouldDisableNavbarGestures() {
return !isDeviceProvisioned()
|| mExpandedVisible
- || (mDisabled & StatusBarManager.DISABLE_SEARCH) != 0;
+ || (mDisabled1 & StatusBarManager.DISABLE_SEARCH) != 0;
}
- public void postStartSettingsActivity(final Intent intent, int delay) {
+ public void postStartActivityDismissingKeyguard(final Intent intent, int delay) {
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
- handleStartSettingsActivity(intent, true /*onlyProvisioned*/);
+ handleStartActivityDismissingKeyguard(intent, true /*onlyProvisioned*/);
}
}, delay);
}
- private void handleStartSettingsActivity(Intent intent, boolean onlyProvisioned) {
+ private void handleStartActivityDismissingKeyguard(Intent intent, boolean onlyProvisioned) {
startActivityDismissingKeyguard(intent, onlyProvisioned, true /* dismissShade */);
}
@@ -3486,7 +3152,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
@Override
- public void setColorFilter(ColorFilter cf) {
+ public void setColorFilter(ColorFilter colorFilter) {
}
@Override
@@ -3519,11 +3185,24 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mHandlerThread = null;
}
mContext.unregisterReceiver(mBroadcastReceiver);
+ mAssistManager.destroy();
+
+ final SignalClusterView signalCluster =
+ (SignalClusterView) mStatusBarView.findViewById(R.id.signal_cluster);
+ final SignalClusterView signalClusterKeyguard =
+ (SignalClusterView) mKeyguardStatusBar.findViewById(R.id.signal_cluster);
+ final SignalClusterView signalClusterQs =
+ (SignalClusterView) mHeader.findViewById(R.id.signal_cluster);
+ mNetworkController.removeSignalCallback(signalCluster);
+ mNetworkController.removeSignalCallback(signalClusterKeyguard);
+ mNetworkController.removeSignalCallback(signalClusterQs);
+ if (mQSPanel != null && mQSPanel.getHost() != null) {
+ mQSPanel.getHost().destroy();
+ }
}
private boolean mDemoModeAllowed;
private boolean mDemoMode;
- private DemoStatusIcons mDemoStatusIcons;
@Override
public void dispatchDemoCommand(String command, Bundle args) {
@@ -3552,10 +3231,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
dispatchDemoCommandToView(command, args, R.id.battery);
}
if (modeChange || command.equals(COMMAND_STATUS)) {
- if (mDemoStatusIcons == null) {
- mDemoStatusIcons = new DemoStatusIcons(mStatusIcons, mIconSize);
- }
- mDemoStatusIcons.dispatchDemoCommand(command, args);
+ mIconController.dispatchDemoCommand(command, args);
+
}
if (mNetworkController != null && (modeChange || command.equals(COMMAND_NETWORK))) {
mNetworkController.dispatchDemoCommand(command, args);
@@ -3604,12 +3281,15 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
return mState;
}
+ @Override
+ protected boolean isPanelFullyCollapsed() {
+ return mNotificationPanel.isFullyCollapsed();
+ }
+
public void showKeyguard() {
if (mLaunchTransitionFadingAway) {
mNotificationPanel.animate().cancel();
- mNotificationPanel.setAlpha(1f);
- runLaunchTransitionEndRunnable();
- mLaunchTransitionFadingAway = false;
+ onLaunchTransitionFadingEnded();
}
mHandler.removeMessages(MSG_LAUNCH_TRANSITION_TIMEOUT);
setBarState(StatusBarState.KEYGUARD);
@@ -3625,11 +3305,19 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mLeaveOpenOnKeyguardHide = false;
if (mDraggedDownRow != null) {
mDraggedDownRow.setUserLocked(false);
- mDraggedDownRow.notifyHeightChanged();
+ mDraggedDownRow.notifyHeightChanged(false /* needsAnimation */);
mDraggedDownRow = null;
}
}
+ private void onLaunchTransitionFadingEnded() {
+ mNotificationPanel.setAlpha(1.0f);
+ runLaunchTransitionEndRunnable();
+ mLaunchTransitionFadingAway = false;
+ mScrimController.forceHideScrims(false /* hide */);
+ updateMediaMetaData(true /* metaDataChanged */);
+ }
+
public boolean isCollapsing() {
return mNotificationPanel.isCollapsing();
}
@@ -3661,6 +3349,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
if (beforeFading != null) {
beforeFading.run();
}
+ mScrimController.forceHideScrims(true /* hide */);
+ updateMediaMetaData(false);
mNotificationPanel.setAlpha(1);
mNotificationPanel.animate()
.alpha(0)
@@ -3670,11 +3360,11 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
.withEndAction(new Runnable() {
@Override
public void run() {
- mNotificationPanel.setAlpha(1);
- runLaunchTransitionEndRunnable();
- mLaunchTransitionFadingAway = false;
+ onLaunchTransitionFadingEnded();
}
});
+ mIconController.appTransitionStarting(SystemClock.uptimeMillis(),
+ StatusBarIconController.DEFAULT_TINT_ANIMATION_DURATION);
}
};
if (mNotificationPanel.isLaunchTransitionRunning()) {
@@ -3742,17 +3432,32 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
/**
+ * Notifies the status bar that Keyguard is going away very soon.
+ */
+ public void keyguardGoingAway() {
+
+ // Treat Keyguard exit animation as an app transition to achieve nice transition for status
+ // bar.
+ mIconController.appTransitionPending();
+ }
+
+ /**
* Notifies the status bar the Keyguard is fading away with the specified timings.
*
- * @param delay the animation delay in miliseconds
+ * @param startTime the start time of the animations in uptime millis
+ * @param delay the precalculated animation delay in miliseconds
* @param fadeoutDuration the duration of the exit animation, in milliseconds
*/
- public void setKeyguardFadingAway(long delay, long fadeoutDuration) {
+ public void setKeyguardFadingAway(long startTime, long delay, long fadeoutDuration) {
mKeyguardFadingAway = true;
mKeyguardFadingAwayDelay = delay;
mKeyguardFadingAwayDuration = fadeoutDuration;
mWaitingForKeyguardExit = false;
- disable(mDisabledUnmodified, true /* animate */);
+ mIconController.appTransitionStarting(
+ startTime + fadeoutDuration
+ - StatusBarIconController.DEFAULT_TINT_ANIMATION_DURATION,
+ StatusBarIconController.DEFAULT_TINT_ANIMATION_DURATION);
+ disable(mDisabledUnmodified1, mDisabledUnmodified2, true /* animate */);
}
public boolean isKeyguardFadingAway() {
@@ -3766,9 +3471,14 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mKeyguardFadingAway = false;
}
+ public void stopWaitingForKeyguardExit() {
+ mWaitingForKeyguardExit = false;
+ }
+
private void updatePublicMode() {
- setLockscreenPublicMode(mStatusBarKeyguardViewManager.isShowing()
- && mStatusBarKeyguardViewManager.isSecure(mCurrentUserId));
+ setLockscreenPublicMode(
+ mStatusBarKeyguardViewManager.isShowing() && mStatusBarKeyguardViewManager
+ .isSecure(mCurrentUserId));
}
private void updateKeyguardState(boolean goingToFullShade, boolean fromShadeLocked) {
@@ -3776,6 +3486,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mKeyguardIndicationController.setVisible(true);
mNotificationPanel.resetViews();
mKeyguardUserSwitcher.setKeyguard(true, fromShadeLocked);
+ mStatusBarView.removePendingHideExpandedRunnables();
} else {
mKeyguardIndicationController.setVisible(false);
mKeyguardUserSwitcher.setKeyguard(false,
@@ -3783,8 +3494,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) {
mScrimController.setKeyguardShowing(true);
+ mIconPolicy.setKeyguardShowing(true);
} else {
mScrimController.setKeyguardShowing(false);
+ mIconPolicy.setKeyguardShowing(false);
}
mNotificationPanel.setBarState(mState, mKeyguardFadingAway, goingToFullShade);
updateDozingState();
@@ -3792,16 +3505,12 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
updateStackScrollerState(goingToFullShade);
updateNotifications();
checkBarModes();
- updateCarrierLabelVisibility(false);
updateMediaMetaData(false);
mKeyguardMonitor.notifyKeyguardState(mStatusBarKeyguardViewManager.isShowing(),
mStatusBarKeyguardViewManager.isSecure());
}
private void updateDozingState() {
- if (mState != StatusBarState.KEYGUARD && !mNotificationPanel.isDozing()) {
- return;
- }
boolean animate = !mDozing && mDozeScrimController.isPulsing();
mNotificationPanel.setDozing(mDozing, animate);
mStackScroller.setDark(mDozing, animate, mScreenOnTouchLocation);
@@ -3878,6 +3587,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
// Make our window larger and the panel expanded.
makeExpandedVisible(true);
mNotificationPanel.instantExpand();
+ if (DEBUG_EMPTY_KEYGUARD) {
+ mStatusBarWindowManager.setLogState(true);
+ }
}
private void instantCollapseNotificationPanel() {
@@ -3913,7 +3625,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
}
mState = state;
+ mGroupManager.setStatusBarState(state);
mStatusBarWindowManager.setStatusBarState(state);
+ updateDozing();
}
@Override
@@ -3945,6 +3659,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mKeyguardIndicationController.showTransientIndication(R.string.camera_hint);
}
+ public void onVoiceAssistHintStarted() {
+ mKeyguardIndicationController.showTransientIndication(R.string.voice_hint);
+ }
+
public void onPhoneHintStarted() {
mKeyguardIndicationController.showTransientIndication(R.string.phone_hint);
}
@@ -4046,13 +3764,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
}
- /**
- * @return a ViewGroup that spans the entire panel which contains the quick settings
- */
- public ViewGroup getQuickSettingsOverlayParent() {
- return mNotificationPanel;
- }
-
public long getKeyguardFadingAwayDelay() {
return mKeyguardFadingAwayDelay;
}
@@ -4061,18 +3772,11 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
return mKeyguardFadingAwayDuration;
}
- public LinearLayout getSystemIcons() {
- return mSystemIcons;
- }
-
- public LinearLayout getSystemIconArea() {
- return mSystemIconArea;
- }
-
@Override
public void setBouncerShowing(boolean bouncerShowing) {
super.setBouncerShowing(bouncerShowing);
- disable(mDisabledUnmodified, true /* animate */);
+ mStatusBarView.setBouncerShowing(bouncerShowing);
+ disable(mDisabledUnmodified1, mDisabledUnmodified2, true /* animate */);
}
public void onScreenTurnedOff() {
@@ -4116,7 +3820,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
if ((time - mLastLockToAppLongPress) < LOCK_TO_APP_GESTURE_TOLERENCE) {
activityManager.stopLockTaskModeOnCurrent();
// When exiting refresh disabled flags.
- mNavigationBarView.setDisabledFlags(mDisabled, true);
+ mNavigationBarView.setDisabledFlags(mDisabled1, true);
} else if ((v.getId() == R.id.back)
&& !mNavigationBarView.getRecentsButton().isPressed()) {
// If we aren't pressing recents right now then they presses
@@ -4133,7 +3837,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
// should stop lock task.
activityManager.stopLockTaskModeOnCurrent();
// When exiting refresh disabled flags.
- mNavigationBarView.setDisabledFlags(mDisabled, true);
+ mNavigationBarView.setDisabledFlags(mDisabled1, true);
}
}
if (sendBackLongPress) {
@@ -4208,9 +3912,43 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mScreenOnComingFromTouch = true;
mScreenOnTouchLocation = new PointF(event.getX(), event.getY());
mNotificationPanel.setTouchDisabled(false);
+ mStatusBarKeyguardViewManager.notifyScreenWakeUpRequested();
}
}
+ @Override
+ public void appTransitionPending() {
+
+ // Use own timings when Keyguard is going away, see keyguardGoingAway and
+ // setKeyguardFadingAway
+ if (!mKeyguardFadingAway) {
+ mIconController.appTransitionPending();
+ }
+ }
+
+ @Override
+ public void appTransitionCancelled() {
+ mIconController.appTransitionCancelled();
+ }
+
+ @Override
+ public void appTransitionStarting(long startTime, long duration) {
+
+ // Use own timings when Keyguard is going away, see keyguardGoingAway and
+ // setKeyguardFadingAway
+ if (!mKeyguardFadingAway) {
+ mIconController.appTransitionStarting(startTime, duration);
+ }
+ if (mIconPolicy != null) {
+ mIconPolicy.appTransitionStarting(startTime, duration);
+ }
+ }
+
+ private void updateDozing() {
+ mDozing = mDozingRequested && mState == StatusBarState.KEYGUARD;
+ updateDozingState();
+ }
+
private final class ShadeUpdates {
private final ArraySet<String> mVisibleNotifications = new ArraySet<String>();
private final ArraySet<String> mNewVisibleNotifications = new ArraySet<String>();
@@ -4316,10 +4054,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
private void handleStartDozing(@NonNull Runnable ready) {
- if (!mDozing) {
- mDozing = true;
+ if (!mDozingRequested) {
+ mDozingRequested = true;
DozeLog.traceDozing(mContext, mDozing);
- updateDozingState();
+ updateDozing();
}
ready.run();
}
@@ -4329,10 +4067,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
private void handleStopDozing() {
- if (mDozing) {
- mDozing = false;
+ if (mDozingRequested) {
+ mDozingRequested = false;
DozeLog.traceDozing(mContext, mDozing);
- updateDozingState();
+ updateDozing();
}
}
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 5c254a26..59e1bba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -16,16 +16,23 @@
package com.android.systemui.statusbar.phone;
+import android.app.ActivityManagerNative;
import android.app.AlarmManager;
+import android.app.AlarmManager.AlarmClockInfo;
+import android.app.IUserSwitchObserver;
import android.app.StatusBarManager;
import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.UserInfo;
import android.media.AudioManager;
import android.os.Handler;
+import android.os.IRemoteCallback;
+import android.os.RemoteException;
import android.os.UserHandle;
+import android.os.UserManager;
import android.provider.Settings.Global;
import android.telecom.TelecomManager;
import android.util.Log;
@@ -33,6 +40,7 @@ import android.util.Log;
import com.android.internal.telephony.IccCardConstants;
import com.android.internal.telephony.TelephonyIntents;
import com.android.systemui.R;
+import com.android.systemui.qs.tiles.DndTile;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.CastController.CastDevice;
import com.android.systemui.statusbar.policy.HotspotController;
@@ -46,23 +54,21 @@ public class PhoneStatusBarPolicy {
private static final String TAG = "PhoneStatusBarPolicy";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- private static final boolean SHOW_SYNC_ICON = false;
-
- private static final String SLOT_SYNC_ACTIVE = "sync_active";
private static final String SLOT_CAST = "cast";
private static final String SLOT_HOTSPOT = "hotspot";
private static final String SLOT_BLUETOOTH = "bluetooth";
private static final String SLOT_TTY = "tty";
private static final String SLOT_ZEN = "zen";
private static final String SLOT_VOLUME = "volume";
- private static final String SLOT_CDMA_ERI = "cdma_eri";
private static final String SLOT_ALARM_CLOCK = "alarm_clock";
+ private static final String SLOT_MANAGED_PROFILE = "managed_profile";
private final Context mContext;
private final StatusBarManager mService;
private final Handler mHandler = new Handler();
private final CastController mCast;
private final HotspotController mHotspot;
+ private final AlarmManager mAlarmManager;
// Assume it's all good unless we hear otherwise. We don't always seem
// to get broadcasts that it *is* there.
@@ -70,11 +76,16 @@ public class PhoneStatusBarPolicy {
private boolean mZenVisible;
private boolean mVolumeVisible;
+ private boolean mCurrentUserSetup;
private int mZen;
private boolean mBluetoothEnabled = false;
+ private boolean mManagedProfileFocused = false;
+ private boolean mManagedProfileIconVisible = true;
+
+ private boolean mKeyguardVisible = true;
private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
@@ -83,9 +94,6 @@ public class PhoneStatusBarPolicy {
if (action.equals(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED)) {
updateAlarm();
}
- else if (action.equals(Intent.ACTION_SYNC_STATE_CHANGED)) {
- updateSyncState(intent);
- }
else if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED) ||
action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) {
updateBluetooth();
@@ -100,9 +108,6 @@ public class PhoneStatusBarPolicy {
else if (action.equals(TelecomManager.ACTION_CURRENT_TTY_MODE_CHANGED)) {
updateTTY(intent);
}
- else if (action.equals(Intent.ACTION_USER_SWITCHED)) {
- updateAlarm();
- }
}
};
@@ -110,29 +115,31 @@ public class PhoneStatusBarPolicy {
mContext = context;
mCast = cast;
mHotspot = hotspot;
- mService = (StatusBarManager)context.getSystemService(Context.STATUS_BAR_SERVICE);
+ mService = (StatusBarManager) context.getSystemService(Context.STATUS_BAR_SERVICE);
+ mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
// listen for broadcasts
IntentFilter filter = new IntentFilter();
filter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
- filter.addAction(Intent.ACTION_SYNC_STATE_CHANGED);
filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
filter.addAction(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION);
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
filter.addAction(TelecomManager.ACTION_CURRENT_TTY_MODE_CHANGED);
- filter.addAction(Intent.ACTION_USER_SWITCHED);
mContext.registerReceiver(mIntentReceiver, filter, null, mHandler);
+ // listen for user / profile change.
+ try {
+ ActivityManagerNative.getDefault().registerUserSwitchObserver(mUserSwitchListener);
+ } catch (RemoteException e) {
+ // Ignore
+ }
+
// TTY status
mService.setIcon(SLOT_TTY, R.drawable.stat_sys_tty_mode, 0, null);
mService.setIconVisibility(SLOT_TTY, false);
- // Cdma Roaming Indicator, ERI
- mService.setIcon(SLOT_CDMA_ERI, R.drawable.stat_sys_roaming_cdma_0, 0, null);
- mService.setIconVisibility(SLOT_CDMA_ERI, false);
-
// bluetooth status
updateBluetooth();
@@ -140,11 +147,6 @@ public class PhoneStatusBarPolicy {
mService.setIcon(SLOT_ALARM_CLOCK, R.drawable.stat_sys_alarm, 0, null);
mService.setIconVisibility(SLOT_ALARM_CLOCK, false);
- // Sync state
- mService.setIcon(SLOT_SYNC_ACTIVE, R.drawable.stat_sys_sync, 0, null);
- mService.setIconVisibility(SLOT_SYNC_ACTIVE, false);
- // "sync_failing" is obsolete: b/1297963
-
// zen
mService.setIcon(SLOT_ZEN, R.drawable.stat_sys_zen_important, 0, null);
mService.setIconVisibility(SLOT_ZEN, false);
@@ -160,9 +162,15 @@ public class PhoneStatusBarPolicy {
mCast.addCallback(mCastCallback);
// hotspot
- mService.setIcon(SLOT_HOTSPOT, R.drawable.stat_sys_hotspot, 0, null);
+ mService.setIcon(SLOT_HOTSPOT, R.drawable.stat_sys_hotspot, 0,
+ mContext.getString(R.string.accessibility_status_bar_hotspot));
mService.setIconVisibility(SLOT_HOTSPOT, mHotspot.isHotspotEnabled());
mHotspot.addCallback(mHotspotCallback);
+
+ // managed profile
+ mService.setIcon(SLOT_MANAGED_PROFILE, R.drawable.stat_sys_managed_profile_status, 0,
+ mContext.getString(R.string.accessibility_managed_profile));
+ mService.setIconVisibility(SLOT_MANAGED_PROFILE, false);
}
public void setZenMode(int zen) {
@@ -171,15 +179,12 @@ public class PhoneStatusBarPolicy {
}
private void updateAlarm() {
- AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
- boolean alarmSet = alarmManager.getNextAlarmClock(UserHandle.USER_CURRENT) != null;
- mService.setIconVisibility(SLOT_ALARM_CLOCK, alarmSet);
- }
-
- private final void updateSyncState(Intent intent) {
- if (!SHOW_SYNC_ICON) return;
- boolean isActive = intent.getBooleanExtra("active", false);
- mService.setIconVisibility(SLOT_SYNC_ACTIVE, isActive);
+ final AlarmClockInfo alarm = mAlarmManager.getNextAlarmClock(UserHandle.USER_CURRENT);
+ final boolean hasAlarm = alarm != null && alarm.getTriggerTime() > 0;
+ final boolean zenNone = mZen == Global.ZEN_MODE_NO_INTERRUPTIONS;
+ mService.setIcon(SLOT_ALARM_CLOCK, zenNone ? R.drawable.stat_sys_alarm_dim
+ : R.drawable.stat_sys_alarm, 0, null);
+ mService.setIconVisibility(SLOT_ALARM_CLOCK, mCurrentUserSetup && hasAlarm);
}
private final void updateSimState(Intent intent) {
@@ -221,17 +226,27 @@ public class PhoneStatusBarPolicy {
int volumeIconId = 0;
String volumeDescription = null;
- if (mZen == Global.ZEN_MODE_NO_INTERRUPTIONS) {
+ if (DndTile.isVisible(mContext) || DndTile.isCombinedIcon(mContext)) {
+ zenVisible = mZen != Global.ZEN_MODE_OFF;
+ zenIconId = mZen == Global.ZEN_MODE_NO_INTERRUPTIONS
+ ? R.drawable.stat_sys_dnd_total_silence : R.drawable.stat_sys_dnd;
+ zenDescription = mContext.getString(R.string.quick_settings_dnd_label);
+ } else if (mZen == Global.ZEN_MODE_NO_INTERRUPTIONS) {
zenVisible = true;
zenIconId = R.drawable.stat_sys_zen_none;
- zenDescription = mContext.getString(R.string.zen_no_interruptions);
+ zenDescription = mContext.getString(R.string.interruption_level_none);
} else if (mZen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
zenVisible = true;
zenIconId = R.drawable.stat_sys_zen_important;
- zenDescription = mContext.getString(R.string.zen_important_interruptions);
+ zenDescription = mContext.getString(R.string.interruption_level_priority);
}
- if (mZen != Global.ZEN_MODE_NO_INTERRUPTIONS &&
+ if (DndTile.isVisible(mContext) && !DndTile.isCombinedIcon(mContext)
+ && audioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_SILENT) {
+ volumeVisible = true;
+ volumeIconId = R.drawable.stat_sys_ringer_silent;
+ volumeDescription = mContext.getString(R.string.accessibility_ringer_silent);
+ } else if (mZen != Global.ZEN_MODE_NO_INTERRUPTIONS && mZen != Global.ZEN_MODE_ALARMS &&
audioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_VIBRATE) {
volumeVisible = true;
volumeIconId = R.drawable.stat_sys_ringer_vibrate;
@@ -253,6 +268,7 @@ public class PhoneStatusBarPolicy {
mService.setIconVisibility(SLOT_VOLUME, volumeVisible);
mVolumeVisible = volumeVisible;
}
+ updateAlarm();
}
private final void updateBluetooth() {
@@ -311,6 +327,53 @@ public class PhoneStatusBarPolicy {
mService.setIconVisibility(SLOT_CAST, isCasting);
}
+ private void profileChanged(int userId) {
+ UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ UserInfo user = null;
+ if (userId == UserHandle.USER_CURRENT) {
+ try {
+ user = ActivityManagerNative.getDefault().getCurrentUser();
+ } catch (RemoteException e) {
+ // Ignore
+ }
+ } else {
+ user = userManager.getUserInfo(userId);
+ }
+
+ mManagedProfileFocused = user != null && user.isManagedProfile();
+ if (DEBUG) Log.v(TAG, "profileChanged: mManagedProfileFocused: " + mManagedProfileFocused);
+ // Actually update the icon later when transition starts.
+ }
+
+ private void updateManagedProfile() {
+ if (DEBUG) Log.v(TAG, "updateManagedProfile: mManagedProfileFocused: "
+ + mManagedProfileFocused
+ + " mKeyguardVisible: " + mKeyguardVisible);
+ boolean showIcon = mManagedProfileFocused && !mKeyguardVisible;
+ if (mManagedProfileIconVisible != showIcon) {
+ mService.setIconVisibility(SLOT_MANAGED_PROFILE, showIcon);
+ mManagedProfileIconVisible = showIcon;
+ }
+ }
+
+ private final IUserSwitchObserver.Stub mUserSwitchListener =
+ new IUserSwitchObserver.Stub() {
+ @Override
+ public void onUserSwitching(int newUserId, IRemoteCallback reply) {
+ }
+
+ @Override
+ public void onUserSwitchComplete(int newUserId) throws RemoteException {
+ updateAlarm();
+ profileChanged(newUserId);
+ }
+
+ @Override
+ public void onForegroundProfileSwitch(int newProfileId) {
+ profileChanged(newProfileId);
+ }
+ };
+
private final HotspotController.Callback mHotspotCallback = new HotspotController.Callback() {
@Override
public void onHotspotChanged(boolean enabled) {
@@ -324,4 +387,19 @@ public class PhoneStatusBarPolicy {
updateCast();
}
};
+
+ public void appTransitionStarting(long startTime, long duration) {
+ updateManagedProfile();
+ }
+
+ public void setKeyguardShowing(boolean visible) {
+ mKeyguardVisible = visible;
+ updateManagedProfile();
+ }
+
+ public void setCurrentUserSetup(boolean userSetup) {
+ if (mCurrentUserSetup == userSetup) return;
+ mCurrentUserSetup = userSetup;
+ updateAlarm();
+ }
}
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 7cbf13f..6a46924 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -20,12 +20,14 @@ import android.content.Context;
import android.content.res.Resources;
import android.util.AttributeSet;
import android.util.EventLog;
+import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import com.android.systemui.EventLogTags;
import com.android.systemui.R;
+import com.android.systemui.statusbar.StatusBarState;
public class PhoneStatusBarView extends PanelBar {
private static final String TAG = "PhoneStatusBarView";
@@ -38,6 +40,14 @@ public class PhoneStatusBarView extends PanelBar {
PanelView mNotificationPanel;
private final PhoneStatusBarTransitions mBarTransitions;
private ScrimController mScrimController;
+ private float mMinFraction;
+ private float mPanelFraction;
+ private Runnable mHideExpandedRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mBar.makeExpandedInvisible();
+ }
+ };
public PhoneStatusBarView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -77,8 +87,8 @@ public class PhoneStatusBarView extends PanelBar {
}
@Override
- public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
- if (super.onRequestSendAccessibilityEvent(child, event)) {
+ public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) {
+ if (super.onRequestSendAccessibilityEventInternal(child, event)) {
// The status bar is very small so augment the view that the user is touching
// with the content of the status bar a whole. This way an accessibility service
// may announce the current item as well as the entire content if appropriate.
@@ -108,17 +118,20 @@ public class PhoneStatusBarView extends PanelBar {
@Override
public void onAllPanelsCollapsed() {
super.onAllPanelsCollapsed();
-
+ if (PhoneStatusBar.DEBUG_EMPTY_KEYGUARD
+ && mBar.getBarState() == StatusBarState.KEYGUARD) {
+ Log.i(PhoneStatusBar.TAG, "Panel collapsed! Stacktrace: "
+ + Log.getStackTraceString(new Throwable()));
+ }
// Close the status bar in the next frame so we can show the end of the animation.
- postOnAnimation(new Runnable() {
- @Override
- public void run() {
- mBar.makeExpandedInvisible();
- }
- });
+ postOnAnimation(mHideExpandedRunnable);
mLastFullyOpenedPanel = null;
}
+ public void removePendingHideExpandedRunnables() {
+ removeCallbacks(mHideExpandedRunnable);
+ }
+
@Override
public void onPanelFullyOpened(PanelView openPanel) {
super.onPanelFullyOpened(openPanel);
@@ -174,9 +187,22 @@ public class PhoneStatusBarView extends PanelBar {
}
@Override
+ public void panelScrimMinFractionChanged(float minFraction) {
+ if (mMinFraction != minFraction) {
+ mMinFraction = minFraction;
+ updateScrimFraction();
+ }
+ }
+
+ @Override
public void panelExpansionChanged(PanelView panel, float frac, boolean expanded) {
super.panelExpansionChanged(panel, frac, expanded);
- mScrimController.setPanelExpansion(frac);
- mBar.updateCarrierLabelVisibility(false);
+ mPanelFraction = frac;
+ updateScrimFraction();
+ }
+
+ private void updateScrimFraction() {
+ float scrimFraction = Math.max(mPanelFraction - mMinFraction / (1.0f - mMinFraction), 0);
+ mScrimController.setPanelExpansion(scrimFraction);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
index 45a1386..12434ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
@@ -19,13 +19,9 @@ package com.android.systemui.statusbar.phone;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.Process;
-import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
-import android.provider.Settings.Secure;
+import android.os.Process;
import android.util.Log;
import com.android.systemui.R;
@@ -35,24 +31,26 @@ import com.android.systemui.qs.tiles.BluetoothTile;
import com.android.systemui.qs.tiles.CastTile;
import com.android.systemui.qs.tiles.CellularTile;
import com.android.systemui.qs.tiles.ColorInversionTile;
+import com.android.systemui.qs.tiles.DndTile;
import com.android.systemui.qs.tiles.FlashlightTile;
import com.android.systemui.qs.tiles.HotspotTile;
import com.android.systemui.qs.tiles.IntentTile;
import com.android.systemui.qs.tiles.LocationTile;
import com.android.systemui.qs.tiles.RotationLockTile;
import com.android.systemui.qs.tiles.WifiTile;
-import com.android.systemui.settings.CurrentUserTracker;
import com.android.systemui.statusbar.policy.BluetoothController;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.FlashlightController;
+import com.android.systemui.statusbar.policy.HotspotController;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
import com.android.systemui.statusbar.policy.LocationController;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.RotationLockController;
-import com.android.systemui.statusbar.policy.HotspotController;
import com.android.systemui.statusbar.policy.SecurityController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.tuner.TunerService;
+import com.android.systemui.tuner.TunerService.Tunable;
import java.util.ArrayList;
import java.util.Arrays;
@@ -62,16 +60,16 @@ import java.util.List;
import java.util.Map;
/** Platform implementation of the quick settings tile host **/
-public class QSTileHost implements QSTile.Host {
+public class QSTileHost implements QSTile.Host, Tunable {
private static final String TAG = "QSTileHost";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- private static final String TILES_SETTING = "sysui_qs_tiles";
+ protected static final String TILES_SETTING = "sysui_qs_tiles";
private final Context mContext;
private final PhoneStatusBar mStatusBar;
private final LinkedHashMap<String, QSTile<?>> mTiles = new LinkedHashMap<>();
- private final Observer mObserver = new Observer();
+ protected final ArrayList<String> mTileSpecs = new ArrayList<>();
private final BluetoothController mBluetooth;
private final LocationController mLocation;
private final RotationLockController mRotation;
@@ -80,7 +78,6 @@ public class QSTileHost implements QSTile.Host {
private final HotspotController mHotspot;
private final CastController mCast;
private final Looper mLooper;
- private final CurrentUserTracker mUserTracker;
private final FlashlightController mFlashlight;
private final UserSwitcherController mUserSwitcherController;
private final KeyguardMonitor mKeyguard;
@@ -114,22 +111,11 @@ public class QSTileHost implements QSTile.Host {
ht.start();
mLooper = ht.getLooper();
- mUserTracker = new CurrentUserTracker(mContext) {
- @Override
- public void onUserSwitched(int newUserId) {
- recreateTiles();
- for (QSTile<?> tile : mTiles.values()) {
- tile.userSwitch(newUserId);
- }
- mSecurity.onUserSwitched(newUserId);
- mNetwork.onUserSwitched(newUserId);
- mObserver.register();
- }
- };
- recreateTiles();
+ TunerService.get(mContext).addTunable(this, TILES_SETTING);
+ }
- mUserTracker.startTracking();
- mObserver.register();
+ public void destroy() {
+ TunerService.get(mContext).removeTunable(this);
}
@Override
@@ -143,8 +129,8 @@ public class QSTileHost implements QSTile.Host {
}
@Override
- public void startSettingsActivity(final Intent intent) {
- mStatusBar.postStartSettingsActivity(intent, 0);
+ public void startActivityDismissingKeyguard(final Intent intent) {
+ mStatusBar.postStartActivityDismissingKeyguard(intent, 0);
}
@Override
@@ -219,10 +205,15 @@ public class QSTileHost implements QSTile.Host {
public SecurityController getSecurityController() {
return mSecurity;
}
-
- private void recreateTiles() {
+
+ @Override
+ public void onTuningChanged(String key, String newValue) {
+ if (!TILES_SETTING.equals(key)) {
+ return;
+ }
if (DEBUG) Log.d(TAG, "Recreating tiles");
- final List<String> tileSpecs = loadTileSpecs();
+ final List<String> tileSpecs = loadTileSpecs(newValue);
+ if (tileSpecs.equals(mTileSpecs)) return;
for (Map.Entry<String, QSTile<?>> tile : mTiles.entrySet()) {
if (!tileSpecs.contains(tile.getKey())) {
if (DEBUG) Log.d(TAG, "Destroying tile: " + tile.getKey());
@@ -242,7 +233,8 @@ public class QSTileHost implements QSTile.Host {
}
}
}
- if (mTiles.equals(newTiles)) return;
+ mTileSpecs.clear();
+ mTileSpecs.addAll(tileSpecs);
mTiles.clear();
mTiles.putAll(newTiles);
if (mCallback != null) {
@@ -250,12 +242,13 @@ public class QSTileHost implements QSTile.Host {
}
}
- private QSTile<?> createTile(String tileSpec) {
+ protected QSTile<?> createTile(String tileSpec) {
if (tileSpec.equals("wifi")) return new WifiTile(this);
else if (tileSpec.equals("bt")) return new BluetoothTile(this);
else if (tileSpec.equals("inversion")) return new ColorInversionTile(this);
else if (tileSpec.equals("cell")) return new CellularTile(this);
else if (tileSpec.equals("airplane")) return new AirplaneModeTile(this);
+ else if (tileSpec.equals("dnd")) return new DndTile(this);
else if (tileSpec.equals("rotation")) return new RotationLockTile(this);
else if (tileSpec.equals("flashlight")) return new FlashlightTile(this);
else if (tileSpec.equals("location")) return new LocationTile(this);
@@ -265,11 +258,9 @@ public class QSTileHost implements QSTile.Host {
else throw new IllegalArgumentException("Bad tile spec: " + tileSpec);
}
- private List<String> loadTileSpecs() {
+ protected List<String> loadTileSpecs(String tileList) {
final Resources res = mContext.getResources();
final String defaultTileList = res.getString(R.string.quick_settings_tiles_default);
- String tileList = Secure.getStringForUser(mContext.getContentResolver(), TILES_SETTING,
- mUserTracker.getCurrentUserId());
if (tileList == null) {
tileList = res.getString(R.string.quick_settings_tiles);
if (DEBUG) Log.d(TAG, "Loaded tile specs from config: " + tileList);
@@ -292,26 +283,4 @@ public class QSTileHost implements QSTile.Host {
}
return tiles;
}
-
- private class Observer extends ContentObserver {
- private boolean mRegistered;
-
- public Observer() {
- super(new Handler(Looper.getMainLooper()));
- }
-
- public void register() {
- if (mRegistered) {
- mContext.getContentResolver().unregisterContentObserver(this);
- }
- mContext.getContentResolver().registerContentObserver(Secure.getUriFor(TILES_SETTING),
- false, this, mUserTracker.getCurrentUserId());
- mRegistered = true;
- }
-
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- recreateTiles();
- }
- }
}
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 0e8a794..bacf890 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.phone;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Color;
@@ -29,24 +30,32 @@ import android.view.animation.Interpolator;
import com.android.systemui.R;
import com.android.systemui.statusbar.BackDropView;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.ScrimView;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.stack.StackStateAnimator;
/**
* Controls both the scrim behind the notifications and in front of the notifications (when a
* security method gets shown).
*/
-public class ScrimController implements ViewTreeObserver.OnPreDrawListener {
+public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
+ HeadsUpManager.OnHeadsUpChangedListener {
public static final long ANIMATION_DURATION = 220;
private static final float SCRIM_BEHIND_ALPHA = 0.62f;
- private static final float SCRIM_BEHIND_ALPHA_KEYGUARD = 0.55f;
+ private static final float SCRIM_BEHIND_ALPHA_KEYGUARD = 0.45f;
private static final float SCRIM_BEHIND_ALPHA_UNLOCKING = 0.2f;
private static final float SCRIM_IN_FRONT_ALPHA = 0.75f;
private static final int TAG_KEY_ANIM = R.id.scrim;
+ private static final int TAG_HUN_START_ALPHA = R.id.hun_scrim_alpha_start;
+ private static final int TAG_HUN_END_ALPHA = R.id.hun_scrim_alpha_end;
private final ScrimView mScrimBehind;
private final ScrimView mScrimInFront;
private final UnlockMethodCache mUnlockMethodCache;
+ private final View mHeadsUpScrim;
private boolean mKeyguardShowing;
private float mFraction;
@@ -70,15 +79,23 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener {
private float mDozeBehindAlpha;
private float mCurrentInFrontAlpha;
private float mCurrentBehindAlpha;
-
- public ScrimController(ScrimView scrimBehind, ScrimView scrimInFront, boolean scrimSrcEnabled) {
+ private float mCurrentHeadsUpAlpha = 1;
+ private int mPinnedHeadsUpCount;
+ private float mTopHeadsUpDragAmount;
+ private View mDraggedHeadsUpView;
+ private boolean mForceHideScrims;
+
+ public ScrimController(ScrimView scrimBehind, ScrimView scrimInFront, View headsUpScrim,
+ boolean scrimSrcEnabled) {
mScrimBehind = scrimBehind;
mScrimInFront = scrimInFront;
+ mHeadsUpScrim = headsUpScrim;
final Context context = scrimBehind.getContext();
mUnlockMethodCache = UnlockMethodCache.getInstance(context);
mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
android.R.interpolator.linear_out_slow_in);
mScrimSrcEnabled = scrimSrcEnabled;
+ updateHeadsUpScrim(false);
}
public void setKeyguardShowing(boolean showing) {
@@ -99,6 +116,9 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener {
if (mFraction != fraction) {
mFraction = fraction;
scheduleUpdate();
+ if (mPinnedHeadsUpCount != 0) {
+ updateHeadsUpScrim(false);
+ }
}
}
@@ -117,6 +137,12 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener {
scheduleUpdate();
}
+ public void abortKeyguardFadingOut() {
+ if (mAnimateKeyguardFadingOut) {
+ endAnimateKeyguardFadingOut();
+ }
+ }
+
public void animateGoingToFullShade(long delay, long duration) {
mDurationOverride = duration;
mAnimationDelay = delay;
@@ -157,7 +183,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener {
}
private void updateScrims() {
- if (mAnimateKeyguardFadingOut) {
+ if (mAnimateKeyguardFadingOut || mForceHideScrims) {
setScrimInFrontColor(0f);
setScrimBehindColor(0f);
} else if (!mKeyguardShowing && !mBouncerShowing) {
@@ -217,7 +243,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener {
}
}
- private void setScrimColor(ScrimView scrim, float alpha) {
+ private void setScrimColor(View scrim, float alpha) {
Object runningAnim = scrim.getTag(TAG_KEY_ANIM);
if (runningAnim instanceof ValueAnimator) {
((ValueAnimator) runningAnim).cancel();
@@ -236,25 +262,34 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener {
}
private float getCurrentScrimAlpha(View scrim) {
- return scrim == mScrimBehind ? mCurrentBehindAlpha : mCurrentInFrontAlpha;
+ return scrim == mScrimBehind ? mCurrentBehindAlpha
+ : scrim == mScrimInFront ? mCurrentInFrontAlpha
+ : mCurrentHeadsUpAlpha;
}
private void setCurrentScrimAlpha(View scrim, float alpha) {
if (scrim == mScrimBehind) {
mCurrentBehindAlpha = alpha;
- } else {
+ } else if (scrim == mScrimInFront) {
mCurrentInFrontAlpha = alpha;
+ } else {
+ alpha = Math.max(0.0f, Math.min(1.0f, alpha));
+ mCurrentHeadsUpAlpha = alpha;
}
}
- private void updateScrimColor(ScrimView scrim) {
+ private void updateScrimColor(View scrim) {
float alpha1 = getCurrentScrimAlpha(scrim);
- float alpha2 = getDozeAlpha(scrim);
- float alpha = 1 - (1 - alpha1) * (1 - alpha2);
- scrim.setScrimColor(Color.argb((int) (alpha * 255), 0, 0, 0));
+ if (scrim instanceof ScrimView) {
+ float alpha2 = getDozeAlpha(scrim);
+ float alpha = 1 - (1 - alpha1) * (1 - alpha2);
+ ((ScrimView) scrim).setScrimColor(Color.argb((int) (alpha * 255), 0, 0, 0));
+ } else {
+ scrim.setAlpha(alpha1);
+ }
}
- private void startScrimAnimation(final ScrimView scrim, float target) {
+ private void startScrimAnimation(final View scrim, float target) {
float current = getCurrentScrimAlpha(scrim);
ValueAnimator anim = ValueAnimator.ofFloat(current, target);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@@ -292,17 +327,21 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener {
mScrimBehind.getViewTreeObserver().removeOnPreDrawListener(this);
mUpdatePending = false;
updateScrims();
- mAnimateKeyguardFadingOut = false;
mDurationOverride = -1;
mAnimationDelay = 0;
// Make sure that we always call the listener even if we didn't start an animation.
+ endAnimateKeyguardFadingOut();
+ mAnimationStarted = false;
+ return true;
+ }
+
+ private void endAnimateKeyguardFadingOut() {
+ mAnimateKeyguardFadingOut = false;
if (!mAnimationStarted && mOnAnimationFinished != null) {
mOnAnimationFinished.run();
mOnAnimationFinished = null;
}
- mAnimationStarted = false;
- return true;
}
public void setBackDropView(BackDropView backDropView) {
@@ -320,4 +359,102 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener {
boolean asSrc = mBackDropView.getVisibility() != View.VISIBLE && mScrimSrcEnabled;
mScrimBehind.setDrawAsSrc(asSrc);
}
+
+ @Override
+ public void onHeadsUpPinnedModeChanged(boolean inPinnedMode) {
+ }
+
+ @Override
+ public void onHeadsUpPinned(ExpandableNotificationRow headsUp) {
+ mPinnedHeadsUpCount++;
+ updateHeadsUpScrim(true);
+ }
+
+ @Override
+ public void onHeadsUpUnPinned(ExpandableNotificationRow headsUp) {
+ mPinnedHeadsUpCount--;
+ if (headsUp == mDraggedHeadsUpView) {
+ mDraggedHeadsUpView = null;
+ mTopHeadsUpDragAmount = 0.0f;
+ }
+ updateHeadsUpScrim(true);
+ }
+
+ @Override
+ public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) {
+ }
+
+ private void updateHeadsUpScrim(boolean animate) {
+ float alpha = calculateHeadsUpAlpha();
+ ValueAnimator previousAnimator = StackStateAnimator.getChildTag(mHeadsUpScrim,
+ TAG_KEY_ANIM);
+ float animEndValue = -1;
+ if (previousAnimator != null) {
+ if (animate || alpha == mCurrentHeadsUpAlpha) {
+ previousAnimator.cancel();
+ } else {
+ animEndValue = StackStateAnimator.getChildTag(mHeadsUpScrim, TAG_HUN_END_ALPHA);
+ }
+ }
+ if (alpha != mCurrentHeadsUpAlpha && alpha != animEndValue) {
+ if (animate) {
+ startScrimAnimation(mHeadsUpScrim, alpha);
+ mHeadsUpScrim.setTag(TAG_HUN_START_ALPHA, mCurrentHeadsUpAlpha);
+ mHeadsUpScrim.setTag(TAG_HUN_END_ALPHA, alpha);
+ } else {
+ if (previousAnimator != null) {
+ float previousStartValue = StackStateAnimator.getChildTag(mHeadsUpScrim,
+ TAG_HUN_START_ALPHA);
+ float previousEndValue = StackStateAnimator.getChildTag(mHeadsUpScrim,
+ TAG_HUN_END_ALPHA);
+ // we need to increase all animation keyframes of the previous animator by the
+ // relative change to the end value
+ PropertyValuesHolder[] values = previousAnimator.getValues();
+ float relativeDiff = alpha - previousEndValue;
+ float newStartValue = previousStartValue + relativeDiff;
+ values[0].setFloatValues(newStartValue, alpha);
+ mHeadsUpScrim.setTag(TAG_HUN_START_ALPHA, newStartValue);
+ mHeadsUpScrim.setTag(TAG_HUN_END_ALPHA, alpha);
+ previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
+ } else {
+ // update the alpha directly
+ setCurrentScrimAlpha(mHeadsUpScrim, alpha);
+ updateScrimColor(mHeadsUpScrim);
+ }
+ }
+ }
+ }
+
+ /**
+ * 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.
+ *
+ * @param draggedHeadsUpView the dragged view
+ * @param topHeadsUpDragAmount how far is it dragged
+ */
+ public void setTopHeadsUpDragAmount(View draggedHeadsUpView, float topHeadsUpDragAmount) {
+ mTopHeadsUpDragAmount = topHeadsUpDragAmount;
+ mDraggedHeadsUpView = draggedHeadsUpView;
+ updateHeadsUpScrim(false);
+ }
+
+ private float calculateHeadsUpAlpha() {
+ float alpha;
+ if (mPinnedHeadsUpCount >= 2) {
+ alpha = 1.0f;
+ } else if (mPinnedHeadsUpCount == 0) {
+ alpha = 0.0f;
+ } else {
+ alpha = 1.0f - mTopHeadsUpDragAmount;
+ }
+ float expandFactor = (1.0f - mFraction);
+ expandFactor = Math.max(expandFactor, 0.0f);
+ return alpha * expandFactor;
+ }
+
+ public void forceHideScrims(boolean hide) {
+ mForceHideScrims = hide;
+ mAnimateChange = false;
+ scheduleUpdate();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SecureCameraLaunchManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SecureCameraLaunchManager.java
index 4a43c47..45c8938 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SecureCameraLaunchManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SecureCameraLaunchManager.java
@@ -27,6 +27,7 @@ import android.provider.MediaStore;
import android.util.Log;
import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardUpdateMonitor;
import java.util.HashMap;
import java.util.List;
@@ -228,7 +229,7 @@ public class SecureCameraLaunchManager {
// Get the list of applications that can handle the intent.
final List<ResolveInfo> appList = packageManager.queryIntentActivitiesAsUser(
- intent, PackageManager.MATCH_DEFAULT_ONLY, mLockPatternUtils.getCurrentUser());
+ intent, PackageManager.MATCH_DEFAULT_ONLY, KeyguardUpdateMonitor.getCurrentUser());
if (appList.size() == 0) {
if (DEBUG) Log.d(TAG, "No targets found for secure camera intent");
return false;
@@ -237,7 +238,7 @@ public class SecureCameraLaunchManager {
// Get the application that the intent resolves to.
ResolveInfo resolved = packageManager.resolveActivityAsUser(intent,
PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_META_DATA,
- mLockPatternUtils.getCurrentUser());
+ KeyguardUpdateMonitor.getCurrentUser());
if (resolved == null || resolved.activityInfo == null) {
return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
index 181926c..a81f06e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
@@ -45,6 +45,7 @@ import com.android.systemui.R;
import com.android.systemui.qs.QSPanel;
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.NetworkControllerImpl.EmergencyListener;
import com.android.systemui.statusbar.policy.NextAlarmController;
import com.android.systemui.statusbar.policy.UserInfoController;
@@ -54,7 +55,8 @@ import java.text.NumberFormat;
* The view to manage the header area in the expanded status bar.
*/
public class StatusBarHeaderView extends RelativeLayout implements View.OnClickListener,
- BatteryController.BatteryStateChangeCallback, NextAlarmController.NextAlarmChangeCallback {
+ BatteryController.BatteryStateChangeCallback, NextAlarmController.NextAlarmChangeCallback,
+ EmergencyListener {
private boolean mExpanded;
private boolean mListening;
@@ -110,7 +112,6 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
private NextAlarmController mNextAlarmController;
private QSPanel mQSPanel;
-
private final Rect mClipBounds = new Rect();
private boolean mCaptureValues;
@@ -121,6 +122,7 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
private float mCurrentT;
private boolean mShowingDetail;
+ private boolean mDetailTransitioning;
public StatusBarHeaderView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -527,7 +529,8 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
return true;
}
- public void setShowEmergencyCallsOnly(boolean show) {
+ @Override
+ public void setEmergencyCallsOnly(boolean show) {
boolean changed = show != mShowEmergencyCallsOnly;
if (changed) {
mShowEmergencyCallsOnly = show;
@@ -623,7 +626,7 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
mSettingsButton.setTranslationX(values.settingsTranslation);
mSettingsButton.setRotation(values.settingsRotation);
applyAlpha(mEmergencyCallsOnly, values.emergencyCallsOnlyAlpha);
- if (!mShowingDetail) {
+ if (!mShowingDetail && !mDetailTransitioning) {
// Otherwise it needs to stay invisible
applyAlpha(mAlarmStatus, values.alarmStatusAlpha);
}
@@ -706,6 +709,7 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
@Override
public void onShowingDetail(final QSTile.DetailAdapter detail) {
+ mDetailTransitioning = true;
post(new Runnable() {
@Override
public void run() {
@@ -788,6 +792,7 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
if (!in) {
v.setVisibility(INVISIBLE);
}
+ mDetailTransitioning = false;
}
})
.start();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
new file mode 100644
index 0000000..067e50e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -0,0 +1,463 @@
+/*
+ * 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.statusbar.phone;
+
+import android.animation.ArgbEvaluator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.internal.statusbar.StatusBarIcon;
+import com.android.internal.util.NotificationColorUtil;
+import com.android.systemui.BatteryMeterView;
+import com.android.systemui.FontSizeUtils;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.NotificationData;
+import com.android.systemui.statusbar.SignalClusterView;
+import com.android.systemui.statusbar.StatusBarIconView;
+import com.android.systemui.tuner.TunerService;
+import com.android.systemui.tuner.TunerService.Tunable;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * Controls everything regarding the icons in the status bar and on Keyguard, including, but not
+ * limited to: notification icons, signal cluster, additional status icons, and clock in the status
+ * bar.
+ */
+public class StatusBarIconController implements Tunable {
+
+ public static final long DEFAULT_TINT_ANIMATION_DURATION = 120;
+
+ public static final String ICON_BLACKLIST = "icon_blacklist";
+
+ private Context mContext;
+ private PhoneStatusBar mPhoneStatusBar;
+ private Interpolator mLinearOutSlowIn;
+ private Interpolator mFastOutSlowIn;
+ private DemoStatusIcons mDemoStatusIcons;
+ private NotificationColorUtil mNotificationColorUtil;
+
+ private LinearLayout mSystemIconArea;
+ private LinearLayout mStatusIcons;
+ private SignalClusterView mSignalCluster;
+ private LinearLayout mStatusIconsKeyguard;
+ private IconMerger mNotificationIcons;
+ private View mNotificationIconArea;
+ private ImageView mMoreIcon;
+ private BatteryMeterView mBatteryMeterView;
+ private TextView mClock;
+
+ private int mIconSize;
+ private int mIconHPadding;
+
+ private int mIconTint = Color.WHITE;
+ private float mDarkIntensity;
+
+ private boolean mTransitionPending;
+ private boolean mTintChangePending;
+ private float mPendingDarkIntensity;
+ private ValueAnimator mTintAnimator;
+
+ private int mDarkModeIconColorSingleTone;
+ private int mLightModeIconColorSingleTone;
+
+ private final Handler mHandler;
+ private boolean mTransitionDeferring;
+ private long mTransitionDeferringStartTime;
+ private long mTransitionDeferringDuration;
+
+ private final ArraySet<String> mIconBlacklist = new ArraySet<>();
+
+ private final Runnable mTransitionDeferringDoneRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mTransitionDeferring = false;
+ }
+ };
+
+ public StatusBarIconController(Context context, View statusBar, View keyguardStatusBar,
+ PhoneStatusBar phoneStatusBar) {
+ mContext = context;
+ mPhoneStatusBar = phoneStatusBar;
+ mNotificationColorUtil = NotificationColorUtil.getInstance(context);
+ mSystemIconArea = (LinearLayout) statusBar.findViewById(R.id.system_icon_area);
+ mStatusIcons = (LinearLayout) statusBar.findViewById(R.id.statusIcons);
+ mSignalCluster = (SignalClusterView) statusBar.findViewById(R.id.signal_cluster);
+ mNotificationIconArea = statusBar.findViewById(R.id.notification_icon_area_inner);
+ mNotificationIcons = (IconMerger) statusBar.findViewById(R.id.notificationIcons);
+ mMoreIcon = (ImageView) statusBar.findViewById(R.id.moreIcon);
+ mNotificationIcons.setOverflowIndicator(mMoreIcon);
+ mStatusIconsKeyguard = (LinearLayout) keyguardStatusBar.findViewById(R.id.statusIcons);
+ mBatteryMeterView = (BatteryMeterView) statusBar.findViewById(R.id.battery);
+ mClock = (TextView) statusBar.findViewById(R.id.clock);
+ mLinearOutSlowIn = AnimationUtils.loadInterpolator(mContext,
+ android.R.interpolator.linear_out_slow_in);
+ mFastOutSlowIn = AnimationUtils.loadInterpolator(mContext,
+ android.R.interpolator.fast_out_slow_in);
+ mDarkModeIconColorSingleTone = context.getColor(R.color.dark_mode_icon_color_single_tone);
+ mLightModeIconColorSingleTone = context.getColor(R.color.light_mode_icon_color_single_tone);
+ mHandler = new Handler();
+ updateResources();
+
+ TunerService.get(mContext).addTunable(this, ICON_BLACKLIST);
+ }
+
+ @Override
+ public void onTuningChanged(String key, String newValue) {
+ if (!ICON_BLACKLIST.equals(key)) {
+ return;
+ }
+ mIconBlacklist.clear();
+ mIconBlacklist.addAll(getIconBlacklist(newValue));
+ ArrayList<StatusBarIconView> views = new ArrayList<StatusBarIconView>();
+ // Get all the current views.
+ for (int i = 0; i < mStatusIcons.getChildCount(); i++) {
+ views.add((StatusBarIconView) mStatusIcons.getChildAt(i));
+ }
+ // Remove all the icons.
+ for (int i = views.size() - 1; i >= 0; i--) {
+ removeSystemIcon(views.get(i).getSlot(), i, i);
+ }
+ // Add them all back
+ for (int i = 0; i < views.size(); i++) {
+ addSystemIcon(views.get(i).getSlot(), i, i, views.get(i).getStatusBarIcon());
+ }
+ };
+
+ public void updateResources() {
+ mIconSize = mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.status_bar_icon_size);
+ mIconHPadding = mContext.getResources().getDimensionPixelSize(
+ R.dimen.status_bar_icon_padding);
+ FontSizeUtils.updateFontSize(mClock, R.dimen.status_bar_clock_size);
+ }
+
+ public void addSystemIcon(String slot, int index, int viewIndex, StatusBarIcon icon) {
+ boolean blocked = mIconBlacklist.contains(slot);
+ StatusBarIconView view = new StatusBarIconView(mContext, slot, null, blocked);
+ view.set(icon);
+ mStatusIcons.addView(view, viewIndex, new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize));
+ view = new StatusBarIconView(mContext, slot, null, blocked);
+ view.set(icon);
+ mStatusIconsKeyguard.addView(view, viewIndex, new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize));
+ applyIconTint();
+ }
+
+ public void updateSystemIcon(String slot, int index, int viewIndex,
+ StatusBarIcon old, StatusBarIcon icon) {
+ StatusBarIconView view = (StatusBarIconView) mStatusIcons.getChildAt(viewIndex);
+ view.set(icon);
+ view = (StatusBarIconView) mStatusIconsKeyguard.getChildAt(viewIndex);
+ view.set(icon);
+ applyIconTint();
+ }
+
+ public void removeSystemIcon(String slot, int index, int viewIndex) {
+ mStatusIcons.removeViewAt(viewIndex);
+ mStatusIconsKeyguard.removeViewAt(viewIndex);
+ }
+
+ public void updateNotificationIcons(NotificationData notificationData) {
+ final LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
+ mIconSize + 2*mIconHPadding, mPhoneStatusBar.getStatusBarHeight());
+
+ ArrayList<NotificationData.Entry> activeNotifications =
+ notificationData.getActiveNotifications();
+ final int N = activeNotifications.size();
+ ArrayList<StatusBarIconView> toShow = new ArrayList<>(N);
+
+ // Filter out ambient notifications and notification children.
+ for (int i = 0; i < N; i++) {
+ NotificationData.Entry ent = activeNotifications.get(i);
+ if (notificationData.isAmbient(ent.key)
+ && !NotificationData.showNotificationEvenIfUnprovisioned(ent.notification)) {
+ continue;
+ }
+ if (!PhoneStatusBar.isTopLevelChild(ent)) {
+ continue;
+ }
+ toShow.add(ent.icon);
+ }
+
+ ArrayList<View> toRemove = new ArrayList<>();
+ for (int i=0; i<mNotificationIcons.getChildCount(); i++) {
+ View child = mNotificationIcons.getChildAt(i);
+ if (!toShow.contains(child)) {
+ toRemove.add(child);
+ }
+ }
+
+ final int toRemoveCount = toRemove.size();
+ for (int i = 0; i < toRemoveCount; i++) {
+ mNotificationIcons.removeView(toRemove.get(i));
+ }
+
+ for (int i=0; i<toShow.size(); i++) {
+ View v = toShow.get(i);
+ if (v.getParent() == null) {
+ mNotificationIcons.addView(v, i, params);
+ }
+ }
+
+ // Resort notification icons
+ final int childCount = mNotificationIcons.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View actual = mNotificationIcons.getChildAt(i);
+ StatusBarIconView expected = toShow.get(i);
+ if (actual == expected) {
+ continue;
+ }
+ mNotificationIcons.removeView(expected);
+ mNotificationIcons.addView(expected, i);
+ }
+
+ applyNotificationIconsTint();
+ }
+
+ public void hideSystemIconArea(boolean animate) {
+ animateHide(mSystemIconArea, animate);
+ }
+
+ public void showSystemIconArea(boolean animate) {
+ animateShow(mSystemIconArea, animate);
+ }
+
+ public void hideNotificationIconArea(boolean animate) {
+ animateHide(mNotificationIconArea, animate);
+ }
+
+ public void showNotificationIconArea(boolean animate) {
+ animateShow(mNotificationIconArea, animate);
+ }
+
+ public void setClockVisibility(boolean visible) {
+ mClock.setVisibility(visible ? View.VISIBLE : View.GONE);
+ }
+
+ public void dump(PrintWriter pw) {
+ int N = mStatusIcons.getChildCount();
+ pw.println(" system icons: " + N);
+ for (int i=0; i<N; i++) {
+ StatusBarIconView ic = (StatusBarIconView) mStatusIcons.getChildAt(i);
+ pw.println(" [" + i + "] icon=" + ic);
+ }
+ }
+
+ public void dispatchDemoCommand(String command, Bundle args) {
+ if (mDemoStatusIcons == null) {
+ mDemoStatusIcons = new DemoStatusIcons(mStatusIcons, mIconSize);
+ }
+ mDemoStatusIcons.dispatchDemoCommand(command, args);
+ }
+
+ /**
+ * Hides a view.
+ */
+ private void animateHide(final View v, boolean animate) {
+ v.animate().cancel();
+ if (!animate) {
+ v.setAlpha(0f);
+ v.setVisibility(View.INVISIBLE);
+ return;
+ }
+ v.animate()
+ .alpha(0f)
+ .setDuration(160)
+ .setStartDelay(0)
+ .setInterpolator(PhoneStatusBar.ALPHA_OUT)
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ v.setVisibility(View.INVISIBLE);
+ }
+ });
+ }
+
+ /**
+ * Shows a view, and synchronizes the animation with Keyguard exit animations, if applicable.
+ */
+ private void animateShow(View v, boolean animate) {
+ v.animate().cancel();
+ v.setVisibility(View.VISIBLE);
+ if (!animate) {
+ v.setAlpha(1f);
+ return;
+ }
+ v.animate()
+ .alpha(1f)
+ .setDuration(320)
+ .setInterpolator(PhoneStatusBar.ALPHA_IN)
+ .setStartDelay(50)
+
+ // 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.
+ // cancel() doesn't really remove the end action.
+ .withEndAction(null);
+
+ // Synchronize the motion with the Keyguard fading if necessary.
+ if (mPhoneStatusBar.isKeyguardFadingAway()) {
+ v.animate()
+ .setDuration(mPhoneStatusBar.getKeyguardFadingAwayDuration())
+ .setInterpolator(mLinearOutSlowIn)
+ .setStartDelay(mPhoneStatusBar.getKeyguardFadingAwayDelay())
+ .start();
+ }
+ }
+
+ public void setIconsDark(boolean dark) {
+ if (mTransitionPending) {
+ deferIconTintChange(dark ? 1.0f : 0.0f);
+ } else if (mTransitionDeferring) {
+ animateIconTint(dark ? 1.0f : 0.0f,
+ Math.max(0, mTransitionDeferringStartTime - SystemClock.uptimeMillis()),
+ mTransitionDeferringDuration);
+ } else {
+ animateIconTint(dark ? 1.0f : 0.0f, 0 /* delay */, DEFAULT_TINT_ANIMATION_DURATION);
+ }
+ }
+
+ private void animateIconTint(float targetDarkIntensity, long delay,
+ long duration) {
+ if (mTintAnimator != null) {
+ mTintAnimator.cancel();
+ }
+ if (mDarkIntensity == targetDarkIntensity) {
+ return;
+ }
+ mTintAnimator = ValueAnimator.ofFloat(mDarkIntensity, targetDarkIntensity);
+ mTintAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ setIconTintInternal((Float) animation.getAnimatedValue());
+ }
+ });
+ mTintAnimator.setDuration(duration);
+ mTintAnimator.setStartDelay(delay);
+ mTintAnimator.setInterpolator(mFastOutSlowIn);
+ mTintAnimator.start();
+ }
+
+ private void setIconTintInternal(float darkIntensity) {
+ mDarkIntensity = darkIntensity;
+ mIconTint = (int) ArgbEvaluator.getInstance().evaluate(darkIntensity,
+ mLightModeIconColorSingleTone, mDarkModeIconColorSingleTone);
+ applyIconTint();
+ }
+
+ private void deferIconTintChange(float darkIntensity) {
+ if (mTintChangePending && darkIntensity == mPendingDarkIntensity) {
+ return;
+ }
+ mTintChangePending = true;
+ mPendingDarkIntensity = darkIntensity;
+ }
+
+ private void applyIconTint() {
+ for (int i = 0; i < mStatusIcons.getChildCount(); i++) {
+ StatusBarIconView v = (StatusBarIconView) mStatusIcons.getChildAt(i);
+ v.setImageTintList(ColorStateList.valueOf(mIconTint));
+ }
+ mSignalCluster.setIconTint(mIconTint, mDarkIntensity);
+ mMoreIcon.setImageTintList(ColorStateList.valueOf(mIconTint));
+ mBatteryMeterView.setDarkIntensity(mDarkIntensity);
+ mClock.setTextColor(mIconTint);
+ applyNotificationIconsTint();
+ }
+
+ private void applyNotificationIconsTint() {
+ for (int i = 0; i < mNotificationIcons.getChildCount(); i++) {
+ StatusBarIconView v = (StatusBarIconView) mNotificationIcons.getChildAt(i);
+ boolean isPreL = Boolean.TRUE.equals(v.getTag(R.id.icon_is_pre_L));
+ boolean colorize = !isPreL || isGrayscale(v);
+ if (colorize) {
+ v.setImageTintList(ColorStateList.valueOf(mIconTint));
+ }
+ }
+ }
+
+ private boolean isGrayscale(StatusBarIconView v) {
+ Object isGrayscale = v.getTag(R.id.icon_is_grayscale);
+ if (isGrayscale != null) {
+ return Boolean.TRUE.equals(isGrayscale);
+ }
+ boolean grayscale = mNotificationColorUtil.isGrayscaleIcon(v.getDrawable());
+ v.setTag(R.id.icon_is_grayscale, grayscale);
+ return grayscale;
+ }
+
+ public void appTransitionPending() {
+ mTransitionPending = true;
+ }
+
+ public void appTransitionCancelled() {
+ if (mTransitionPending && mTintChangePending) {
+ mTintChangePending = false;
+ animateIconTint(mPendingDarkIntensity, 0 /* delay */, DEFAULT_TINT_ANIMATION_DURATION);
+ }
+ mTransitionPending = false;
+ }
+
+ public void appTransitionStarting(long startTime, long duration) {
+ if (mTransitionPending && mTintChangePending) {
+ mTintChangePending = false;
+ animateIconTint(mPendingDarkIntensity,
+ Math.max(0, startTime - SystemClock.uptimeMillis()),
+ duration);
+
+ } else if (mTransitionPending) {
+
+ // If we don't have a pending tint change yet, the change might come in the future until
+ // startTime is reached.
+ mTransitionDeferring = true;
+ mTransitionDeferringStartTime = startTime;
+ mTransitionDeferringDuration = duration;
+ mHandler.removeCallbacks(mTransitionDeferringDoneRunnable);
+ mHandler.postAtTime(mTransitionDeferringDoneRunnable, startTime);
+ }
+ mTransitionPending = false;
+ }
+
+ public static ArraySet<String> getIconBlacklist(String blackListStr) {
+ ArraySet<String> ret = new ArraySet<String>();
+ if (blackListStr != null) {
+ String[] blacklist = blackListStr.split(",");
+ for (String slot : blacklist) {
+ if (!TextUtils.isEmpty(slot)) {
+ ret.add(slot);
+ }
+ }
+ }
+ return ret;
+ }
+}
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 1724e70..a69416a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -31,6 +31,7 @@ import com.android.internal.policy.IKeyguardShowCallback;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.ViewMediatorCallback;
+import com.android.systemui.statusbar.CommandQueue;
import static com.android.keyguard.KeyguardHostView.OnDismissAction;
@@ -72,6 +73,7 @@ public class StatusBarKeyguardViewManager {
private boolean mLastBouncerShowing;
private boolean mLastBouncerDismissible;
private OnDismissAction mAfterKeyguardGoneAction;
+ private boolean mScreenWillWakeUp;
public StatusBarKeyguardViewManager(Context context, ViewMediatorCallback callback,
LockPatternUtils lockPatternUtils) {
@@ -98,6 +100,7 @@ public class StatusBarKeyguardViewManager {
public void show(Bundle options) {
mShowing = true;
mStatusBarWindowManager.setKeyguardShowing(true);
+ mScrimController.abortKeyguardFadingOut();
reset();
}
@@ -125,10 +128,11 @@ public class StatusBarKeyguardViewManager {
updateStates();
}
- public void dismissWithAction(OnDismissAction r, boolean afterKeyguardGone) {
+ public void dismissWithAction(OnDismissAction r, Runnable cancelAction,
+ boolean afterKeyguardGone) {
if (mShowing) {
if (!afterKeyguardGone) {
- mBouncer.showWithDismissAction(r);
+ mBouncer.showWithDismissAction(r, cancelAction);
} else {
mBouncer.show(false /* resetSecuritySelection */);
mAfterKeyguardGoneAction = r;
@@ -144,6 +148,7 @@ public class StatusBarKeyguardViewManager {
if (mShowing) {
if (mOccluded) {
mPhoneStatusBar.hideKeyguard();
+ mPhoneStatusBar.stopWaitingForKeyguardExit();
mBouncer.hide(false /* destroyView */);
} else {
showBouncerOrKeyguard();
@@ -160,6 +165,7 @@ public class StatusBarKeyguardViewManager {
public void onScreenTurnedOn(final IKeyguardShowCallback callback) {
mScreenOn = true;
+ mScreenWillWakeUp = false;
mPhoneStatusBar.onScreenTurnedOn();
if (callback != null) {
callbackAfterDraw(callback);
@@ -179,6 +185,10 @@ public class StatusBarKeyguardViewManager {
});
}
+ public void notifyScreenWakeUpRequested() {
+ mScreenWillWakeUp = !mScreenOn;
+ }
+
public void verifyUnlock() {
dismiss();
}
@@ -187,10 +197,6 @@ public class StatusBarKeyguardViewManager {
mStatusBarWindowManager.setKeyguardNeedsInput(needsInput);
}
- public void updateUserActivityTimeout() {
- mStatusBarWindowManager.setKeyguardUserActivityTimeout(mBouncer.getUserActivityTimeout());
- }
-
public void setOccluded(boolean occluded) {
if (occluded && !mOccluded && mShowing) {
if (mPhoneStatusBar.isInLaunchTransition()) {
@@ -261,7 +267,7 @@ public class StatusBarKeyguardViewManager {
}
});
} else {
- mPhoneStatusBar.setKeyguardFadingAway(delay, fadeoutDuration);
+ mPhoneStatusBar.setKeyguardFadingAway(startTime, delay, fadeoutDuration);
boolean staying = mPhoneStatusBar.hideKeyguard();
if (!staying) {
mStatusBarWindowManager.setKeyguardFadingAway(true);
@@ -298,7 +304,7 @@ public class StatusBarKeyguardViewManager {
* Dismisses the keyguard by going to the next screen or making it gone.
*/
public void dismiss() {
- if (mScreenOn) {
+ if (mScreenOn || mScreenWillWakeUp) {
showBouncer();
}
}
@@ -439,4 +445,21 @@ public class StatusBarKeyguardViewManager {
public boolean isInputRestricted() {
return mViewMediatorCallback.isInputRestricted();
}
+
+ public void keyguardGoingAway() {
+ mPhoneStatusBar.keyguardGoingAway();
+ }
+
+ public void animateCollapsePanels(float speedUpFactor) {
+ mPhoneStatusBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */,
+ false /* delayed */, speedUpFactor);
+ }
+
+ /**
+ * Notifies that the user has authenticated by other means than using the bouncer, for example,
+ * fingerprint.
+ */
+ public void notifyKeyguardAuthenticated() {
+ mBouncer.notifyKeyguardAuthenticated();
+ }
}
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 0dbdca1..0d816dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
@@ -21,15 +21,19 @@ import android.content.pm.ActivityInfo;
import android.content.res.Resources;
import android.graphics.PixelFormat;
import android.os.SystemProperties;
+import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import com.android.keyguard.R;
+import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.statusbar.BaseStatusBar;
import com.android.systemui.statusbar.StatusBarState;
+import java.lang.reflect.Field;
+
/**
* Encapsulates all logic for the status bar window state management.
*/
@@ -44,6 +48,7 @@ public class StatusBarWindowManager {
private final boolean mKeyguardScreenRotation;
private final State mCurrentState = new State();
+ private boolean mLogState;
public StatusBarWindowManager(Context context) {
mContext = context;
@@ -113,11 +118,11 @@ public class StatusBarWindowManager {
}
private void applyFocusableFlag(State state) {
- if (state.isKeyguardShowingAndNotOccluded() && state.keyguardNeedsInput
- && state.bouncerShowing) {
+ boolean panelFocusable = state.statusBarFocusable && state.panelExpanded;
+ if (state.keyguardShowing && state.keyguardNeedsInput && state.bouncerShowing) {
mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
- } else if (state.isKeyguardShowingAndNotOccluded() || state.statusBarFocusable) {
+ } else if (state.isKeyguardShowingAndNotOccluded() || panelFocusable) {
mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
mLpChanged.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
} else {
@@ -127,8 +132,7 @@ public class StatusBarWindowManager {
}
private void applyHeight(State state) {
- boolean expanded = state.isKeyguardShowingAndNotOccluded() || state.statusBarExpanded
- || state.keyguardFadingAway || state.bouncerShowing;
+ boolean expanded = isExpanded(state);
if (expanded) {
mLpChanged.height = ViewGroup.LayoutParams.MATCH_PARENT;
} else {
@@ -136,6 +140,12 @@ public class StatusBarWindowManager {
}
}
+ private boolean isExpanded(State state) {
+ return !state.forceCollapsed && (state.isKeyguardShowingAndNotOccluded()
+ || state.panelVisible || state.keyguardFadingAway || state.bouncerShowing
+ || state.headsUpShowing);
+ }
+
private void applyFitsSystemWindows(State state) {
mStatusBarView.setFitsSystemWindows(!state.isKeyguardShowingAndNotOccluded());
}
@@ -144,7 +154,7 @@ public class StatusBarWindowManager {
if (state.isKeyguardShowingAndNotOccluded()
&& state.statusBarState == StatusBarState.KEYGUARD
&& !state.qsExpanded) {
- mLpChanged.userActivityTimeout = state.keyguardUserActivityTimeout;
+ mLpChanged.userActivityTimeout = KeyguardViewMediator.AWAKE_INTERVAL_DEFAULT_MS;
} else {
mLpChanged.userActivityTimeout = -1;
}
@@ -164,17 +174,40 @@ public class StatusBarWindowManager {
private void apply(State state) {
applyKeyguardFlags(state);
+ applyForceStatusBarVisibleFlag(state);
applyFocusableFlag(state);
adjustScreenOrientation(state);
applyHeight(state);
applyUserActivityTimeout(state);
applyInputFeatures(state);
applyFitsSystemWindows(state);
+ applyModalFlag(state);
if (mLp.copyFrom(mLpChanged) != 0) {
+ if (PhoneStatusBar.DEBUG_EMPTY_KEYGUARD && mLogState) {
+ logCurrentState();
+ }
mWindowManager.updateViewLayout(mStatusBarView, mLp);
}
}
+ private void applyForceStatusBarVisibleFlag(State state) {
+ if (state.forceStatusBarVisible) {
+ mLpChanged.privateFlags |= WindowManager
+ .LayoutParams.PRIVATE_FLAG_FORCE_STATUS_BAR_VISIBLE_TRANSPARENT;
+ } else {
+ mLpChanged.privateFlags &= ~WindowManager
+ .LayoutParams.PRIVATE_FLAG_FORCE_STATUS_BAR_VISIBLE_TRANSPARENT;
+ }
+ }
+
+ private void applyModalFlag(State state) {
+ if (state.headsUpShowing) {
+ mLpChanged.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+ } else {
+ mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+ }
+ }
+
public void setKeyguardShowing(boolean showing) {
mCurrentState.keyguardShowing = showing;
apply(mCurrentState);
@@ -190,9 +223,9 @@ public class StatusBarWindowManager {
apply(mCurrentState);
}
- public void setStatusBarExpanded(boolean expanded) {
- mCurrentState.statusBarExpanded = expanded;
- mCurrentState.statusBarFocusable = expanded;
+ public void setPanelVisible(boolean visible) {
+ mCurrentState.panelVisible = visible;
+ mCurrentState.statusBarFocusable = visible;
apply(mCurrentState);
}
@@ -201,11 +234,6 @@ public class StatusBarWindowManager {
apply(mCurrentState);
}
- public void setKeyguardUserActivityTimeout(long timeout) {
- mCurrentState.keyguardUserActivityTimeout = timeout;
- apply(mCurrentState);
- }
-
public void setBouncerShowing(boolean showing) {
mCurrentState.bouncerShowing = showing;
apply(mCurrentState);
@@ -221,6 +249,11 @@ public class StatusBarWindowManager {
apply(mCurrentState);
}
+ public void setHeadsUpShowing(boolean showing) {
+ mCurrentState.headsUpShowing = showing;
+ apply(mCurrentState);
+ }
+
/**
* @param state The {@link StatusBarState} of the status bar.
*/
@@ -229,16 +262,54 @@ public class StatusBarWindowManager {
apply(mCurrentState);
}
+ public void setForceStatusBarVisible(boolean forceStatusBarVisible) {
+ mCurrentState.forceStatusBarVisible = forceStatusBarVisible;
+ apply(mCurrentState);
+ }
+
+ /**
+ * Force the window to be collapsed, even if it should theoretically be expanded.
+ * Used for when a heads-up comes in but we still need to wait for the touchable regions to
+ * be computed.
+ */
+ public void setForceWindowCollapsed(boolean force) {
+ mCurrentState.forceCollapsed = force;
+ apply(mCurrentState);
+ }
+
+ public void setPanelExpanded(boolean isExpanded) {
+ mCurrentState.panelExpanded = isExpanded;
+ apply(mCurrentState);
+ }
+
+ public void setLogState(boolean logState) {
+ mLogState = logState;
+ if (logState) {
+ Log.w(PhoneStatusBar.TAG, "===== Started logging WM state changes =====");
+ logCurrentState();
+ } else {
+ Log.w(PhoneStatusBar.TAG, "===== Finished logging WM state changes =====");
+ }
+ }
+
+ private void logCurrentState() {
+ Log.i(PhoneStatusBar.TAG, mCurrentState.toString()
+ + "\n Expanded: " + isExpanded(mCurrentState));
+ }
+
private static class State {
boolean keyguardShowing;
boolean keyguardOccluded;
boolean keyguardNeedsInput;
- boolean statusBarExpanded;
+ boolean panelVisible;
+ boolean panelExpanded;
boolean statusBarFocusable;
- long keyguardUserActivityTimeout;
boolean bouncerShowing;
boolean keyguardFadingAway;
boolean qsExpanded;
+ boolean headsUpShowing;
+ boolean forceStatusBarVisible;
+ boolean forceCollapsed;
/**
* The {@link BaseStatusBar} state from the status bar.
@@ -248,5 +319,31 @@ public class StatusBarWindowManager {
private boolean isKeyguardShowingAndNotOccluded() {
return keyguardShowing && !keyguardOccluded;
}
+
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder();
+ String newLine = "\n";
+ result.append("Window State {");
+ result.append(newLine);
+
+ Field[] fields = this.getClass().getDeclaredFields();
+
+ // Print field names paired with their values
+ for (Field field : fields) {
+ result.append(" ");
+ try {
+ result.append(field.getName());
+ result.append(": ");
+ //requires access to private field:
+ result.append(field.get(this));
+ } catch (IllegalAccessException ex) {
+ }
+ result.append(newLine);
+ }
+ result.append("}");
+
+ return result.toString();
+ }
}
}
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 a96f4e9..0e22aa8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.phone;
import android.app.StatusBarManager;
import android.content.Context;
+import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
@@ -50,7 +51,9 @@ public class StatusBarWindowView extends FrameLayout {
private NotificationPanelView mNotificationPanel;
private View mBrightnessMirror;
- PhoneStatusBar mService;
+ private int mRightInset = 0;
+
+ private PhoneStatusBar mService;
private final Paint mTransparentSrcPaint = new Paint();
public StatusBarWindowView(Context context, AttributeSet attrs) {
@@ -63,17 +66,27 @@ public class StatusBarWindowView extends FrameLayout {
@Override
protected boolean fitSystemWindows(Rect insets) {
if (getFitsSystemWindows()) {
- boolean changed = insets.left != getPaddingLeft()
+ boolean paddingChanged = insets.left != getPaddingLeft()
|| insets.top != getPaddingTop()
- || insets.right != getPaddingRight()
|| insets.bottom != getPaddingBottom();
- if (changed) {
- setPadding(insets.left, insets.top, insets.right, 0);
+
+ // Super-special right inset handling, because scrims and backdrop need to ignore it.
+ if (insets.right != mRightInset) {
+ mRightInset = insets.right;
+ applyMargins();
+ }
+ // Drop top inset, apply left inset and pass through bottom inset.
+ if (paddingChanged) {
+ setPadding(insets.left, 0, 0, 0);
}
insets.left = 0;
insets.top = 0;
insets.right = 0;
} else {
+ if (mRightInset != 0) {
+ mRightInset = 0;
+ applyMargins();
+ }
boolean changed = getPaddingLeft() != 0
|| getPaddingRight() != 0
|| getPaddingTop() != 0
@@ -81,19 +94,52 @@ public class StatusBarWindowView extends FrameLayout {
if (changed) {
setPadding(0, 0, 0, 0);
}
+ insets.top = 0;
}
return false;
}
+ private void applyMargins() {
+ final int N = getChildCount();
+ for (int i = 0; i < N; i++) {
+ View child = getChildAt(i);
+ if (child.getLayoutParams() instanceof LayoutParams) {
+ LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if (!lp.ignoreRightInset && lp.rightMargin != mRightInset) {
+ lp.rightMargin = mRightInset;
+ child.requestLayout();
+ }
+ }
+ }
+ }
+
@Override
- protected void onAttachedToWindow () {
- super.onAttachedToWindow();
+ public FrameLayout.LayoutParams generateLayoutParams(AttributeSet attrs) {
+ return new LayoutParams(getContext(), attrs);
+ }
+ @Override
+ protected FrameLayout.LayoutParams generateDefaultLayoutParams() {
+ return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
mStackScrollLayout = (NotificationStackScrollLayout) findViewById(
R.id.notification_stack_scroller);
mNotificationPanel = (NotificationPanelView) findViewById(R.id.notification_panel);
- mDragDownHelper = new DragDownHelper(getContext(), this, mStackScrollLayout, mService);
mBrightnessMirror = findViewById(R.id.brightness_mirror);
+ }
+
+ public void setService(PhoneStatusBar service) {
+ mService = service;
+ mDragDownHelper = new DragDownHelper(getContext(), this, mStackScrollLayout, mService);
+ }
+
+ @Override
+ protected void onAttachedToWindow () {
+ super.onAttachedToWindow();
// We really need to be able to animate while window animations are going on
// so that activities may be started asynchronously from panel animations
@@ -168,7 +214,6 @@ public class StatusBarWindowView extends FrameLayout {
if (mNotificationPanel.isFullyExpanded()
&& mStackScrollLayout.getVisibility() == View.VISIBLE
&& mService.getBarState() == StatusBarState.KEYGUARD
- && !mService.isQsExpanded()
&& !mService.isBouncerShowing()) {
intercept = mDragDownHelper.onInterceptTouchEvent(ev);
// wake up on a touch down event, if dozing
@@ -192,7 +237,7 @@ public class StatusBarWindowView extends FrameLayout {
@Override
public boolean onTouchEvent(MotionEvent ev) {
boolean handled = false;
- if (mService.getBarState() == StatusBarState.KEYGUARD && !mService.isQsExpanded()) {
+ if (mService.getBarState() == StatusBarState.KEYGUARD) {
handled = mDragDownHelper.onTouchEvent(ev);
}
if (!handled) {
@@ -242,5 +287,23 @@ public class StatusBarWindowView extends FrameLayout {
mStackScrollLayout.cancelExpandHelper();
}
}
+
+ public class LayoutParams extends FrameLayout.LayoutParams {
+
+ public boolean ignoreRightInset;
+
+ public LayoutParams(int width, int height) {
+ super(width, height);
+ }
+
+ public LayoutParams(Context c, AttributeSet attrs) {
+ super(c, attrs);
+
+ TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.StatusBarWindowView_Layout);
+ ignoreRightInset = a.getBoolean(
+ R.styleable.StatusBarWindowView_Layout_ignoreRightInset, false);
+ a.recycle();
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/Ticker.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/Ticker.java
deleted file mode 100644
index a6ce288..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/Ticker.java
+++ /dev/null
@@ -1,305 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.content.res.Resources;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.service.notification.StatusBarNotification;
-import android.text.Layout.Alignment;
-import android.text.StaticLayout;
-import android.text.TextPaint;
-import android.view.View;
-import android.view.animation.AnimationUtils;
-import android.widget.ImageSwitcher;
-import android.widget.TextSwitcher;
-import android.widget.TextView;
-
-import com.android.internal.statusbar.StatusBarIcon;
-import com.android.systemui.R;
-import com.android.systemui.statusbar.StatusBarIconView;
-
-import java.util.ArrayList;
-
-public abstract class Ticker {
- private static final int TICKER_SEGMENT_DELAY = 3000;
-
- private Context mContext;
- private Handler mHandler = new Handler();
- private ArrayList<Segment> mSegments = new ArrayList();
- private TextPaint mPaint;
- private View mTickerView;
- private ImageSwitcher mIconSwitcher;
- private TextSwitcher mTextSwitcher;
- private float mIconScale;
-
- public static boolean isGraphicOrEmoji(char c) {
- int gc = Character.getType(c);
- return gc != Character.CONTROL
- && gc != Character.FORMAT
- && gc != Character.UNASSIGNED
- && gc != Character.LINE_SEPARATOR
- && gc != Character.PARAGRAPH_SEPARATOR
- && gc != Character.SPACE_SEPARATOR;
- }
-
- private final class Segment {
- StatusBarNotification notification;
- Drawable icon;
- CharSequence text;
- int current;
- int next;
- boolean first;
-
- StaticLayout getLayout(CharSequence substr) {
- int w = mTextSwitcher.getWidth() - mTextSwitcher.getPaddingLeft()
- - mTextSwitcher.getPaddingRight();
- return new StaticLayout(substr, mPaint, w, Alignment.ALIGN_NORMAL, 1, 0, true);
- }
-
- CharSequence rtrim(CharSequence substr, int start, int end) {
- while (end > start && !isGraphicOrEmoji(substr.charAt(end-1))) {
- end--;
- }
- if (end > start) {
- return substr.subSequence(start, end);
- }
- return null;
- }
-
- /** returns null if there is no more text */
- CharSequence getText() {
- if (this.current > this.text.length()) {
- return null;
- }
- CharSequence substr = this.text.subSequence(this.current, this.text.length());
- StaticLayout l = getLayout(substr);
- int lineCount = l.getLineCount();
- if (lineCount > 0) {
- int start = l.getLineStart(0);
- int end = l.getLineEnd(0);
- this.next = this.current + end;
- return rtrim(substr, start, end);
- } else {
- throw new RuntimeException("lineCount=" + lineCount + " current=" + current +
- " text=" + text);
- }
- }
-
- /** returns null if there is no more text */
- CharSequence advance() {
- this.first = false;
- int index = this.next;
- final int len = this.text.length();
- while (index < len && !isGraphicOrEmoji(this.text.charAt(index))) {
- index++;
- }
- if (index >= len) {
- return null;
- }
-
- CharSequence substr = this.text.subSequence(index, this.text.length());
- StaticLayout l = getLayout(substr);
- final int lineCount = l.getLineCount();
- int i;
- for (i=0; i<lineCount; i++) {
- int start = l.getLineStart(i);
- int end = l.getLineEnd(i);
- if (i == lineCount-1) {
- this.next = len;
- } else {
- this.next = index + l.getLineStart(i+1);
- }
- CharSequence result = rtrim(substr, start, end);
- if (result != null) {
- this.current = index + start;
- return result;
- }
- }
- this.current = len;
- return null;
- }
-
- Segment(StatusBarNotification n, Drawable icon, CharSequence text) {
- this.notification = n;
- this.icon = icon;
- this.text = text;
- int index = 0;
- final int len = text.length();
- while (index < len && !isGraphicOrEmoji(text.charAt(index))) {
- index++;
- }
- this.current = index;
- this.next = index;
- this.first = true;
- }
- };
-
- public Ticker(Context context, View sb) {
- mContext = context;
- final Resources res = context.getResources();
- final int outerBounds = res.getDimensionPixelSize(R.dimen.status_bar_icon_size);
- final int imageBounds = res.getDimensionPixelSize(R.dimen.status_bar_icon_drawing_size);
- mIconScale = (float)imageBounds / (float)outerBounds;
-
- mTickerView = sb.findViewById(R.id.ticker);
-
- mIconSwitcher = (ImageSwitcher)sb.findViewById(R.id.tickerIcon);
- mIconSwitcher.setInAnimation(
- AnimationUtils.loadAnimation(context, com.android.internal.R.anim.push_up_in));
- mIconSwitcher.setOutAnimation(
- AnimationUtils.loadAnimation(context, com.android.internal.R.anim.push_up_out));
- mIconSwitcher.setScaleX(mIconScale);
- mIconSwitcher.setScaleY(mIconScale);
-
- mTextSwitcher = (TextSwitcher)sb.findViewById(R.id.tickerText);
- mTextSwitcher.setInAnimation(
- AnimationUtils.loadAnimation(context, com.android.internal.R.anim.push_up_in));
- mTextSwitcher.setOutAnimation(
- AnimationUtils.loadAnimation(context, com.android.internal.R.anim.push_up_out));
-
- // Copy the paint style of one of the TextSwitchers children to use later for measuring
- TextView text = (TextView)mTextSwitcher.getChildAt(0);
- mPaint = text.getPaint();
- }
-
-
- public void addEntry(StatusBarNotification n) {
- int initialCount = mSegments.size();
-
- // If what's being displayed has the same text and icon, just drop it
- // (which will let the current one finish, this happens when apps do
- // a notification storm).
- if (initialCount > 0) {
- final Segment seg = mSegments.get(0);
- if (n.getPackageName().equals(seg.notification.getPackageName())
- && n.getNotification().icon == seg.notification.getNotification().icon
- && n.getNotification().iconLevel == seg.notification.getNotification().iconLevel
- && charSequencesEqual(seg.notification.getNotification().tickerText,
- n.getNotification().tickerText)) {
- return;
- }
- }
-
- final Drawable icon = StatusBarIconView.getIcon(mContext,
- new StatusBarIcon(n.getPackageName(), n.getUser(), n.getNotification().icon, n.getNotification().iconLevel, 0,
- n.getNotification().tickerText));
- final CharSequence text = n.getNotification().tickerText;
- final Segment newSegment = new Segment(n, icon, text);
-
- // If there's already a notification schedule for this package and id, remove it.
- for (int i=0; i<mSegments.size(); i++) {
- Segment seg = mSegments.get(i);
- if (n.getId() == seg.notification.getId() && n.getPackageName().equals(seg.notification.getPackageName())) {
- // just update that one to use this new data instead
- mSegments.remove(i--); // restart iteration here
- }
- }
-
- mSegments.add(newSegment);
-
- if (initialCount == 0 && mSegments.size() > 0) {
- Segment seg = mSegments.get(0);
- seg.first = false;
-
- mIconSwitcher.setAnimateFirstView(false);
- mIconSwitcher.reset();
- mIconSwitcher.setImageDrawable(seg.icon);
-
- mTextSwitcher.setAnimateFirstView(false);
- mTextSwitcher.reset();
- mTextSwitcher.setText(seg.getText());
-
- tickerStarting();
- scheduleAdvance();
- }
- }
-
- private static boolean charSequencesEqual(CharSequence a, CharSequence b) {
- if (a.length() != b.length()) {
- return false;
- }
-
- int length = a.length();
- for (int i = 0; i < length; i++) {
- if (a.charAt(i) != b.charAt(i)) {
- return false;
- }
- }
- return true;
- }
-
- public void removeEntry(StatusBarNotification n) {
- for (int i=mSegments.size()-1; i>=0; i--) {
- Segment seg = mSegments.get(i);
- if (n.getId() == seg.notification.getId() && n.getPackageName().equals(seg.notification.getPackageName())) {
- mSegments.remove(i);
- }
- }
- }
-
- public void halt() {
- mHandler.removeCallbacks(mAdvanceTicker);
- mSegments.clear();
- tickerHalting();
- }
-
- public void reflowText() {
- if (mSegments.size() > 0) {
- Segment seg = mSegments.get(0);
- CharSequence text = seg.getText();
- mTextSwitcher.setCurrentText(text);
- }
- }
-
- private Runnable mAdvanceTicker = new Runnable() {
- public void run() {
- while (mSegments.size() > 0) {
- Segment seg = mSegments.get(0);
-
- if (seg.first) {
- // this makes the icon slide in for the first one for a given
- // notification even if there are two notifications with the
- // same icon in a row
- mIconSwitcher.setImageDrawable(seg.icon);
- }
- CharSequence text = seg.advance();
- if (text == null) {
- mSegments.remove(0);
- continue;
- }
- mTextSwitcher.setText(text);
-
- scheduleAdvance();
- break;
- }
- if (mSegments.size() == 0) {
- tickerDone();
- }
- }
- };
-
- private void scheduleAdvance() {
- mHandler.postDelayed(mAdvanceTicker, TICKER_SEGMENT_DELAY);
- }
-
- public abstract void tickerStarting();
- public abstract void tickerDone();
- public abstract void tickerHalting();
-}
-
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/TrustDrawable.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/TrustDrawable.java
index b89aa8f..56c1e10 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/TrustDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/TrustDrawable.java
@@ -120,7 +120,7 @@ public class TrustDrawable extends Drawable {
}
@Override
- public void setColorFilter(ColorFilter cf) {
+ public void setColorFilter(ColorFilter colorFilter) {
throw new UnsupportedOperationException("not implemented");
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java
index 5ef345b..66d71f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java
@@ -80,8 +80,8 @@ public class UnlockMethodCache {
}
private void update(boolean updateAlways) {
- int user = mLockPatternUtils.getCurrentUser();
- boolean secure = mLockPatternUtils.isSecure();
+ int user = KeyguardUpdateMonitor.getCurrentUser();
+ boolean secure = mLockPatternUtils.isSecure(user);
boolean currentlyInsecure = !secure || mKeyguardUpdateMonitor.getUserHasTrust(user);
boolean trustManaged = mKeyguardUpdateMonitor.getUserTrustIsManaged(user);
boolean faceUnlockRunning = mKeyguardUpdateMonitor.isFaceUnlockRunning(user)
@@ -125,7 +125,7 @@ public class UnlockMethodCache {
}
@Override
- public void onFingerprintRecognized(int userId) {
+ public void onFingerprintAuthenticated(int userId) {
update(false /* updateAlways */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/VelocityTrackerFactory.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/VelocityTrackerFactory.java
index 4f43b4d..e153b85 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/VelocityTrackerFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/VelocityTrackerFactory.java
@@ -20,8 +20,6 @@ import android.content.Context;
import com.android.systemui.R;
-import static android.util.Pools.SynchronizedPool;
-
/**
* A class to generate {@link VelocityTrackerInterface}, depending on the configuration.
*/
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 ad4c211..d1e4963 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
@@ -17,35 +17,26 @@
package com.android.systemui.statusbar.policy;
import android.app.ActivityManager;
-import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.wifi.ScanResult;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiConfiguration.KeyMgmt;
-import android.net.wifi.WifiInfo;
-import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.ActionListener;
+import android.os.Looper;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
-import android.text.TextUtils;
-import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.Log;
+import com.android.settingslib.wifi.AccessPoint;
+import com.android.settingslib.wifi.WifiTracker;
+import com.android.settingslib.wifi.WifiTracker.WifiListener;
import com.android.systemui.R;
+import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
import java.util.List;
-
-// TODO: Unify this logic with platform settings (see WifiSettings and AccessPoint). There is a
-// fair amount of complexity here in statuses and logic beyond just connected/disconnected.
-public class AccessPointControllerImpl implements NetworkController.AccessPointController {
+public class AccessPointControllerImpl
+ implements NetworkController.AccessPointController, WifiListener {
private static final String TAG = "AccessPointController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -54,7 +45,7 @@ public class AccessPointControllerImpl implements NetworkController.AccessPointC
private static final String EXTRA_START_CONNECT_SSID = "wifi_start_connect_ssid";
private static final int[] ICONS = {
- R.drawable.ic_qs_wifi_0,
+ R.drawable.ic_qs_wifi_full_0,
R.drawable.ic_qs_wifi_full_1,
R.drawable.ic_qs_wifi_full_2,
R.drawable.ic_qs_wifi_full_3,
@@ -63,25 +54,18 @@ public class AccessPointControllerImpl implements NetworkController.AccessPointC
private final Context mContext;
private final ArrayList<AccessPointCallback> mCallbacks = new ArrayList<AccessPointCallback>();
- private final WifiManager mWifiManager;
+ private final WifiTracker mWifiTracker;
private final UserManager mUserManager;
- private final Receiver mReceiver = new Receiver();
- private NetworkControllerImpl mNetworkController;
- private boolean mScanning;
private int mCurrentUser;
- public AccessPointControllerImpl(Context context) {
+ public AccessPointControllerImpl(Context context, Looper bgLooper) {
mContext = context;
- mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ mWifiTracker = new WifiTracker(context, this, bgLooper, false, true);
mCurrentUser = ActivityManager.getCurrentUser();
}
- void setNetworkController(NetworkControllerImpl networkController) {
- mNetworkController = networkController;
- }
-
public boolean canConfigWifi() {
return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_WIFI,
new UserHandle(mCurrentUser));
@@ -96,7 +80,9 @@ public class AccessPointControllerImpl implements NetworkController.AccessPointC
if (callback == null || mCallbacks.contains(callback)) return;
if (DEBUG) Log.d(TAG, "addCallback " + callback);
mCallbacks.add(callback);
- mReceiver.setListening(!mCallbacks.isEmpty());
+ if (mCallbacks.size() == 1) {
+ mWifiTracker.startTracking();
+ }
}
@Override
@@ -104,37 +90,40 @@ public class AccessPointControllerImpl implements NetworkController.AccessPointC
if (callback == null) return;
if (DEBUG) Log.d(TAG, "removeCallback " + callback);
mCallbacks.remove(callback);
- mReceiver.setListening(!mCallbacks.isEmpty());
+ if (mCallbacks.isEmpty()) {
+ mWifiTracker.stopTracking();
+ }
}
@Override
public void scanForAccessPoints() {
- if (mScanning) return;
if (DEBUG) Log.d(TAG, "scan!");
- mScanning = mWifiManager.startScan();
- // Grab current networks immediately while we wait for scan.
- updateAccessPoints();
+ mWifiTracker.forceScan();
+ }
+
+ @Override
+ public int getIcon(AccessPoint ap) {
+ int level = ap.getLevel();
+ return ICONS[level >= 0 ? level : 0];
}
public boolean connect(AccessPoint ap) {
if (ap == null) return false;
- if (DEBUG) Log.d(TAG, "connect networkId=" + ap.networkId);
- if (ap.networkId < 0) {
+ if (DEBUG) Log.d(TAG, "connect networkId=" + ap.getConfig().networkId);
+ if (ap.isSaved()) {
+ mWifiTracker.getManager().connect(ap.getConfig().networkId, mConnectListener);
+ } else {
// Unknown network, need to add it.
- if (ap.hasSecurity) {
+ if (ap.getSecurity() != AccessPoint.SECURITY_NONE) {
Intent intent = new Intent(Settings.ACTION_WIFI_SETTINGS);
- intent.putExtra(EXTRA_START_CONNECT_SSID, ap.ssid);
+ intent.putExtra(EXTRA_START_CONNECT_SSID, ap.getSsidStr());
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
fireSettingsIntentCallback(intent);
return true;
} else {
- WifiConfiguration config = new WifiConfiguration();
- config.SSID = "\"" + ap.ssid + "\"";
- config.allowedKeyManagement.set(KeyMgmt.NONE);
- mWifiManager.connect(config, mConnectListener);
+ ap.generateOpenNetworkConfig();
+ mWifiTracker.getManager().connect(ap.getConfig(), mConnectListener);
}
- } else {
- mWifiManager.connect(ap.networkId, mConnectListener);
}
return false;
}
@@ -145,76 +134,28 @@ public class AccessPointControllerImpl implements NetworkController.AccessPointC
}
}
- private void fireAcccessPointsCallback(AccessPoint[] aps) {
+ private void fireAcccessPointsCallback(List<AccessPoint> aps) {
for (AccessPointCallback callback : mCallbacks) {
callback.onAccessPointsChanged(aps);
}
}
- private static String trimDoubleQuotes(String v) {
- return v != null && v.length() >= 2 && v.charAt(0) == '\"'
- && v.charAt(v.length() - 1) == '\"' ? v.substring(1, v.length() - 1) : v;
+ public void dump(PrintWriter pw) {
+ mWifiTracker.dump(pw);
}
- private int getConnectedNetworkId(WifiInfo wifiInfo) {
- return wifiInfo != null ? wifiInfo.getNetworkId() : AccessPoint.NO_NETWORK;
+ @Override
+ public void onWifiStateChanged(int state) {
}
- private ArrayMap<String, WifiConfiguration> getConfiguredNetworksBySsid() {
- final List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
- if (configs == null || configs.size() == 0) return ArrayMap.EMPTY;
- final ArrayMap<String, WifiConfiguration> rt = new ArrayMap<String, WifiConfiguration>();
- for (WifiConfiguration config : configs) {
- rt.put(trimDoubleQuotes(config.SSID), config);
- }
- return rt;
+ @Override
+ public void onConnectedChanged() {
+ fireAcccessPointsCallback(mWifiTracker.getAccessPoints());
}
- private void updateAccessPoints() {
- final WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
- final int connectedNetworkId = getConnectedNetworkId(wifiInfo);
- if (DEBUG) Log.d(TAG, "connectedNetworkId: " + connectedNetworkId);
- final List<ScanResult> scanResults = mWifiManager.getScanResults();
- final ArrayMap<String, WifiConfiguration> configured = getConfiguredNetworksBySsid();
- if (DEBUG) Log.d(TAG, "scanResults: " + scanResults);
- final List<AccessPoint> aps = new ArrayList<AccessPoint>(scanResults.size());
- final ArraySet<String> ssids = new ArraySet<String>();
- for (ScanResult scanResult : scanResults) {
- if (scanResult == null) {
- continue;
- }
- final String ssid = scanResult.SSID;
- if (TextUtils.isEmpty(ssid) || ssids.contains(ssid)) continue;
- ssids.add(ssid);
- final WifiConfiguration config = configured.get(ssid);
- final int level = WifiManager.calculateSignalLevel(scanResult.level, ICONS.length);
- final AccessPoint ap = new AccessPoint();
- ap.isConfigured = config != null;
- ap.networkId = config != null ? config.networkId : AccessPoint.NO_NETWORK;
- ap.ssid = ssid;
- // Connected if either:
- // -The network ID in the active WifiInfo matches this network's ID.
- // -The network is ephemeral (no configuration) but the SSID matches.
- ap.isConnected = (ap.networkId != AccessPoint.NO_NETWORK
- && ap.networkId == connectedNetworkId) ||
- (ap.networkId == WifiConfiguration.INVALID_NETWORK_ID && wifiInfo != null &&
- ap.ssid.equals(trimDoubleQuotes(wifiInfo.getSSID())));
- if (ap.isConnected && mNetworkController != null) {
- // Ensure we have the connected network's RSSI.
- ap.level = mNetworkController.getConnectedWifiLevel();
- } else {
- ap.level = level;
- }
- ap.iconId = ICONS[ap.level];
- // Based on Settings AccessPoint#getSecurity, keep up to date
- // with better methods of determining no security or not.
- ap.hasSecurity = scanResult.capabilities.contains("WEP")
- || scanResult.capabilities.contains("PSK")
- || scanResult.capabilities.contains("EAP");
- aps.add(ap);
- }
- Collections.sort(aps, mByStrength);
- fireAcccessPointsCallback(aps.toArray(new AccessPoint[aps.size()]));
+ @Override
+ public void onAccessPointsChanged() {
+ fireAcccessPointsCallback(mWifiTracker.getAccessPoints());
}
private final ActionListener mConnectListener = new ActionListener() {
@@ -228,49 +169,4 @@ public class AccessPointControllerImpl implements NetworkController.AccessPointC
if (DEBUG) Log.d(TAG, "connect failure reason=" + reason);
}
};
-
- private final Comparator<AccessPoint> mByStrength = new Comparator<AccessPoint> () {
- @Override
- public int compare(AccessPoint lhs, AccessPoint rhs) {
- return -Integer.compare(score(lhs), score(rhs));
- }
-
- private int score(AccessPoint ap) {
- return ap.level + (ap.isConnected ? 20 : 0) + (ap.isConfigured ? 10 : 0);
- }
- };
-
- private final class Receiver extends BroadcastReceiver {
- private boolean mRegistered;
-
- public void setListening(boolean listening) {
- if (listening && !mRegistered) {
- if (DEBUG) Log.d(TAG, "Registering receiver");
- final IntentFilter filter = new IntentFilter();
- filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
- filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
- filter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION);
- filter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
- filter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
- filter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION);
- filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
- filter.addAction(WifiManager.RSSI_CHANGED_ACTION);
- mContext.registerReceiver(this, filter);
- mRegistered = true;
- } else if (!listening && mRegistered) {
- if (DEBUG) Log.d(TAG, "Unregistering receiver");
- mContext.unregisterReceiver(this);
- mRegistered = false;
- }
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- if (DEBUG) Log.d(TAG, "onReceive " + intent.getAction());
- if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(intent.getAction())) {
- updateAccessPoints();
- mScanning = false;
- }
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityContentDescriptions.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityContentDescriptions.java
index 63fcbc5..8f86e2d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityContentDescriptions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityContentDescriptions.java
@@ -35,4 +35,9 @@ public class AccessibilityContentDescriptions {
};
static final int WIFI_NO_CONNECTION = R.string.accessibility_no_wifi;
+
+ static final int[] ETHERNET_CONNECTION_VALUES = {
+ R.string.accessibility_ethernet_disconnected,
+ R.string.accessibility_ethernet_connected,
+ };
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityController.java
index 89ed787..cc431dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityController.java
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.policy;
import android.content.Context;
-import android.util.Log;
import android.view.accessibility.AccessibilityManager;
import java.io.FileDescriptor;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
index 49693f5fe..8fa9c7e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
@@ -16,7 +16,9 @@
package com.android.systemui.statusbar.policy;
-import java.util.Set;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+
+import java.util.Collection;
public interface BluetoothController {
void addStateChangedCallback(Callback callback);
@@ -28,36 +30,12 @@ public interface BluetoothController {
boolean isBluetoothConnecting();
String getLastDeviceName();
void setBluetoothEnabled(boolean enabled);
- Set<PairedDevice> getPairedDevices();
- void connect(PairedDevice device);
- void disconnect(PairedDevice device);
+ Collection<CachedBluetoothDevice> getDevices();
+ void connect(CachedBluetoothDevice device);
+ void disconnect(CachedBluetoothDevice device);
public interface Callback {
- void onBluetoothStateChange(boolean enabled, boolean connecting);
- void onBluetoothPairedDevicesChanged();
- }
-
- public static final class PairedDevice implements Comparable<PairedDevice> {
- public static int STATE_DISCONNECTED = 0;
- public static int STATE_CONNECTING = 1;
- public static int STATE_CONNECTED = 2;
- public static int STATE_DISCONNECTING = 3;
-
- public String id;
- public String name;
- public int state = STATE_DISCONNECTED;
- public Object tag;
-
- public static String stateToString(int state) {
- if (state == STATE_DISCONNECTED) return "STATE_DISCONNECTED";
- if (state == STATE_CONNECTING) return "STATE_CONNECTING";
- if (state == STATE_CONNECTED) return "STATE_CONNECTED";
- if (state == STATE_DISCONNECTING) return "STATE_DISCONNECTING";
- return "UNKNOWN";
- }
-
- public int compareTo(PairedDevice another) {
- return name.compareTo(another.name);
- }
+ void onBluetoothStateChange(boolean enabled);
+ void onBluetoothDevicesChanged();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index 894f82a..daa84ad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -16,143 +16,85 @@
package com.android.systemui.statusbar.policy;
-import static android.bluetooth.BluetoothAdapter.ERROR;
-import static com.android.systemui.statusbar.policy.BluetoothUtil.connectionStateToString;
-import static com.android.systemui.statusbar.policy.BluetoothUtil.deviceToString;
-import static com.android.systemui.statusbar.policy.BluetoothUtil.profileToString;
-import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidToProfile;
-import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidToString;
-import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidsToString;
-
-import android.bluetooth.BluetoothA2dp;
-import android.bluetooth.BluetoothA2dpSink;
import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothHeadset;
-import android.bluetooth.BluetoothHeadsetClient;
-import android.bluetooth.BluetoothInputDevice;
-import android.bluetooth.BluetoothManager;
-import android.bluetooth.BluetoothMap;
-import android.bluetooth.BluetoothPan;
-import android.bluetooth.BluetoothProfile;
-import android.bluetooth.BluetoothProfile.ServiceListener;
-import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
-import android.os.ParcelUuid;
-import android.util.ArrayMap;
import android.util.Log;
-import android.util.SparseArray;
-import com.android.systemui.statusbar.policy.BluetoothUtil.Profile;
+import com.android.settingslib.bluetooth.BluetoothCallback;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Set;
-import java.util.TreeSet;
+import java.util.Collection;
-public class BluetoothControllerImpl implements BluetoothController {
+public class BluetoothControllerImpl implements BluetoothController, BluetoothCallback,
+ CachedBluetoothDevice.Callback {
private static final String TAG = "BluetoothController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- // This controls the order in which we check the states. Since a device can only have
- // one state on screen, but can have multiple profiles, the later states override the
- // value of earlier states. So if a device has a profile in CONNECTING and one in
- // CONNECTED, it will show as CONNECTED, theoretically this shouldn't really happen often,
- // but seemed worth noting.
- private static final int[] CONNECTION_STATES = {
- BluetoothProfile.STATE_DISCONNECTED,
- BluetoothProfile.STATE_DISCONNECTING,
- BluetoothProfile.STATE_CONNECTING,
- BluetoothProfile.STATE_CONNECTED,
- };
- // Update all the BT device states.
- private static final int MSG_UPDATE_CONNECTION_STATES = 1;
- // Update just one BT device.
- private static final int MSG_UPDATE_SINGLE_CONNECTION_STATE = 2;
- // Update whether devices are bonded or not.
- private static final int MSG_UPDATE_BONDED_DEVICES = 3;
-
- private static final int MSG_ADD_PROFILE = 4;
- private static final int MSG_REM_PROFILE = 5;
-
- private final Context mContext;
- private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
- private final BluetoothAdapter mAdapter;
- private final Receiver mReceiver = new Receiver();
- private final ArrayMap<BluetoothDevice, DeviceInfo> mDeviceInfo = new ArrayMap<>();
- private final SparseArray<BluetoothProfile> mProfiles = new SparseArray<>();
- private final H mHandler;
+ private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
+ private final LocalBluetoothManager mLocalBluetoothManager;
private boolean mEnabled;
- private boolean mConnecting;
- private BluetoothDevice mLastDevice;
+ private int mConnectionState = BluetoothAdapter.STATE_DISCONNECTED;
+ private CachedBluetoothDevice mLastDevice;
+
+ private final H mHandler = new H();
public BluetoothControllerImpl(Context context, Looper bgLooper) {
- mContext = context;
- mHandler = new H(bgLooper);
-
- final BluetoothManager bluetoothManager =
- (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
- mAdapter = bluetoothManager.getAdapter();
- if (mAdapter == null) {
- Log.w(TAG, "Default BT adapter not found");
- return;
+ mLocalBluetoothManager = LocalBluetoothManager.getInstance(context, null);
+ if (mLocalBluetoothManager != null) {
+ mLocalBluetoothManager.getEventManager().setReceiverHandler(new Handler(bgLooper));
+ mLocalBluetoothManager.getEventManager().registerCallback(this);
+ onBluetoothStateChanged(
+ mLocalBluetoothManager.getBluetoothAdapter().getBluetoothState());
}
-
- mReceiver.register();
- setAdapterState(mAdapter.getState());
- updateBondedDevices();
- bindAllProfiles();
}
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("BluetoothController state:");
- pw.print(" mAdapter="); pw.println(mAdapter);
+ pw.print(" mLocalBluetoothManager="); pw.println(mLocalBluetoothManager);
+ if (mLocalBluetoothManager == null) {
+ return;
+ }
pw.print(" mEnabled="); pw.println(mEnabled);
- pw.print(" mConnecting="); pw.println(mConnecting);
+ pw.print(" mConnectionState="); pw.println(stateToString(mConnectionState));
pw.print(" mLastDevice="); pw.println(mLastDevice);
pw.print(" mCallbacks.size="); pw.println(mCallbacks.size());
- pw.print(" mProfiles="); pw.println(profilesToString(mProfiles));
- pw.print(" mDeviceInfo.size="); pw.println(mDeviceInfo.size());
- for (int i = 0; i < mDeviceInfo.size(); i++) {
- final BluetoothDevice device = mDeviceInfo.keyAt(i);
- final DeviceInfo info = mDeviceInfo.valueAt(i);
- pw.print(" "); pw.print(deviceToString(device));
- pw.print('('); pw.print(uuidsToString(device)); pw.print(')');
- pw.print(" "); pw.println(infoToString(info));
+ pw.println(" Bluetooth Devices:");
+ for (CachedBluetoothDevice device :
+ mLocalBluetoothManager.getCachedDeviceManager().getCachedDevicesCopy()) {
+ pw.println(" " + getDeviceString(device));
}
}
- private static String infoToString(DeviceInfo info) {
- return info == null ? null : ("connectionState=" +
- connectionStateToString(CONNECTION_STATES[info.connectionStateIndex])
- + ",bonded=" + info.bonded + ",profiles="
- + profilesToString(info.connectedProfiles));
+ private static String stateToString(int state) {
+ switch (state) {
+ case BluetoothAdapter.STATE_CONNECTED:
+ return "CONNECTED";
+ case BluetoothAdapter.STATE_CONNECTING:
+ return "CONNECTING";
+ case BluetoothAdapter.STATE_DISCONNECTED:
+ return "DISCONNECTED";
+ case BluetoothAdapter.STATE_DISCONNECTING:
+ return "DISCONNECTING";
+ }
+ return "UNKNOWN(" + state + ")";
}
- private static String profilesToString(SparseArray<?> profiles) {
- final int N = profiles.size();
- final StringBuffer buffer = new StringBuffer();
- buffer.append('[');
- for (int i = 0; i < N; i++) {
- if (i != 0) {
- buffer.append(',');
- }
- buffer.append(BluetoothUtil.profileToString(profiles.keyAt(i)));
- }
- buffer.append(']');
- return buffer.toString();
+ private String getDeviceString(CachedBluetoothDevice device) {
+ return device.getName() + " " + device.getBondState() + " " + device.isConnected();
}
+ @Override
public void addStateChangedCallback(Callback cb) {
mCallbacks.add(cb);
- fireStateChange(cb);
+ mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
}
@Override
@@ -162,411 +104,147 @@ public class BluetoothControllerImpl implements BluetoothController {
@Override
public boolean isBluetoothEnabled() {
- return mAdapter != null && mAdapter.isEnabled();
+ return mEnabled;
}
@Override
public boolean isBluetoothConnected() {
- return mAdapter != null
- && mAdapter.getConnectionState() == BluetoothAdapter.STATE_CONNECTED;
+ return mConnectionState == BluetoothAdapter.STATE_CONNECTED;
}
@Override
public boolean isBluetoothConnecting() {
- return mAdapter != null
- && mAdapter.getConnectionState() == BluetoothAdapter.STATE_CONNECTING;
+ return mConnectionState == BluetoothAdapter.STATE_CONNECTING;
}
@Override
public void setBluetoothEnabled(boolean enabled) {
- if (mAdapter != null) {
- if (enabled) {
- mAdapter.enable();
- } else {
- mAdapter.disable();
- }
+ if (mLocalBluetoothManager != null) {
+ mLocalBluetoothManager.getBluetoothAdapter().setBluetoothEnabled(enabled);
}
}
@Override
public boolean isBluetoothSupported() {
- return mAdapter != null;
+ return mLocalBluetoothManager != null;
}
@Override
- public Set<PairedDevice> getPairedDevices() {
- final Set<PairedDevice> rt = new TreeSet<>();
- for (int i = 0; i < mDeviceInfo.size(); i++) {
- final BluetoothDevice device = mDeviceInfo.keyAt(i);
- final DeviceInfo info = mDeviceInfo.valueAt(i);
- if (!info.bonded) continue;
- final PairedDevice paired = new PairedDevice();
- paired.id = device.getAddress();
- paired.tag = device;
- paired.name = device.getAliasName();
- paired.state = connectionStateToPairedDeviceState(info.connectionStateIndex);
- rt.add(paired);
- }
- return rt;
- }
-
- private static int connectionStateToPairedDeviceState(int index) {
- int state = CONNECTION_STATES[index];
- if (state == BluetoothAdapter.STATE_CONNECTED) return PairedDevice.STATE_CONNECTED;
- if (state == BluetoothAdapter.STATE_CONNECTING) return PairedDevice.STATE_CONNECTING;
- if (state == BluetoothAdapter.STATE_DISCONNECTING) return PairedDevice.STATE_DISCONNECTING;
- return PairedDevice.STATE_DISCONNECTED;
+ public void connect(final CachedBluetoothDevice device) {
+ if (mLocalBluetoothManager == null || device == null) return;
+ device.connect(true);
}
@Override
- public void connect(final PairedDevice pd) {
- connect(pd, true);
- }
-
- @Override
- public void disconnect(PairedDevice pd) {
- connect(pd, false);
- }
-
- private void connect(PairedDevice pd, final boolean connect) {
- if (mAdapter == null || pd == null || pd.tag == null) return;
- final BluetoothDevice device = (BluetoothDevice) pd.tag;
- final DeviceInfo info = mDeviceInfo.get(device);
- final String action = connect ? "connect" : "disconnect";
- if (DEBUG) Log.d(TAG, action + " " + deviceToString(device));
- final ParcelUuid[] uuids = device.getUuids();
- if (uuids == null) {
- Log.w(TAG, "No uuids returned, aborting " + action + " for " + deviceToString(device));
- return;
- }
- SparseArray<Boolean> profiles = new SparseArray<>();
- if (connect) {
- // When connecting add every profile we can recognize by uuid.
- for (ParcelUuid uuid : uuids) {
- final int profile = uuidToProfile(uuid);
- if (profile == 0) {
- Log.w(TAG, "Device " + deviceToString(device) + " has an unsupported uuid: "
- + uuidToString(uuid));
- continue;
- }
- final boolean connected = info.connectedProfiles.get(profile, false);
- if (!connected) {
- profiles.put(profile, true);
- }
- }
- } else {
- // When disconnecting, just add every profile we know they are connected to.
- profiles = info.connectedProfiles;
- }
- for (int i = 0; i < profiles.size(); i++) {
- final int profile = profiles.keyAt(i);
- if (mProfiles.indexOfKey(profile) >= 0) {
- final Profile p = BluetoothUtil.getProfile(mProfiles.get(profile));
- final boolean ok = connect ? p.connect(device) : p.disconnect(device);
- if (DEBUG) Log.d(TAG, action + " " + profileToString(profile) + " "
- + (ok ? "succeeded" : "failed"));
- } else {
- Log.w(TAG, "Unable get get Profile for " + profileToString(profile));
- }
- }
+ public void disconnect(CachedBluetoothDevice device) {
+ if (mLocalBluetoothManager == null || device == null) return;
+ device.disconnect();
}
@Override
public String getLastDeviceName() {
- return mLastDevice != null ? mLastDevice.getAliasName() : null;
- }
-
- private void updateBondedDevices() {
- mHandler.removeMessages(MSG_UPDATE_BONDED_DEVICES);
- mHandler.sendEmptyMessage(MSG_UPDATE_BONDED_DEVICES);
- }
-
- private void updateConnectionStates() {
- mHandler.removeMessages(MSG_UPDATE_CONNECTION_STATES);
- mHandler.removeMessages(MSG_UPDATE_SINGLE_CONNECTION_STATE);
- mHandler.sendEmptyMessage(MSG_UPDATE_CONNECTION_STATES);
- }
-
- private void updateConnectionState(BluetoothDevice device, int profile, int state) {
- if (mHandler.hasMessages(MSG_UPDATE_CONNECTION_STATES)) {
- // If we are about to update all the devices, then we don't need to update this one.
- return;
- }
- mHandler.obtainMessage(MSG_UPDATE_SINGLE_CONNECTION_STATE, profile, state, device)
- .sendToTarget();
+ return mLastDevice != null ? mLastDevice.getName() : null;
}
- private void handleUpdateBondedDevices() {
- if (mAdapter == null) return;
- final Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
- for (DeviceInfo info : mDeviceInfo.values()) {
- info.bonded = false;
- }
- int bondedCount = 0;
- BluetoothDevice lastBonded = null;
- if (bondedDevices != null) {
- for (BluetoothDevice bondedDevice : bondedDevices) {
- final boolean bonded = bondedDevice.getBondState() != BluetoothDevice.BOND_NONE;
- updateInfo(bondedDevice).bonded = bonded;
- if (bonded) {
- bondedCount++;
- lastBonded = bondedDevice;
- }
- }
- }
- if (mLastDevice == null && bondedCount == 1) {
- mLastDevice = lastBonded;
- }
- updateConnectionStates();
- firePairedDevicesChanged();
+ @Override
+ public Collection<CachedBluetoothDevice> getDevices() {
+ return mLocalBluetoothManager != null
+ ? mLocalBluetoothManager.getCachedDeviceManager().getCachedDevicesCopy()
+ : null;
}
- private void handleUpdateConnectionStates() {
- final int N = mDeviceInfo.size();
- for (int i = 0; i < N; i++) {
- BluetoothDevice device = mDeviceInfo.keyAt(i);
- DeviceInfo info = updateInfo(device);
- info.connectionStateIndex = 0;
- info.connectedProfiles.clear();
- for (int j = 0; j < mProfiles.size(); j++) {
- int state = mProfiles.valueAt(j).getConnectionState(device);
- handleUpdateConnectionState(device, mProfiles.keyAt(j), state);
- }
+ private void updateConnected() {
+ // Make sure our connection state is up to date.
+ int state = mLocalBluetoothManager.getBluetoothAdapter().getConnectionState();
+ if (state != mConnectionState) {
+ mConnectionState = state;
+ mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
}
- handleConnectionChange();
- firePairedDevicesChanged();
- }
-
- private void handleUpdateConnectionState(BluetoothDevice device, int profile, int state) {
- if (DEBUG) Log.d(TAG, "updateConnectionState " + BluetoothUtil.deviceToString(device)
- + " " + BluetoothUtil.profileToString(profile)
- + " " + BluetoothUtil.connectionStateToString(state));
- DeviceInfo info = updateInfo(device);
- int stateIndex = 0;
- for (int i = 0; i < CONNECTION_STATES.length; i++) {
- if (CONNECTION_STATES[i] == state) {
- stateIndex = i;
- break;
- }
+ if (mLastDevice != null && mLastDevice.isConnected()) {
+ // Our current device is still valid.
+ return;
}
- info.profileStates.put(profile, stateIndex);
-
- info.connectionStateIndex = 0;
- final int N = info.profileStates.size();
- for (int i = 0; i < N; i++) {
- if (info.profileStates.valueAt(i) > info.connectionStateIndex) {
- info.connectionStateIndex = info.profileStates.valueAt(i);
+ for (CachedBluetoothDevice device : getDevices()) {
+ if (device.isConnected()) {
+ mLastDevice = device;
}
}
- if (state == BluetoothProfile.STATE_CONNECTED) {
- info.connectedProfiles.put(profile, true);
- } else {
- info.connectedProfiles.remove(profile);
- }
}
- private void handleConnectionChange() {
- // If we are no longer connected to the current device, see if we are connected to
- // something else, so we don't display a name we aren't connected to.
- if (mLastDevice != null &&
- CONNECTION_STATES[mDeviceInfo.get(mLastDevice).connectionStateIndex]
- != BluetoothProfile.STATE_CONNECTED) {
- // Make sure we don't keep this device while it isn't connected.
- mLastDevice = null;
- // Look for anything else connected.
- final int size = mDeviceInfo.size();
- for (int i = 0; i < size; i++) {
- BluetoothDevice device = mDeviceInfo.keyAt(i);
- DeviceInfo info = mDeviceInfo.valueAt(i);
- if (CONNECTION_STATES[info.connectionStateIndex]
- == BluetoothProfile.STATE_CONNECTED) {
- mLastDevice = device;
- break;
- }
- }
- }
+ @Override
+ public void onBluetoothStateChanged(int bluetoothState) {
+ mEnabled = bluetoothState == BluetoothAdapter.STATE_ON;
+ mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
}
- private void bindAllProfiles() {
- // Note: This needs to contain all of the types that can be returned by BluetoothUtil
- // otherwise we can't find the profiles we need when we connect/disconnect.
- mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.A2DP);
- mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.A2DP_SINK);
- mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.AVRCP_CONTROLLER);
- mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.HEADSET);
- mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.HEADSET_CLIENT);
- mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.INPUT_DEVICE);
- mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.MAP);
- mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.PAN);
- // Note Health is not in this list because health devices aren't 'connected'.
- // If profiles are expanded to use more than just connection state and connect/disconnect
- // then it should be added.
+ @Override
+ public void onScanningStateChanged(boolean started) {
+ // Don't care.
}
- private void firePairedDevicesChanged() {
- for (Callback cb : mCallbacks) {
- cb.onBluetoothPairedDevicesChanged();
- }
+ @Override
+ public void onDeviceAdded(CachedBluetoothDevice cachedDevice) {
+ cachedDevice.registerCallback(this);
+ updateConnected();
+ mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED);
}
- private void setAdapterState(int adapterState) {
- final boolean enabled = adapterState == BluetoothAdapter.STATE_ON;
- if (mEnabled == enabled) return;
- mEnabled = enabled;
- fireStateChange();
+ @Override
+ public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) {
+ updateConnected();
+ mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED);
}
- private void setConnecting(boolean connecting) {
- if (mConnecting == connecting) return;
- mConnecting = connecting;
- fireStateChange();
+ @Override
+ public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
+ updateConnected();
+ mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED);
}
- private void fireStateChange() {
- for (Callback cb : mCallbacks) {
- fireStateChange(cb);
- }
+ @Override
+ public void onDeviceAttributesChanged() {
+ updateConnected();
+ mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED);
}
- private void fireStateChange(Callback cb) {
- cb.onBluetoothStateChange(mEnabled, mConnecting);
+ @Override
+ public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
+ mLastDevice = cachedDevice;
+ updateConnected();
+ mConnectionState = state;
+ mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
}
- private static int getProfileFromAction(String action) {
- if (BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
- return BluetoothProfile.A2DP;
- } else if (BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
- return BluetoothProfile.HEADSET;
- } else if (BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
- return BluetoothProfile.A2DP_SINK;
- } else if (BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
- return BluetoothProfile.HEADSET_CLIENT;
- } else if (BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
- return BluetoothProfile.INPUT_DEVICE;
- } else if (BluetoothMap.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
- return BluetoothProfile.MAP;
- } else if (BluetoothPan.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
- return BluetoothProfile.PAN;
- }
- if (DEBUG) Log.d(TAG, "Unknown action " + action);
- return -1;
- }
+ private final class H extends Handler {
+ private static final int MSG_PAIRED_DEVICES_CHANGED = 1;
+ private static final int MSG_STATE_CHANGED = 2;
- private final ServiceListener mProfileListener = new ServiceListener() {
@Override
- public void onServiceDisconnected(int profile) {
- if (DEBUG) Log.d(TAG, "Disconnected from " + BluetoothUtil.profileToString(profile));
- // We lost a profile, don't do any updates until it gets removed.
- mHandler.removeMessages(MSG_UPDATE_CONNECTION_STATES);
- mHandler.removeMessages(MSG_UPDATE_SINGLE_CONNECTION_STATE);
- mHandler.obtainMessage(MSG_REM_PROFILE, profile, 0).sendToTarget();
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_PAIRED_DEVICES_CHANGED:
+ firePairedDevicesChanged();
+ break;
+ case MSG_STATE_CHANGED:
+ fireStateChange();
+ break;
+ }
}
- @Override
- public void onServiceConnected(int profile, BluetoothProfile proxy) {
- if (DEBUG) Log.d(TAG, "Connected to " + BluetoothUtil.profileToString(profile));
- mHandler.obtainMessage(MSG_ADD_PROFILE, profile, 0, proxy).sendToTarget();
- }
- };
-
- private final class Receiver extends BroadcastReceiver {
- public void register() {
- final IntentFilter filter = new IntentFilter();
- filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
- filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
- filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
- filter.addAction(BluetoothDevice.ACTION_ALIAS_CHANGED);
- filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
- filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
- filter.addAction(BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED);
- filter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED);
- filter.addAction(BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED);
- filter.addAction(BluetoothMap.ACTION_CONNECTION_STATE_CHANGED);
- filter.addAction(BluetoothPan.ACTION_CONNECTION_STATE_CHANGED);
- mContext.registerReceiver(this, filter);
+ private void firePairedDevicesChanged() {
+ for (BluetoothController.Callback cb : mCallbacks) {
+ cb.onBluetoothDevicesChanged();
+ }
}
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-
- if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
- setAdapterState(intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, ERROR));
- updateBondedDevices();
- if (DEBUG) Log.d(TAG, "ACTION_STATE_CHANGED " + mEnabled);
- } else if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) {
- updateInfo(device);
- final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE,
- ERROR);
- mLastDevice = device;
- if (DEBUG) Log.d(TAG, "ACTION_CONNECTION_STATE_CHANGED "
- + connectionStateToString(state) + " " + deviceToString(device));
- setConnecting(state == BluetoothAdapter.STATE_CONNECTING);
- } else if (action.equals(BluetoothDevice.ACTION_ALIAS_CHANGED)) {
- updateInfo(device);
- mLastDevice = device;
- } else if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) {
- if (DEBUG) Log.d(TAG, "ACTION_BOND_STATE_CHANGED " + device);
- updateBondedDevices();
- } else {
- int profile = getProfileFromAction(intent.getAction());
- int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
- if (DEBUG) Log.d(TAG, "ACTION_CONNECTION_STATE_CHANGE "
- + BluetoothUtil.profileToString(profile)
- + " " + BluetoothUtil.connectionStateToString(state));
- if ((profile != -1) && (state != -1)) {
- updateConnectionState(device, profile, state);
- }
+ private void fireStateChange() {
+ for (BluetoothController.Callback cb : mCallbacks) {
+ fireStateChange(cb);
}
}
- }
- private DeviceInfo updateInfo(BluetoothDevice device) {
- DeviceInfo info = mDeviceInfo.get(device);
- info = info != null ? info : new DeviceInfo();
- mDeviceInfo.put(device, info);
- return info;
- }
-
- private class H extends Handler {
- public H(Looper l) {
- super(l);
+ private void fireStateChange(BluetoothController.Callback cb) {
+ cb.onBluetoothStateChange(mEnabled);
}
-
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_UPDATE_CONNECTION_STATES:
- handleUpdateConnectionStates();
- firePairedDevicesChanged();
- break;
- case MSG_UPDATE_SINGLE_CONNECTION_STATE:
- handleUpdateConnectionState((BluetoothDevice) msg.obj, msg.arg1, msg.arg2);
- handleConnectionChange();
- firePairedDevicesChanged();
- break;
- case MSG_UPDATE_BONDED_DEVICES:
- handleUpdateBondedDevices();
- firePairedDevicesChanged();
- break;
- case MSG_ADD_PROFILE:
- mProfiles.put(msg.arg1, (BluetoothProfile) msg.obj);
- handleUpdateConnectionStates();
- firePairedDevicesChanged();
- break;
- case MSG_REM_PROFILE:
- mProfiles.remove(msg.arg1);
- handleUpdateConnectionStates();
- firePairedDevicesChanged();
- break;
- }
- };
- };
-
- private static class DeviceInfo {
- int connectionStateIndex = 0;
- boolean bonded; // per getBondedDevices
- SparseArray<Boolean> connectedProfiles = new SparseArray<>();
- SparseArray<Integer> profileStates = new SparseArray<>();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothUtil.java
deleted file mode 100644
index ed8ac2c..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothUtil.java
+++ /dev/null
@@ -1,234 +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.statusbar.policy;
-
-import android.bluetooth.BluetoothA2dp;
-import android.bluetooth.BluetoothA2dpSink;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothHeadset;
-import android.bluetooth.BluetoothHeadsetClient;
-import android.bluetooth.BluetoothInputDevice;
-import android.bluetooth.BluetoothMap;
-import android.bluetooth.BluetoothPan;
-import android.bluetooth.BluetoothProfile;
-import android.bluetooth.BluetoothUuid;
-import android.os.ParcelUuid;
-import android.text.TextUtils;
-
-public class BluetoothUtil {
-
- public static String profileToString(int profile) {
- if (profile == BluetoothProfile.HEADSET) return "HEADSET";
- if (profile == BluetoothProfile.A2DP) return "A2DP";
- if (profile == BluetoothProfile.AVRCP_CONTROLLER) return "AVRCP_CONTROLLER";
- if (profile == BluetoothProfile.PAN) return "PAN";
- if (profile == BluetoothProfile.INPUT_DEVICE) return "INPUT_DEVICE";
- if (profile == BluetoothProfile.MAP) return "MAP";
- return "UNKNOWN(" + profile + ")";
- }
-
- public static String profileStateToString(int state) {
- if (state == BluetoothProfile.STATE_CONNECTED) return "STATE_CONNECTED";
- if (state == BluetoothProfile.STATE_CONNECTING) return "STATE_CONNECTING";
- if (state == BluetoothProfile.STATE_DISCONNECTED) return "STATE_DISCONNECTED";
- if (state == BluetoothProfile.STATE_DISCONNECTING) return "STATE_DISCONNECTING";
- return "STATE_UNKNOWN";
- }
-
- public static String uuidToString(ParcelUuid uuid) {
- if (BluetoothUuid.AudioSink.equals(uuid)) return "AudioSink";
- if (BluetoothUuid.AudioSource.equals(uuid)) return "AudioSource";
- if (BluetoothUuid.AdvAudioDist.equals(uuid)) return "AdvAudioDist";
- if (BluetoothUuid.HSP.equals(uuid)) return "HSP";
- if (BluetoothUuid.HSP_AG.equals(uuid)) return "HSP_AG";
- if (BluetoothUuid.Handsfree.equals(uuid)) return "Handsfree";
- if (BluetoothUuid.Handsfree_AG.equals(uuid)) return "Handsfree_AG";
- if (BluetoothUuid.AvrcpController.equals(uuid)) return "AvrcpController";
- if (BluetoothUuid.AvrcpTarget.equals(uuid)) return "AvrcpTarget";
- if (BluetoothUuid.ObexObjectPush.equals(uuid)) return "ObexObjectPush";
- if (BluetoothUuid.Hid.equals(uuid)) return "Hid";
- if (BluetoothUuid.Hogp.equals(uuid)) return "Hogp";
- if (BluetoothUuid.PANU.equals(uuid)) return "PANU";
- if (BluetoothUuid.NAP.equals(uuid)) return "NAP";
- if (BluetoothUuid.BNEP.equals(uuid)) return "BNEP";
- if (BluetoothUuid.PBAP_PSE.equals(uuid)) return "PBAP_PSE";
- if (BluetoothUuid.MAP.equals(uuid)) return "MAP";
- if (BluetoothUuid.MNS.equals(uuid)) return "MNS";
- if (BluetoothUuid.MAS.equals(uuid)) return "MAS";
- return uuid != null ? uuid.toString() : null;
- }
-
- public static String connectionStateToString(int connectionState) {
- if (connectionState == BluetoothAdapter.STATE_DISCONNECTED) return "STATE_DISCONNECTED";
- if (connectionState == BluetoothAdapter.STATE_CONNECTED) return "STATE_CONNECTED";
- if (connectionState == BluetoothAdapter.STATE_DISCONNECTING) return "STATE_DISCONNECTING";
- if (connectionState == BluetoothAdapter.STATE_CONNECTING) return "STATE_CONNECTING";
- return "ERROR";
- }
-
- public static String deviceToString(BluetoothDevice device) {
- return device == null ? null : (device.getAddress() + '[' + device.getAliasName() + ']');
- }
-
- public static String uuidsToString(BluetoothDevice device) {
- if (device == null) return null;
- final ParcelUuid[] ids = device.getUuids();
- if (ids == null) return null;
- final String[] tokens = new String[ids.length];
- for (int i = 0; i < tokens.length; i++) {
- tokens[i] = uuidToString(ids[i]);
- }
- return TextUtils.join(",", tokens);
- }
-
- public static int uuidToProfile(ParcelUuid uuid) {
- if (BluetoothUuid.AudioSink.equals(uuid)) return BluetoothProfile.A2DP;
- if (BluetoothUuid.AdvAudioDist.equals(uuid)) return BluetoothProfile.A2DP;
-
- if (BluetoothUuid.HSP.equals(uuid)) return BluetoothProfile.HEADSET;
- if (BluetoothUuid.Handsfree.equals(uuid)) return BluetoothProfile.HEADSET;
-
- if (BluetoothUuid.MAP.equals(uuid)) return BluetoothProfile.MAP;
- if (BluetoothUuid.MNS.equals(uuid)) return BluetoothProfile.MAP;
- if (BluetoothUuid.MAS.equals(uuid)) return BluetoothProfile.MAP;
-
- if (BluetoothUuid.AvrcpController.equals(uuid)) return BluetoothProfile.AVRCP_CONTROLLER;
-
- if (BluetoothUuid.Hid.equals(uuid)) return BluetoothProfile.INPUT_DEVICE;
- if (BluetoothUuid.Hogp.equals(uuid)) return BluetoothProfile.INPUT_DEVICE;
-
- if (BluetoothUuid.NAP.equals(uuid)) return BluetoothProfile.PAN;
-
- return 0;
- }
-
- public static Profile getProfile(BluetoothProfile p) {
- if (p instanceof BluetoothA2dp) return newProfile((BluetoothA2dp) p);
- if (p instanceof BluetoothHeadset) return newProfile((BluetoothHeadset) p);
- if (p instanceof BluetoothA2dpSink) return newProfile((BluetoothA2dpSink) p);
- if (p instanceof BluetoothHeadsetClient) return newProfile((BluetoothHeadsetClient) p);
- if (p instanceof BluetoothInputDevice) return newProfile((BluetoothInputDevice) p);
- if (p instanceof BluetoothMap) return newProfile((BluetoothMap) p);
- if (p instanceof BluetoothPan) return newProfile((BluetoothPan) p);
- return null;
- }
-
- private static Profile newProfile(final BluetoothA2dp a2dp) {
- return new Profile() {
- @Override
- public boolean connect(BluetoothDevice device) {
- return a2dp.connect(device);
- }
-
- @Override
- public boolean disconnect(BluetoothDevice device) {
- return a2dp.disconnect(device);
- }
- };
- }
-
- private static Profile newProfile(final BluetoothHeadset headset) {
- return new Profile() {
- @Override
- public boolean connect(BluetoothDevice device) {
- return headset.connect(device);
- }
-
- @Override
- public boolean disconnect(BluetoothDevice device) {
- return headset.disconnect(device);
- }
- };
- }
-
- private static Profile newProfile(final BluetoothA2dpSink sink) {
- return new Profile() {
- @Override
- public boolean connect(BluetoothDevice device) {
- return sink.connect(device);
- }
-
- @Override
- public boolean disconnect(BluetoothDevice device) {
- return sink.disconnect(device);
- }
- };
- }
-
- private static Profile newProfile(final BluetoothHeadsetClient client) {
- return new Profile() {
- @Override
- public boolean connect(BluetoothDevice device) {
- return client.connect(device);
- }
-
- @Override
- public boolean disconnect(BluetoothDevice device) {
- return client.disconnect(device);
- }
- };
- }
-
- private static Profile newProfile(final BluetoothInputDevice input) {
- return new Profile() {
- @Override
- public boolean connect(BluetoothDevice device) {
- return input.connect(device);
- }
-
- @Override
- public boolean disconnect(BluetoothDevice device) {
- return input.disconnect(device);
- }
- };
- }
-
- private static Profile newProfile(final BluetoothMap map) {
- return new Profile() {
- @Override
- public boolean connect(BluetoothDevice device) {
- return map.connect(device);
- }
-
- @Override
- public boolean disconnect(BluetoothDevice device) {
- return map.disconnect(device);
- }
- };
- }
-
- private static Profile newProfile(final BluetoothPan pan) {
- return new Profile() {
- @Override
- public boolean connect(BluetoothDevice device) {
- return pan.connect(device);
- }
-
- @Override
- public boolean disconnect(BluetoothDevice device) {
- return pan.disconnect(device);
- }
- };
- }
-
- // common abstraction for supported profiles
- public interface Profile {
- boolean connect(BluetoothDevice device);
- boolean disconnect(BluetoothDevice device);
- }
-}
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 895af62..0340984 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
@@ -58,7 +58,7 @@ public class BrightnessMirrorController {
.withEndAction(new Runnable() {
@Override
public void run() {
- mBrightnessMirror.setVisibility(View.GONE);
+ mBrightnessMirror.setVisibility(View.INVISIBLE);
}
});
}
@@ -77,12 +77,18 @@ public class BrightnessMirrorController {
public void setLocation(View original) {
original.getLocationInWindow(mInt2Cache);
+
+ // Original is slightly larger than the mirror, so make sure to use the center for the
+ // positioning.
+ int originalX = mInt2Cache[0] + original.getWidth()/2;
int originalY = mInt2Cache[1];
+ mBrightnessMirror.setTranslationX(0);
+ mBrightnessMirror.setTranslationY(0);
mBrightnessMirror.getLocationInWindow(mInt2Cache);
+ int mirrorX = mInt2Cache[0] + mBrightnessMirror.getWidth()/2;
int mirrorY = mInt2Cache[1];
-
- mBrightnessMirror.setTranslationY(mBrightnessMirror.getTranslationY()
- + originalY - mirrorY);
+ mBrightnessMirror.setTranslationX(originalX - mirrorX);
+ mBrightnessMirror.setTranslationY(originalY - mirrorY);
}
public View getMirror() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java
new file mode 100644
index 0000000..e618cb8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java
@@ -0,0 +1,180 @@
+/*
+ * 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.statusbar.policy;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.telephony.SubscriptionInfo;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.statusbar.policy.NetworkController.IconState;
+import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+import com.android.systemui.statusbar.policy.NetworkControllerImpl.EmergencyListener;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * Implements network listeners and forwards the calls along onto other listeners but on
+ * the current or specified Looper.
+ */
+public class CallbackHandler extends Handler implements EmergencyListener, SignalCallback {
+ private static final int MSG_EMERGENCE_CHANGED = 0;
+ private static final int MSG_SUBS_CHANGED = 1;
+ private static final int MSG_NO_SIM_VISIBLE_CHANGED = 2;
+ private static final int MSG_ETHERNET_CHANGED = 3;
+ private static final int MSG_AIRPLANE_MODE_CHANGED = 4;
+ private static final int MSG_MOBILE_DATA_ENABLED_CHANGED = 5;
+ private static final int MSG_ADD_REMOVE_EMERGENCY = 6;
+ private static final int MSG_ADD_REMOVE_SIGNAL = 7;
+
+ // All the callbacks.
+ private final ArrayList<EmergencyListener> mEmergencyListeners = new ArrayList<>();
+ private final ArrayList<SignalCallback> mSignalCallbacks = new ArrayList<>();
+
+ public CallbackHandler() {
+ super();
+ }
+
+ @VisibleForTesting
+ CallbackHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_EMERGENCE_CHANGED:
+ for (EmergencyListener listener : mEmergencyListeners) {
+ listener.setEmergencyCallsOnly(msg.arg1 != 0);
+ }
+ break;
+ case MSG_SUBS_CHANGED:
+ for (SignalCallback signalCluster : mSignalCallbacks) {
+ signalCluster.setSubs((List<SubscriptionInfo>) msg.obj);
+ }
+ break;
+ case MSG_NO_SIM_VISIBLE_CHANGED:
+ for (SignalCallback signalCluster : mSignalCallbacks) {
+ signalCluster.setNoSims(msg.arg1 != 0);
+ }
+ break;
+ case MSG_ETHERNET_CHANGED:
+ for (SignalCallback signalCluster : mSignalCallbacks) {
+ signalCluster.setEthernetIndicators((IconState) msg.obj);
+ }
+ break;
+ case MSG_AIRPLANE_MODE_CHANGED:
+ for (SignalCallback signalCluster : mSignalCallbacks) {
+ signalCluster.setIsAirplaneMode((IconState) msg.obj);
+ }
+ break;
+ case MSG_MOBILE_DATA_ENABLED_CHANGED:
+ for (SignalCallback signalCluster : mSignalCallbacks) {
+ signalCluster.setMobileDataEnabled(msg.arg1 != 0);
+ }
+ break;
+ case MSG_ADD_REMOVE_EMERGENCY:
+ if (msg.arg1 != 0) {
+ mEmergencyListeners.add((EmergencyListener) msg.obj);
+ } else {
+ mEmergencyListeners.remove((EmergencyListener) msg.obj);
+ }
+ break;
+ case MSG_ADD_REMOVE_SIGNAL:
+ if (msg.arg1 != 0) {
+ mSignalCallbacks.add((SignalCallback) msg.obj);
+ } else {
+ mSignalCallbacks.remove((SignalCallback) msg.obj);
+ }
+ break;
+ }
+ }
+
+ @Override
+ public void setWifiIndicators(final boolean enabled, final IconState statusIcon,
+ final IconState qsIcon, final boolean activityIn, final boolean activityOut,
+ final String description) {
+ post(new Runnable() {
+ @Override
+ public void run() {
+ for (SignalCallback callback : mSignalCallbacks) {
+ callback.setWifiIndicators(enabled, statusIcon, qsIcon, activityIn, activityOut,
+ description);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void setMobileDataIndicators(final IconState statusIcon, final IconState qsIcon,
+ final int statusType, final int qsType,final boolean activityIn,
+ final boolean activityOut, final String typeContentDescription,
+ final String description, final boolean isWide, final int subId) {
+ post(new Runnable() {
+ @Override
+ public void run() {
+ for (SignalCallback signalCluster : mSignalCallbacks) {
+ signalCluster.setMobileDataIndicators(statusIcon, qsIcon, statusType, qsType,
+ activityIn, activityOut, typeContentDescription, description, isWide,
+ subId);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void setSubs(List<SubscriptionInfo> subs) {
+ obtainMessage(MSG_SUBS_CHANGED, subs).sendToTarget();
+ }
+
+ @Override
+ public void setNoSims(boolean show) {
+ obtainMessage(MSG_NO_SIM_VISIBLE_CHANGED, show ? 1 : 0, 0).sendToTarget();
+ }
+
+ @Override
+ public void setMobileDataEnabled(boolean enabled) {
+ obtainMessage(MSG_MOBILE_DATA_ENABLED_CHANGED, enabled ? 1 : 0, 0).sendToTarget();
+ }
+
+ @Override
+ public void setEmergencyCallsOnly(boolean emergencyOnly) {
+ obtainMessage(MSG_EMERGENCE_CHANGED, emergencyOnly ? 1 : 0, 0).sendToTarget();
+ }
+
+ @Override
+ public void setEthernetIndicators(IconState icon) {
+ obtainMessage(MSG_ETHERNET_CHANGED, icon).sendToTarget();;
+ }
+
+ @Override
+ public void setIsAirplaneMode(IconState icon) {
+ obtainMessage(MSG_AIRPLANE_MODE_CHANGED, icon).sendToTarget();;
+ }
+
+ public void setListening(EmergencyListener listener, boolean listening) {
+ obtainMessage(MSG_ADD_REMOVE_EMERGENCY, listening ? 1 : 0, 0, listener).sendToTarget();
+ }
+
+ public void setListening(SignalCallback listener, boolean listening) {
+ obtainMessage(MSG_ADD_REMOVE_SIGNAL, listening ? 1 : 0, 0, listener).sendToTarget();
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recent/Constants.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EthernetIcons.java
index 8252a9f..b391bd9 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/Constants.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EthernetIcons.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2011 The Android Open Source Project
+ * 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.
@@ -14,12 +14,13 @@
* limitations under the License.
*/
-package com.android.systemui.recent;
+package com.android.systemui.statusbar.policy;
-public class Constants {
- static final int MAX_ESCAPE_ANIMATION_DURATION = 500; // in ms
- static final int SNAP_BACK_DURATION = 250; // in ms
- static final int ESCAPE_VELOCITY = 100; // speed of item required to "curate" it in dp/s
- public static float ALPHA_FADE_START = 0.8f; // fraction of thumbnail width where fade starts
- static final float ALPHA_FADE_END = 0.5f; // fraction of thumbnail width beyond which alpha->0
+import com.android.systemui.R;
+
+class EthernetIcons {
+ static final int[][] ETHERNET_ICONS = {
+ { R.drawable.stat_sys_ethernet },
+ { R.drawable.stat_sys_ethernet_fully },
+ };
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EthernetSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EthernetSignalController.java
new file mode 100644
index 0000000..bd36462
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EthernetSignalController.java
@@ -0,0 +1,62 @@
+/*
+ * 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.statusbar.policy;
+
+import android.content.Context;
+import android.net.NetworkCapabilities;
+
+import com.android.systemui.statusbar.policy.NetworkController.IconState;
+
+import java.util.BitSet;
+
+
+public class EthernetSignalController extends
+ SignalController<SignalController.State, SignalController.IconGroup> {
+
+ public EthernetSignalController(Context context,
+ CallbackHandler callbackHandler, NetworkControllerImpl networkController) {
+ super("EthernetSignalController", context, NetworkCapabilities.TRANSPORT_ETHERNET,
+ callbackHandler, networkController);
+ mCurrentState.iconGroup = mLastState.iconGroup = new IconGroup(
+ "Ethernet Icons",
+ EthernetIcons.ETHERNET_ICONS,
+ null,
+ AccessibilityContentDescriptions.ETHERNET_CONNECTION_VALUES,
+ 0, 0, 0, 0,
+ AccessibilityContentDescriptions.ETHERNET_CONNECTION_VALUES[0]);
+ }
+
+ @Override
+ public void updateConnectivity(BitSet connectedTransports, BitSet validatedTransports) {
+ mCurrentState.connected = connectedTransports.get(mTransportType);
+ super.updateConnectivity(connectedTransports, validatedTransports);
+ }
+
+ @Override
+ public void notifyListeners() {
+ boolean ethernetVisible = mCurrentState.connected;
+ String contentDescription = getStringIfExists(getContentDescription());
+
+ // TODO: wire up data transfer using WifiSignalPoller.
+ mCallbackHandler.setEthernetIndicators(new IconState(ethernetVisible, getCurrentIconId(),
+ contentDescription));
+ }
+
+ @Override
+ public SignalController.State cleanState() {
+ return new SignalController.State();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java
index 33f7aff..cd1914c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java
@@ -17,21 +17,14 @@
package com.android.systemui.statusbar.policy;
import android.content.Context;
-import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
-import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
-import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
-import android.hardware.camera2.CameraMetadata;
-import android.hardware.camera2.CaptureRequest;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Process;
-import android.os.SystemProperties;
+import android.text.TextUtils;
import android.util.Log;
-import android.util.Size;
-import android.view.Surface;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
@@ -45,7 +38,7 @@ public class FlashlightController {
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final int DISPATCH_ERROR = 0;
- private static final int DISPATCH_OFF = 1;
+ private static final int DISPATCH_CHANGED = 1;
private static final int DISPATCH_AVAILABILITY_CHANGED = 2;
private final CameraManager mCameraManager;
@@ -58,52 +51,50 @@ public class FlashlightController {
/** Lock on {@code this} when accessing */
private boolean mFlashlightEnabled;
- private String mCameraId;
- private boolean mCameraAvailable;
- private CameraDevice mCameraDevice;
- private CaptureRequest mFlashlightRequest;
- private CameraCaptureSession mSession;
- private SurfaceTexture mSurfaceTexture;
- private Surface mSurface;
+ private final String mCameraId;
+ private boolean mTorchAvailable;
public FlashlightController(Context mContext) {
mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
- initialize();
- }
- public void initialize() {
+ String cameraId = null;
try {
- mCameraId = getCameraId();
+ cameraId = getCameraId();
} catch (Throwable e) {
Log.e(TAG, "Couldn't initialize.", e);
return;
+ } finally {
+ mCameraId = cameraId;
}
if (mCameraId != null) {
ensureHandler();
- mCameraManager.registerAvailabilityCallback(mAvailabilityCallback, mHandler);
+ mCameraManager.registerTorchCallback(mTorchCallback, mHandler);
}
}
- public synchronized void setFlashlight(boolean enabled) {
- if (mFlashlightEnabled != enabled) {
- mFlashlightEnabled = enabled;
- postUpdateFlashlight();
- }
- }
-
- public void killFlashlight() {
- boolean enabled;
+ public void setFlashlight(boolean enabled) {
+ boolean pendingError = false;
synchronized (this) {
- enabled = mFlashlightEnabled;
+ if (mFlashlightEnabled != enabled) {
+ mFlashlightEnabled = enabled;
+ try {
+ mCameraManager.setTorchMode(mCameraId, enabled);
+ } catch (CameraAccessException e) {
+ Log.e(TAG, "Couldn't set torch mode", e);
+ mFlashlightEnabled = false;
+ pendingError = true;
+ }
+ }
}
- if (enabled) {
- mHandler.post(mKillFlashlightRunnable);
+ dispatchModeChanged(mFlashlightEnabled);
+ if (pendingError) {
+ dispatchError();
}
}
public synchronized boolean isAvailable() {
- return mCameraAvailable;
+ return mTorchAvailable;
}
public void addListener(FlashlightListener l) {
@@ -127,42 +118,6 @@ public class FlashlightController {
}
}
- private void startDevice() throws CameraAccessException {
- mCameraManager.openCamera(getCameraId(), mCameraListener, mHandler);
- }
-
- private void startSession() throws CameraAccessException {
- mSurfaceTexture = new SurfaceTexture(false);
- Size size = getSmallestSize(mCameraDevice.getId());
- mSurfaceTexture.setDefaultBufferSize(size.getWidth(), size.getHeight());
- mSurface = new Surface(mSurfaceTexture);
- ArrayList<Surface> outputs = new ArrayList<>(1);
- outputs.add(mSurface);
- mCameraDevice.createCaptureSession(outputs, mSessionListener, mHandler);
- }
-
- private Size getSmallestSize(String cameraId) throws CameraAccessException {
- Size[] outputSizes = mCameraManager.getCameraCharacteristics(cameraId)
- .get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
- .getOutputSizes(SurfaceTexture.class);
- if (outputSizes == null || outputSizes.length == 0) {
- throw new IllegalStateException(
- "Camera " + cameraId + "doesn't support any outputSize.");
- }
- Size chosen = outputSizes[0];
- for (Size s : outputSizes) {
- if (chosen.getWidth() >= s.getWidth() && chosen.getHeight() >= s.getHeight()) {
- chosen = s;
- }
- }
- return chosen;
- }
-
- private void postUpdateFlashlight() {
- ensureHandler();
- mHandler.post(mUpdateFlashlightRunnable);
- }
-
private String getCameraId() throws CameraAccessException {
String[] ids = mCameraManager.getCameraIdList();
for (String id : ids) {
@@ -177,70 +132,12 @@ public class FlashlightController {
return null;
}
- private void updateFlashlight(boolean forceDisable) {
- try {
- boolean enabled;
- synchronized (this) {
- enabled = mFlashlightEnabled && !forceDisable;
- }
- if (enabled) {
- if (mCameraDevice == null) {
- startDevice();
- return;
- }
- if (mSession == null) {
- startSession();
- return;
- }
- if (mFlashlightRequest == null) {
- CaptureRequest.Builder builder = mCameraDevice.createCaptureRequest(
- CameraDevice.TEMPLATE_PREVIEW);
- builder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_TORCH);
- builder.addTarget(mSurface);
- CaptureRequest request = builder.build();
- mSession.capture(request, null, mHandler);
- mFlashlightRequest = request;
- }
- } else {
- if (mCameraDevice != null) {
- mCameraDevice.close();
- teardown();
- }
- }
-
- } catch (CameraAccessException|IllegalStateException|UnsupportedOperationException e) {
- Log.e(TAG, "Error in updateFlashlight", e);
- handleError();
- }
- }
-
- private void teardown() {
- mCameraDevice = null;
- mSession = null;
- mFlashlightRequest = null;
- if (mSurface != null) {
- mSurface.release();
- mSurfaceTexture.release();
- }
- mSurface = null;
- mSurfaceTexture = null;
- }
-
- private void handleError() {
- synchronized (this) {
- mFlashlightEnabled = false;
- }
- dispatchError();
- dispatchOff();
- updateFlashlight(true /* forceDisable */);
- }
-
- private void dispatchOff() {
- dispatchListeners(DISPATCH_OFF, false /* argument (ignored) */);
+ private void dispatchModeChanged(boolean enabled) {
+ dispatchListeners(DISPATCH_CHANGED, enabled);
}
private void dispatchError() {
- dispatchListeners(DISPATCH_ERROR, false /* argument (ignored) */);
+ dispatchListeners(DISPATCH_CHANGED, false /* argument (ignored) */);
}
private void dispatchAvailabilityChanged(boolean available) {
@@ -256,8 +153,8 @@ public class FlashlightController {
if (l != null) {
if (message == DISPATCH_ERROR) {
l.onFlashlightError();
- } else if (message == DISPATCH_OFF) {
- l.onFlashlightOff();
+ } else if (message == DISPATCH_CHANGED) {
+ l.onFlashlightChanged(argument);
} else if (message == DISPATCH_AVAILABILITY_CHANGED) {
l.onFlashlightAvailabilityChanged(argument);
}
@@ -280,106 +177,57 @@ public class FlashlightController {
}
}
- private final CameraDevice.StateListener mCameraListener = new CameraDevice.StateListener() {
- @Override
- public void onOpened(CameraDevice camera) {
- mCameraDevice = camera;
- postUpdateFlashlight();
- }
-
- @Override
- public void onDisconnected(CameraDevice camera) {
- if (mCameraDevice == camera) {
- dispatchOff();
- teardown();
- }
- }
-
- @Override
- public void onError(CameraDevice camera, int error) {
- Log.e(TAG, "Camera error: camera=" + camera + " error=" + error);
- if (camera == mCameraDevice || mCameraDevice == null) {
- handleError();
- }
- }
- };
+ private final CameraManager.TorchCallback mTorchCallback =
+ new CameraManager.TorchCallback() {
- private final CameraCaptureSession.StateListener mSessionListener =
- new CameraCaptureSession.StateListener() {
@Override
- public void onConfigured(CameraCaptureSession session) {
- if (session.getDevice() == mCameraDevice) {
- mSession = session;
- } else {
- session.close();
- }
- postUpdateFlashlight();
- }
-
- @Override
- public void onConfigureFailed(CameraCaptureSession session) {
- Log.e(TAG, "Configure failed.");
- if (mSession == null || mSession == session) {
- handleError();
- }
- }
- };
-
- private final Runnable mUpdateFlashlightRunnable = new Runnable() {
- @Override
- public void run() {
- updateFlashlight(false /* forceDisable */);
- }
- };
-
- private final Runnable mKillFlashlightRunnable = new Runnable() {
- @Override
- public void run() {
- synchronized (this) {
- mFlashlightEnabled = false;
+ public void onTorchModeUnavailable(String cameraId) {
+ if (TextUtils.equals(cameraId, mCameraId)) {
+ setCameraAvailable(false);
}
- updateFlashlight(true /* forceDisable */);
- dispatchOff();
}
- };
- private final CameraManager.AvailabilityCallback mAvailabilityCallback =
- new CameraManager.AvailabilityCallback() {
@Override
- public void onCameraAvailable(String cameraId) {
- if (DEBUG) Log.d(TAG, "onCameraAvailable(" + cameraId + ")");
- if (cameraId.equals(mCameraId)) {
+ public void onTorchModeChanged(String cameraId, boolean enabled) {
+ if (TextUtils.equals(cameraId, mCameraId)) {
setCameraAvailable(true);
- }
- }
-
- @Override
- public void onCameraUnavailable(String cameraId) {
- if (DEBUG) Log.d(TAG, "onCameraUnavailable(" + cameraId + ")");
- if (cameraId.equals(mCameraId)) {
- setCameraAvailable(false);
+ setTorchMode(enabled);
}
}
private void setCameraAvailable(boolean available) {
boolean changed;
synchronized (FlashlightController.this) {
- changed = mCameraAvailable != available;
- mCameraAvailable = available;
+ changed = mTorchAvailable != available;
+ mTorchAvailable = available;
}
if (changed) {
if (DEBUG) Log.d(TAG, "dispatchAvailabilityChanged(" + available + ")");
dispatchAvailabilityChanged(available);
}
}
+
+ private void setTorchMode(boolean enabled) {
+ boolean changed;
+ synchronized (FlashlightController.this) {
+ changed = mFlashlightEnabled != enabled;
+ mFlashlightEnabled = enabled;
+ }
+ if (changed) {
+ if (DEBUG) Log.d(TAG, "dispatchModeChanged(" + enabled + ")");
+ dispatchModeChanged(enabled);
+ }
+ }
};
public interface FlashlightListener {
/**
- * Called when the flashlight turns off unexpectedly.
+ * Called when the flashlight was turned off or on.
+ * @param enabled true if the flashlight is currently turned on.
*/
- void onFlashlightOff();
+ void onFlashlightChanged(boolean enabled);
+
/**
* Called when there is an error that turns the flashlight off.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
new file mode 100644
index 0000000..63f5711
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -0,0 +1,631 @@
+/*
+ * 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.statusbar.policy;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.util.ArrayMap;
+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.phone.PhoneStatusBar;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Stack;
+import java.util.TreeSet;
+
+/**
+ * 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 {
+ 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 final int mHeadsUpNotificationDecay;
+ private final int mMinimumDisplayTime;
+
+ 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();
+ private final Pools.Pool<HeadsUpEntry> mEntryPool = new Pools.Pool<HeadsUpEntry>() {
+
+ private Stack<HeadsUpEntry> mPoolObjects = new Stack<>();
+
+ @Override
+ public HeadsUpEntry acquire() {
+ if (!mPoolObjects.isEmpty()) {
+ return mPoolObjects.pop();
+ }
+ return new HeadsUpEntry();
+ }
+
+ @Override
+ public boolean release(HeadsUpEntry instance) {
+ instance.reset();
+ mPoolObjects.push(instance);
+ return true;
+ }
+ };
+
+ private final View mStatusBarWindowView;
+ private final int mStatusBarHeight;
+ private final int mNotificationsTopPadding;
+ private final Context mContext;
+ private PhoneStatusBar mBar;
+ private int mSnoozeLengthMs;
+ private ContentObserver mSettingsObserver;
+ private HashMap<String, HeadsUpEntry> mHeadsUpEntries = new HashMap<>();
+ private TreeSet<HeadsUpEntry> mSortedEntries = new TreeSet<>();
+ 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 boolean mIsExpanded;
+ private boolean mHasPinnedNotification;
+ private int[] mTmpTwoArray = new int[2];
+ private boolean mHeadsUpGoingAway;
+ private boolean mWaitingOnCollapseWhenGoingAway;
+ private boolean mIsObserving;
+
+ public HeadsUpManager(final Context context, View statusBarWindowView) {
+ 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;
+ mMinimumDisplayTime = resources.getInteger(R.integer.heads_up_notification_minimum_time);
+ mHeadsUpNotificationDecay = resources.getInteger(R.integer.heads_up_notification_decay);
+ mClock = new Clock();
+
+ mSnoozeLengthMs = Settings.Global.getInt(context.getContentResolver(),
+ SETTING_HEADS_UP_SNOOZE_LENGTH_MS, mDefaultSnoozeLengthMs);
+ mSettingsObserver = new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ final int packageSnoozeLengthMs = Settings.Global.getInt(
+ context.getContentResolver(), SETTING_HEADS_UP_SNOOZE_LENGTH_MS, -1);
+ if (packageSnoozeLengthMs > -1 && packageSnoozeLengthMs != mSnoozeLengthMs) {
+ mSnoozeLengthMs = packageSnoozeLengthMs;
+ if (DEBUG) Log.v(TAG, "mSnoozeLengthMs = " + mSnoozeLengthMs);
+ }
+ }
+ };
+ context.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(SETTING_HEADS_UP_SNOOZE_LENGTH_MS), false,
+ mSettingsObserver);
+ mStatusBarWindowView = statusBarWindowView;
+ mStatusBarHeight = resources.getDimensionPixelSize(
+ com.android.internal.R.dimen.status_bar_height);
+ mNotificationsTopPadding = context.getResources()
+ .getDimensionPixelSize(R.dimen.notifications_top_padding);
+ }
+
+ 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;
+ }
+
+ public void setBar(PhoneStatusBar bar) {
+ mBar = bar;
+ }
+
+ public void addListener(OnHeadsUpChangedListener listener) {
+ mListeners.add(listener);
+ }
+
+ public PhoneStatusBar getBar() {
+ return mBar;
+ }
+
+ /**
+ * Called when posting a new notification to the heads up.
+ */
+ public void showNotification(NotificationData.Entry headsUp) {
+ if (DEBUG) Log.v(TAG, "showNotification");
+ MetricsLogger.count(mContext, "note_peek", 1);
+ addHeadsUpEntry(headsUp);
+ updateNotification(headsUp, true);
+ headsUp.setInterruption();
+ }
+
+ /**
+ * Called when updating or posting a notification to the heads up.
+ */
+ public void updateNotification(NotificationData.Entry headsUp, boolean alert) {
+ if (DEBUG) Log.v(TAG, "updateNotification");
+
+ headsUp.row.setChildrenExpanded(false /* expanded */, false /* animated */);
+ headsUp.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+
+ if (alert) {
+ HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(headsUp.key);
+ headsUpEntry.updateEntry();
+ setEntryPinned(headsUpEntry, shouldHeadsUpBecomePinned(headsUp));
+ }
+ }
+
+ private void addHeadsUpEntry(NotificationData.Entry entry) {
+ HeadsUpEntry headsUpEntry = mEntryPool.acquire();
+
+ // This will also add the entry to the sortedList
+ headsUpEntry.setEntry(entry);
+ mHeadsUpEntries.put(entry.key, headsUpEntry);
+ entry.row.setHeadsUp(true);
+ setEntryPinned(headsUpEntry, shouldHeadsUpBecomePinned(entry));
+ for (OnHeadsUpChangedListener listener : mListeners) {
+ listener.onHeadsUpStateChanged(entry, true);
+ }
+ entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+ }
+
+ private boolean shouldHeadsUpBecomePinned(NotificationData.Entry entry) {
+ return !mIsExpanded || hasFullScreenIntent(entry);
+ }
+
+ private boolean hasFullScreenIntent(NotificationData.Entry entry) {
+ return entry.notification.getNotification().fullScreenIntent != null;
+ }
+
+ private void setEntryPinned(HeadsUpEntry headsUpEntry, boolean isPinned) {
+ ExpandableNotificationRow row = headsUpEntry.entry.row;
+ if (row.isPinned() != isPinned) {
+ row.setPinned(isPinned);
+ updatePinnedMode();
+ for (OnHeadsUpChangedListener listener : mListeners) {
+ if (isPinned) {
+ listener.onHeadsUpPinned(row);
+ } else {
+ listener.onHeadsUpUnPinned(row);
+ }
+ }
+ }
+ }
+
+ private void removeHeadsUpEntry(NotificationData.Entry entry) {
+ HeadsUpEntry remove = mHeadsUpEntries.remove(entry.key);
+ mSortedEntries.remove(remove);
+ 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);
+ }
+
+ private void updatePinnedMode() {
+ boolean hasPinnedNotification = hasPinnedNotificationInternal();
+ if (hasPinnedNotification == mHasPinnedNotification) {
+ return;
+ }
+ mHasPinnedNotification = hasPinnedNotification;
+ updateTouchableRegionListener();
+ for (OnHeadsUpChangedListener listener : mListeners) {
+ listener.onHeadsUpPinnedModeChanged(hasPinnedNotification);
+ }
+ }
+
+ /**
+ * 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
+ */
+ public boolean removeNotification(String key) {
+ if (DEBUG) Log.v(TAG, "remove");
+ if (wasShownLongEnough(key)) {
+ releaseImmediately(key);
+ return true;
+ } else {
+ getHeadsUpEntry(key).removeAsSoonAsPossible();
+ return false;
+ }
+ }
+
+ 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) {
+ return mHeadsUpEntries.containsKey(key);
+ }
+
+ /**
+ * Push 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);
+ }
+ }
+
+ public void releaseImmediately(String key) {
+ HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
+ if (headsUpEntry == null) {
+ return;
+ }
+ NotificationData.Entry shadeEntry = headsUpEntry.entry;
+ removeHeadsUpEntry(shadeEntry);
+ }
+
+ public boolean isSnoozed(String packageName) {
+ final String key = snoozeKey(packageName, mUser);
+ Long snoozedUntil = mSnoozedPackages.get(key);
+ if (snoozedUntil != null) {
+ if (snoozedUntil > SystemClock.elapsedRealtime()) {
+ if (DEBUG) Log.v(TAG, key + " snoozed");
+ return true;
+ }
+ mSnoozedPackages.remove(packageName);
+ }
+ return false;
+ }
+
+ 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);
+ }
+ mReleaseOnExpandFinish = true;
+ }
+
+ private static String snoozeKey(String packageName, int user) {
+ return user + "," + packageName;
+ }
+
+ private HeadsUpEntry getHeadsUpEntry(String key) {
+ return mHeadsUpEntries.get(key);
+ }
+
+ public NotificationData.Entry getEntry(String key) {
+ return mHeadsUpEntries.get(key).entry;
+ }
+
+ public TreeSet<HeadsUpEntry> getSortedEntries() {
+ return mSortedEntries;
+ }
+
+ public HeadsUpEntry getTopEntry() {
+ return mSortedEntries.isEmpty() ? null : mSortedEntries.first();
+ }
+
+ /**
+ * 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(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) {
+ // The touchable region is always the full area when expanded
+ return;
+ }
+ if (mHasPinnedNotification) {
+ int minX = Integer.MAX_VALUE;
+ int maxX = 0;
+ int minY = Integer.MAX_VALUE;
+ int maxY = 0;
+ for (HeadsUpEntry entry : mSortedEntries) {
+ ExpandableNotificationRow row = entry.entry.row;
+ if (row.isPinned()) {
+ row.getLocationOnScreen(mTmpTwoArray);
+ minX = Math.min(minX, mTmpTwoArray[0]);
+ minY = Math.min(minY, 0);
+ maxX = Math.max(maxX, mTmpTwoArray[0] + row.getWidth());
+ maxY = Math.max(maxY, row.getHeadsUpHeight());
+ }
+ }
+
+ info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+ info.touchableRegion.set(minX, minY, maxX, maxY + mNotificationsTopPadding);
+ } 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) {
+ pw.println("HeadsUpManager state:");
+ pw.print(" mTouchAcceptanceDelay="); pw.println(mTouchAcceptanceDelay);
+ pw.print(" mSnoozeLengthMs="); pw.println(mSnoozeLengthMs);
+ pw.print(" now="); pw.println(SystemClock.elapsedRealtime());
+ pw.print(" mUser="); pw.println(mUser);
+ for (HeadsUpEntry entry: mSortedEntries) {
+ pw.print(" HeadsUpEntry="); pw.println(entry.entry);
+ }
+ int N = mSnoozedPackages.size();
+ pw.println(" snoozed packages: " + N);
+ for (int i = 0; i < N; i++) {
+ pw.print(" "); pw.print(mSnoozedPackages.valueAt(i));
+ pw.print(", "); pw.println(mSnoozedPackages.keyAt(i));
+ }
+ }
+
+ public boolean hasPinnedHeadsUp() {
+ return mHasPinnedNotification;
+ }
+
+ private boolean hasPinnedNotificationInternal() {
+ for (String key : mHeadsUpEntries.keySet()) {
+ HeadsUpEntry entry = mHeadsUpEntries.get(key);
+ if (entry.entry.row.isPinned()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Notifies that a notification was swiped out and will be removed.
+ *
+ * @param key the notification key
+ */
+ public void addSwipedOutNotification(String key) {
+ mSwipedOutKeys.add(key);
+ }
+
+ public void unpinAll() {
+ for (String key : mHeadsUpEntries.keySet()) {
+ HeadsUpEntry entry = mHeadsUpEntries.get(key);
+ setEntryPinned(entry, false /* isPinned */);
+ }
+ }
+
+ public void onExpandingFinished() {
+ if (mReleaseOnExpandFinish) {
+ releaseAllImmediately();
+ mReleaseOnExpandFinish = false;
+ } else {
+ for (NotificationData.Entry entry : mEntriesToRemoveAfterExpand) {
+ removeHeadsUpEntry(entry);
+ }
+ mEntriesToRemoveAfterExpand.clear();
+ }
+ }
+
+ public void setTrackingHeadsUp(boolean trackingHeadsUp) {
+ mTrackingHeadsUp = trackingHeadsUp;
+ }
+
+ public void setIsExpanded(boolean isExpanded) {
+ if (isExpanded != mIsExpanded) {
+ mIsExpanded = isExpanded;
+ if (isExpanded) {
+ // make sure our state is sane
+ mWaitingOnCollapseWhenGoingAway = false;
+ mHeadsUpGoingAway = false;
+ updateTouchableRegionListener();
+ }
+ }
+ }
+
+ public int getTopHeadsUpHeight() {
+ HeadsUpEntry topEntry = getTopEntry();
+ return topEntry != null ? topEntry.entry.row.getHeadsUpHeight() : 0;
+ }
+
+ /**
+ * Compare two entries and decide how they should be ranked.
+ *
+ * @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) {
+ HeadsUpEntry aEntry = getHeadsUpEntry(a.key);
+ HeadsUpEntry bEntry = getHeadsUpEntry(b.key);
+ if (aEntry == null || bEntry == null) {
+ return aEntry == null ? 1 : -1;
+ }
+ return aEntry.compareTo(bEntry);
+ }
+
+ /**
+ * 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();
+ }
+ }
+ });
+ }
+
+ /**
+ * 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;
+ public long postTime;
+ public long earliestRemovaltime;
+ private Runnable mRemoveHeadsUpRunnable;
+
+ public void setEntry(final NotificationData.Entry entry) {
+ this.entry = entry;
+
+ // 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 (!mTrackingHeadsUp) {
+ removeHeadsUpEntry(entry);
+ } else {
+ mEntriesToRemoveAfterExpand.add(entry);
+ }
+ }
+ };
+ updateEntry();
+ }
+
+ public void updateEntry() {
+ mSortedEntries.remove(HeadsUpEntry.this);
+ long currentTime = mClock.currentTimeMillis();
+ earliestRemovaltime = currentTime + mMinimumDisplayTime;
+ postTime = Math.max(postTime, currentTime);
+ removeAutoRemovalCallbacks();
+ if (!hasFullScreenIntent(entry)) {
+ long finishTime = postTime + mHeadsUpNotificationDecay;
+ long removeDelay = Math.max(finishTime - currentTime, mMinimumDisplayTime);
+ mHandler.postDelayed(mRemoveHeadsUpRunnable, removeDelay);
+ }
+ mSortedEntries.add(HeadsUpEntry.this);
+ }
+
+ @Override
+ public int compareTo(HeadsUpEntry o) {
+ return postTime < o.postTime ? 1
+ : postTime == o.postTime ? entry.key.compareTo(o.entry.key)
+ : -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 reset() {
+ removeAutoRemovalCallbacks();
+ entry = null;
+ mRemoveHeadsUpRunnable = null;
+ }
+ }
+
+ public static class Clock {
+ public long currentTimeMillis() {
+ return SystemClock.elapsedRealtime();
+ }
+ }
+
+ public interface OnHeadsUpChangedListener {
+ /**
+ * The state whether there exist pinned heads-ups or not changed.
+ *
+ * @param inPinnedMode whether there are any pinned heads-ups
+ */
+ void onHeadsUpPinnedModeChanged(boolean inPinnedMode);
+
+ /**
+ * A notification was just pinned to the top.
+ */
+ void onHeadsUpPinned(ExpandableNotificationRow headsUp);
+
+ /**
+ * A notification was just unpinned from the top.
+ */
+ void onHeadsUpUnPinned(ExpandableNotificationRow headsUp);
+
+ /**
+ * A notification just became a heads up or turned back to its normal state.
+ *
+ * @param entry the entry of the changed notification
+ * @param isHeadsUp whether the notification is now a headsUp notification
+ */
+ void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java
deleted file mode 100644
index 2e3e67a..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java
+++ /dev/null
@@ -1,495 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.database.ContentObserver;
-import android.graphics.Outline;
-import android.graphics.Rect;
-import android.os.SystemClock;
-import android.provider.Settings;
-import android.util.ArrayMap;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.view.ViewOutlineProvider;
-import android.view.ViewTreeObserver;
-import android.view.accessibility.AccessibilityEvent;
-import android.widget.FrameLayout;
-
-import com.android.systemui.ExpandHelper;
-import com.android.systemui.Gefingerpoken;
-import com.android.systemui.R;
-import com.android.systemui.SwipeHelper;
-import com.android.systemui.statusbar.ExpandableView;
-import com.android.systemui.statusbar.NotificationData;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
-
-import java.util.ArrayList;
-
-public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper.Callback, ExpandHelper.Callback,
- ViewTreeObserver.OnComputeInternalInsetsListener {
- private static final String TAG = "HeadsUpNotificationView";
- private static final boolean DEBUG = false;
- private static final boolean SPEW = DEBUG;
- private static final String SETTING_HEADS_UP_SNOOZE_LENGTH_MS = "heads_up_snooze_length_ms";
-
- Rect mTmpRect = new Rect();
- int[] mTmpTwoArray = new int[2];
-
- private final int mTouchSensitivityDelay;
- private final float mMaxAlpha = 1f;
- private final ArrayMap<String, Long> mSnoozedPackages;
- private final int mDefaultSnoozeLengthMs;
-
- private SwipeHelper mSwipeHelper;
- private EdgeSwipeHelper mEdgeSwipeHelper;
-
- private PhoneStatusBar mBar;
-
- private long mStartTouchTime;
- private ViewGroup mContentHolder;
- private int mSnoozeLengthMs;
- private ContentObserver mSettingsObserver;
-
- private NotificationData.Entry mHeadsUp;
- private int mUser;
- private String mMostRecentPackageName;
-
- public HeadsUpNotificationView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public HeadsUpNotificationView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- Resources resources = context.getResources();
- mTouchSensitivityDelay = resources.getInteger(R.integer.heads_up_sensitivity_delay);
- if (DEBUG) Log.v(TAG, "create() " + mTouchSensitivityDelay);
- mSnoozedPackages = new ArrayMap<>();
- mDefaultSnoozeLengthMs = resources.getInteger(R.integer.heads_up_default_snooze_length_ms);
- mSnoozeLengthMs = mDefaultSnoozeLengthMs;
- }
-
- public void updateResources() {
- if (mContentHolder != null) {
- final LayoutParams lp = (LayoutParams) mContentHolder.getLayoutParams();
- lp.width = getResources().getDimensionPixelSize(R.dimen.notification_panel_width);
- lp.gravity = getResources().getInteger(R.integer.notification_panel_layout_gravity);
- mContentHolder.setLayoutParams(lp);
- }
- }
-
- public void setBar(PhoneStatusBar bar) {
- mBar = bar;
- }
-
- public ViewGroup getHolder() {
- return mContentHolder;
- }
-
- public boolean showNotification(NotificationData.Entry headsUp) {
- if (mHeadsUp != null && headsUp != null && !mHeadsUp.key.equals(headsUp.key)) {
- // bump any previous heads up back to the shade
- release();
- }
-
- mHeadsUp = headsUp;
- if (mContentHolder != null) {
- mContentHolder.removeAllViews();
- }
-
- if (mHeadsUp != null) {
- mMostRecentPackageName = mHeadsUp.notification.getPackageName();
- mHeadsUp.row.setSystemExpanded(true);
- mHeadsUp.row.setSensitive(false);
- mHeadsUp.row.setHeadsUp(true);
- mHeadsUp.row.setHideSensitive(
- false, false /* animated */, 0 /* delay */, 0 /* duration */);
- if (mContentHolder == null) {
- // too soon!
- return false;
- }
- mContentHolder.setX(0);
- mContentHolder.setVisibility(View.VISIBLE);
- mContentHolder.setAlpha(mMaxAlpha);
- mContentHolder.addView(mHeadsUp.row);
- sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
-
- mSwipeHelper.snapChild(mContentHolder, 1f);
- mStartTouchTime = SystemClock.elapsedRealtime() + mTouchSensitivityDelay;
-
- mHeadsUp.setInterruption();
-
- // 2. Animate mHeadsUpNotificationView in
- mBar.scheduleHeadsUpOpen();
-
- // 3. Set alarm to age the notification off
- mBar.resetHeadsUpDecayTimer();
- }
- return true;
- }
-
- @Override
- protected void onVisibilityChanged(View changedView, int visibility) {
- super.onVisibilityChanged(changedView, visibility);
- if (changedView.getVisibility() == VISIBLE) {
- sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
- }
- }
-
- public boolean isShowing(String key) {
- return mHeadsUp != null && mHeadsUp.key.equals(key);
- }
-
- /** Discard the Heads Up notification. */
- public void clear() {
- mHeadsUp = null;
- mBar.scheduleHeadsUpClose();
- }
-
- /** Respond to dismissal of the Heads Up window. */
- public void dismiss() {
- if (mHeadsUp == null) return;
- if (mHeadsUp.notification.isClearable()) {
- mBar.onNotificationClear(mHeadsUp.notification);
- } else {
- release();
- }
- mHeadsUp = null;
- mBar.scheduleHeadsUpClose();
- }
-
- /** Push any current Heads Up notification down into the shade. */
- public void release() {
- if (mHeadsUp != null) {
- mBar.displayNotificationFromHeadsUp(mHeadsUp.notification);
- }
- mHeadsUp = null;
- }
-
- public boolean isSnoozed(String packageName) {
- final String key = snoozeKey(packageName, mUser);
- Long snoozedUntil = mSnoozedPackages.get(key);
- if (snoozedUntil != null) {
- if (snoozedUntil > SystemClock.elapsedRealtime()) {
- if (DEBUG) Log.v(TAG, key + " snoozed");
- return true;
- }
- mSnoozedPackages.remove(packageName);
- }
- return false;
- }
-
- private void snooze() {
- if (mMostRecentPackageName != null) {
- mSnoozedPackages.put(snoozeKey(mMostRecentPackageName, mUser),
- SystemClock.elapsedRealtime() + mSnoozeLengthMs);
- }
- releaseAndClose();
- }
-
- private static String snoozeKey(String packageName, int user) {
- return user + "," + packageName;
- }
-
- public void releaseAndClose() {
- release();
- mBar.scheduleHeadsUpClose();
- }
-
- public NotificationData.Entry getEntry() {
- return mHeadsUp;
- }
-
- public boolean isClearable() {
- return mHeadsUp == null || mHeadsUp.notification.isClearable();
- }
-
- // ViewGroup methods
-
- private static final ViewOutlineProvider CONTENT_HOLDER_OUTLINE_PROVIDER =
- new ViewOutlineProvider() {
- @Override
- public void getOutline(View view, Outline outline) {
- int outlineLeft = view.getPaddingLeft();
- int outlineTop = view.getPaddingTop();
-
- // Apply padding to shadow.
- outline.setRect(outlineLeft, outlineTop,
- view.getWidth() - outlineLeft - view.getPaddingRight(),
- view.getHeight() - outlineTop - view.getPaddingBottom());
- }
- };
-
- @Override
- public void onAttachedToWindow() {
- final ViewConfiguration viewConfiguration = ViewConfiguration.get(getContext());
- float touchSlop = viewConfiguration.getScaledTouchSlop();
- mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, getContext());
- mSwipeHelper.setMaxSwipeProgress(mMaxAlpha);
- mEdgeSwipeHelper = new EdgeSwipeHelper(touchSlop);
-
- int minHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
- int maxHeight = getResources().getDimensionPixelSize(R.dimen.notification_max_height);
-
- mContentHolder = (ViewGroup) findViewById(R.id.content_holder);
- mContentHolder.setOutlineProvider(CONTENT_HOLDER_OUTLINE_PROVIDER);
-
- mSnoozeLengthMs = Settings.Global.getInt(mContext.getContentResolver(),
- SETTING_HEADS_UP_SNOOZE_LENGTH_MS, mDefaultSnoozeLengthMs);
- mSettingsObserver = new ContentObserver(getHandler()) {
- @Override
- public void onChange(boolean selfChange) {
- final int packageSnoozeLengthMs = Settings.Global.getInt(
- mContext.getContentResolver(), SETTING_HEADS_UP_SNOOZE_LENGTH_MS, -1);
- if (packageSnoozeLengthMs > -1 && packageSnoozeLengthMs != mSnoozeLengthMs) {
- mSnoozeLengthMs = packageSnoozeLengthMs;
- if (DEBUG) Log.v(TAG, "mSnoozeLengthMs = " + mSnoozeLengthMs);
- }
- }
- };
- mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(SETTING_HEADS_UP_SNOOZE_LENGTH_MS), false,
- mSettingsObserver);
- if (DEBUG) Log.v(TAG, "mSnoozeLengthMs = " + mSnoozeLengthMs);
-
- if (mHeadsUp != null) {
- // whoops, we're on already!
- showNotification(mHeadsUp);
- }
-
- getViewTreeObserver().addOnComputeInternalInsetsListener(this);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- mContext.getContentResolver().unregisterContentObserver(mSettingsObserver);
- }
-
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- if (DEBUG) Log.v(TAG, "onInterceptTouchEvent()");
- if (SystemClock.elapsedRealtime() < mStartTouchTime) {
- return true;
- }
- return mEdgeSwipeHelper.onInterceptTouchEvent(ev)
- || mSwipeHelper.onInterceptTouchEvent(ev)
- || super.onInterceptTouchEvent(ev);
- }
-
- // View methods
-
- @Override
- public void onDraw(android.graphics.Canvas c) {
- super.onDraw(c);
- if (DEBUG) {
- //Log.d(TAG, "onDraw: canvas height: " + c.getHeight() + "px; measured height: "
- // + getMeasuredHeight() + "px");
- c.save();
- c.clipRect(6, 6, c.getWidth() - 6, getMeasuredHeight() - 6,
- android.graphics.Region.Op.DIFFERENCE);
- c.drawColor(0xFFcc00cc);
- c.restore();
- }
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent ev) {
- if (SystemClock.elapsedRealtime() < mStartTouchTime) {
- return false;
- }
- mBar.resetHeadsUpDecayTimer();
- return mEdgeSwipeHelper.onTouchEvent(ev)
- || mSwipeHelper.onTouchEvent(ev)
- || super.onTouchEvent(ev);
- }
-
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- float densityScale = getResources().getDisplayMetrics().density;
- mSwipeHelper.setDensityScale(densityScale);
- float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
- mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
- }
-
- // ExpandHelper.Callback methods
-
- @Override
- public ExpandableView getChildAtRawPosition(float x, float y) {
- return getChildAtPosition(x, y);
- }
-
- @Override
- public ExpandableView getChildAtPosition(float x, float y) {
- return mHeadsUp == null ? null : mHeadsUp.row;
- }
-
- @Override
- public boolean canChildBeExpanded(View v) {
- return mHeadsUp != null && mHeadsUp.row == v && mHeadsUp.row.isExpandable();
- }
-
- @Override
- public void setUserExpandedChild(View v, boolean userExpanded) {
- if (mHeadsUp != null && mHeadsUp.row == v) {
- mHeadsUp.row.setUserExpanded(userExpanded);
- }
- }
-
- @Override
- public void setUserLockedChild(View v, boolean userLocked) {
- if (mHeadsUp != null && mHeadsUp.row == v) {
- mHeadsUp.row.setUserLocked(userLocked);
- }
- }
-
- @Override
- public void expansionStateChanged(boolean isExpanding) {
-
- }
-
- // SwipeHelper.Callback methods
-
- @Override
- public boolean canChildBeDismissed(View v) {
- return true;
- }
-
- @Override
- public boolean isAntiFalsingNeeded() {
- return false;
- }
-
- @Override
- public float getFalsingThresholdFactor() {
- return 1.0f;
- }
-
- @Override
- public void onChildDismissed(View v) {
- Log.v(TAG, "User swiped heads up to dismiss");
- mBar.onHeadsUpDismissed();
- }
-
- @Override
- public void onBeginDrag(View v) {
- }
-
- @Override
- public void onDragCancelled(View v) {
- mContentHolder.setAlpha(mMaxAlpha); // sometimes this isn't quite reset
- }
-
- @Override
- public void onChildSnappedBack(View animView) {
- }
-
- @Override
- public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) {
- getBackground().setAlpha((int) (255 * swipeProgress));
- return false;
- }
-
- @Override
- public View getChildAtPosition(MotionEvent ev) {
- return mContentHolder;
- }
-
- @Override
- public View getChildContentView(View v) {
- return mContentHolder;
- }
-
- @Override
- public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) {
- mContentHolder.getLocationOnScreen(mTmpTwoArray);
-
- info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
- info.touchableRegion.set(mTmpTwoArray[0], mTmpTwoArray[1],
- mTmpTwoArray[0] + mContentHolder.getWidth(),
- mTmpTwoArray[1] + mContentHolder.getHeight());
- }
-
- public void escalate() {
- mBar.scheduleHeadsUpEscalation();
- }
-
- public String getKey() {
- return mHeadsUp == null ? null : mHeadsUp.notification.getKey();
- }
-
- public void setUser(int user) {
- mUser = user;
- }
-
- private class EdgeSwipeHelper implements Gefingerpoken {
- private static final boolean DEBUG_EDGE_SWIPE = false;
- private final float mTouchSlop;
- private boolean mConsuming;
- private float mFirstY;
- private float mFirstX;
-
- public EdgeSwipeHelper(float touchSlop) {
- mTouchSlop = touchSlop;
- }
-
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- switch (ev.getActionMasked()) {
- case MotionEvent.ACTION_DOWN:
- if (DEBUG_EDGE_SWIPE) Log.d(TAG, "action down " + ev.getY());
- mFirstX = ev.getX();
- mFirstY = ev.getY();
- mConsuming = false;
- break;
-
- case MotionEvent.ACTION_MOVE:
- if (DEBUG_EDGE_SWIPE) Log.d(TAG, "action move " + ev.getY());
- final float dY = ev.getY() - mFirstY;
- final float daX = Math.abs(ev.getX() - mFirstX);
- final float daY = Math.abs(dY);
- if (!mConsuming && daX < daY && daY > mTouchSlop) {
- snooze();
- if (dY > 0) {
- if (DEBUG_EDGE_SWIPE) Log.d(TAG, "found an open");
- mBar.animateExpandNotificationsPanel();
- }
- mConsuming = true;
- }
- break;
-
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- if (DEBUG_EDGE_SWIPE) Log.d(TAG, "action done" );
- mConsuming = false;
- break;
- }
- return mConsuming;
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent ev) {
- return mConsuming;
- }
- }
-}
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 0863c86..7ca91a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java
@@ -22,7 +22,6 @@ public interface HotspotController {
boolean isHotspotEnabled();
boolean isHotspotSupported();
void setHotspotEnabled(boolean enabled);
- boolean isProvisioningNeeded();
public interface Callback {
void onHotspotChanged(boolean enabled);
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 5eff5a6..4bfd528 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
@@ -16,45 +16,38 @@
package com.android.systemui.statusbar.policy;
-import android.app.ActivityManager;
import android.content.BroadcastReceiver;
-import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.net.ConnectivityManager;
import android.net.wifi.WifiManager;
-import android.os.SystemProperties;
import android.os.UserHandle;
-import android.provider.Settings;
import android.util.Log;
+import com.android.settingslib.TetherUtil;
+
import java.util.ArrayList;
public class HotspotControllerImpl implements HotspotController {
private static final String TAG = "HotspotController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- // Keep these in sync with Settings TetherService.java
- public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType";
- public static final String EXTRA_SET_ALARM = "extraSetAlarm";
- public static final String EXTRA_RUN_PROVISION = "extraRunProvision";
- public static final String EXTRA_ENABLE_WIFI_TETHER = "extraEnableWifiTether";
- // Keep this in sync with Settings TetherSettings.java
- public static final int WIFI_TETHERING = 0;
+ private static final Intent TETHER_SERVICE_INTENT = new Intent()
+ .putExtra(TetherUtil.EXTRA_ADD_TETHER_TYPE, TetherUtil.TETHERING_WIFI)
+ .putExtra(TetherUtil.EXTRA_SET_ALARM, true)
+ .putExtra(TetherUtil.EXTRA_RUN_PROVISION, true)
+ .putExtra(TetherUtil.EXTRA_ENABLE_WIFI_TETHER, true)
+ .setComponent(TetherUtil.TETHER_SERVICE);
private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
private final Receiver mReceiver = new Receiver();
private final Context mContext;
private final WifiManager mWifiManager;
- private final ConnectivityManager mConnectivityManager;
public HotspotControllerImpl(Context context) {
mContext = context;
mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
- mConnectivityManager = (ConnectivityManager)
- mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
}
public void addCallback(Callback callback) {
@@ -78,54 +71,17 @@ public class HotspotControllerImpl implements HotspotController {
@Override
public boolean isHotspotSupported() {
- final boolean isSecondaryUser = ActivityManager.getCurrentUser() != UserHandle.USER_OWNER;
- return !isSecondaryUser && mConnectivityManager.isTetheringSupported();
- }
-
- @Override
- public boolean isProvisioningNeeded() {
- // Keep in sync with other usage of config_mobile_hotspot_provision_app.
- // TetherSettings#isProvisioningNeeded and
- // ConnectivityManager#enforceTetherChangePermission
- String[] provisionApp = mContext.getResources().getStringArray(
- com.android.internal.R.array.config_mobile_hotspot_provision_app);
- if (SystemProperties.getBoolean("net.tethering.noprovisioning", false)
- || provisionApp == null) {
- return false;
- }
- return (provisionApp.length == 2);
+ return TetherUtil.isTetheringSupported(mContext);
}
@Override
public void setHotspotEnabled(boolean enabled) {
final ContentResolver cr = mContext.getContentResolver();
// Call provisioning app which is called when enabling Tethering from Settings
- if (enabled) {
- if (isProvisioningNeeded()) {
- String tetherEnable = mContext.getResources().getString(
- com.android.internal.R.string.config_wifi_tether_enable);
- Intent intent = new Intent();
- intent.putExtra(EXTRA_ADD_TETHER_TYPE, WIFI_TETHERING);
- intent.putExtra(EXTRA_SET_ALARM, true);
- intent.putExtra(EXTRA_RUN_PROVISION, true);
- intent.putExtra(EXTRA_ENABLE_WIFI_TETHER, true);
- intent.setComponent(ComponentName.unflattenFromString(tetherEnable));
- mContext.startServiceAsUser(intent, UserHandle.CURRENT);
- } else {
- int wifiState = mWifiManager.getWifiState();
- if ((wifiState == WifiManager.WIFI_STATE_ENABLING) ||
- (wifiState == WifiManager.WIFI_STATE_ENABLED)) {
- mWifiManager.setWifiEnabled(false);
- Settings.Global.putInt(cr, Settings.Global.WIFI_SAVED_STATE, 1);
- }
- mWifiManager.setWifiApEnabled(null, true);
- }
+ if (enabled && TetherUtil.isProvisioningNeeded(mContext)) {
+ mContext.startServiceAsUser(TETHER_SERVICE_INTENT, UserHandle.CURRENT);
} else {
- mWifiManager.setWifiApEnabled(null, false);
- if (Settings.Global.getInt(cr, Settings.Global.WIFI_SAVED_STATE, 0) == 1) {
- mWifiManager.setWifiEnabled(true);
- Settings.Global.putInt(cr, Settings.Global.WIFI_SAVED_STATE, 0);
- }
+ TetherUtil.setWifiTethering(enabled, mContext);
}
}
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 6998791..3f63b5f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java
@@ -26,7 +26,7 @@ import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
-import android.view.HardwareCanvas;
+import android.view.DisplayListCanvas;
import android.view.RenderNodeAnimator;
import android.view.View;
import android.view.animation.Interpolator;
@@ -106,7 +106,7 @@ public class KeyButtonRipple extends Drawable {
public void draw(Canvas canvas) {
mSupportHardware = canvas.isHardwareAccelerated();
if (mSupportHardware) {
- drawHardware((HardwareCanvas) canvas);
+ drawHardware((DisplayListCanvas) canvas);
} else {
drawSoftware(canvas);
}
@@ -118,7 +118,7 @@ public class KeyButtonRipple extends Drawable {
}
@Override
- public void setColorFilter(ColorFilter cf) {
+ public void setColorFilter(ColorFilter colorFilter) {
// Not supported.
}
@@ -131,7 +131,7 @@ public class KeyButtonRipple extends Drawable {
return getBounds().width() > getBounds().height();
}
- private void drawHardware(HardwareCanvas c) {
+ private void drawHardware(DisplayListCanvas c) {
if (mDrawingHardwareGlow) {
c.drawRoundRect(mLeftProp, mTopProp, mRightProp, mBottomProp, mRxProp, mRyProp,
mPaintProp);
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 b9cc0f9..4c99792 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
@@ -16,8 +16,6 @@
package com.android.systemui.statusbar.policy;
-import android.animation.Animator;
-import android.animation.ObjectAnimator;
import android.app.ActivityManager;
import android.content.Context;
import android.content.res.TypedArray;
@@ -26,7 +24,6 @@ import android.media.AudioManager;
import android.os.Bundle;
import android.os.SystemClock;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.HapticFeedbackConstants;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
@@ -45,20 +42,13 @@ import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK;
public class KeyButtonView extends ImageView {
- private static final String TAG = "StatusBar.KeyButtonView";
- private static final boolean DEBUG = false;
-
- // TODO: Get rid of this
- public static final float DEFAULT_QUIESCENT_ALPHA = 1f;
private long mDownTime;
private int mCode;
private int mTouchSlop;
- private float mDrawingAlpha = 1f;
- private float mQuiescentAlpha = DEFAULT_QUIESCENT_ALPHA;
private boolean mSupportsLongpress = true;
private AudioManager mAudioManager;
- private Animator mAnimateToQuiescent = new ObjectAnimator();
+ private boolean mGestureAborted;
private final Runnable mCheckLongPress = new Runnable() {
public void run() {
@@ -67,7 +57,7 @@ public class KeyButtonView extends ImageView {
if (isLongClickable()) {
// Just an old-fashioned ImageView
performLongClick();
- } else {
+ } else if (mSupportsLongpress) {
sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.FLAG_LONG_PRESS);
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
}
@@ -89,9 +79,6 @@ public class KeyButtonView extends ImageView {
mSupportsLongpress = a.getBoolean(R.styleable.KeyButtonView_keyRepeat, true);
-
- setDrawingAlpha(mQuiescentAlpha);
-
a.recycle();
setClickable(true);
@@ -105,7 +92,7 @@ public class KeyButtonView extends ImageView {
super.onInitializeAccessibilityNodeInfo(info);
if (mCode != 0) {
info.addAction(new AccessibilityNodeInfo.AccessibilityAction(ACTION_CLICK, null));
- if (mSupportsLongpress) {
+ if (mSupportsLongpress || isLongClickable()) {
info.addAction(
new AccessibilityNodeInfo.AccessibilityAction(ACTION_LONG_CLICK, null));
}
@@ -121,60 +108,34 @@ public class KeyButtonView extends ImageView {
}
@Override
- public boolean performAccessibilityAction(int action, Bundle arguments) {
+ public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
if (action == ACTION_CLICK && mCode != 0) {
sendEvent(KeyEvent.ACTION_DOWN, 0, SystemClock.uptimeMillis());
sendEvent(KeyEvent.ACTION_UP, 0);
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
playSoundEffect(SoundEffectConstants.CLICK);
return true;
- } else if (action == ACTION_LONG_CLICK && mCode != 0 && mSupportsLongpress) {
+ } else if (action == ACTION_LONG_CLICK && mCode != 0) {
sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.FLAG_LONG_PRESS);
sendEvent(KeyEvent.ACTION_UP, 0);
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
return true;
}
- return super.performAccessibilityAction(action, arguments);
- }
-
- public void setQuiescentAlpha(float alpha, boolean animate) {
- mAnimateToQuiescent.cancel();
- alpha = Math.min(Math.max(alpha, 0), 1);
- if (alpha == mQuiescentAlpha && alpha == mDrawingAlpha) return;
- mQuiescentAlpha = alpha;
- if (DEBUG) Log.d(TAG, "New quiescent alpha = " + mQuiescentAlpha);
- if (animate) {
- mAnimateToQuiescent = animateToQuiescent();
- mAnimateToQuiescent.start();
- } else {
- setDrawingAlpha(mQuiescentAlpha);
- }
- }
-
- private ObjectAnimator animateToQuiescent() {
- return ObjectAnimator.ofFloat(this, "drawingAlpha", mQuiescentAlpha);
- }
-
- public float getQuiescentAlpha() {
- return mQuiescentAlpha;
- }
-
- public float getDrawingAlpha() {
- return mDrawingAlpha;
- }
-
- public void setDrawingAlpha(float x) {
- setImageAlpha((int) (x * 255));
- mDrawingAlpha = x;
+ return super.performAccessibilityActionInternal(action, arguments);
}
public boolean onTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
int x, y;
+ if (action == MotionEvent.ACTION_DOWN) {
+ mGestureAborted = false;
+ }
+ if (mGestureAborted) {
+ return false;
+ }
switch (action) {
case MotionEvent.ACTION_DOWN:
- //Log.d("KeyButtonView", "press");
mDownTime = SystemClock.uptimeMillis();
setPressed(true);
if (mCode != 0) {
@@ -183,10 +144,8 @@ public class KeyButtonView extends ImageView {
// Provide the same haptic feedback that the system offers for virtual keys.
performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
}
- if (mSupportsLongpress) {
- removeCallbacks(mCheckLongPress);
- postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout());
- }
+ removeCallbacks(mCheckLongPress);
+ postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout());
break;
case MotionEvent.ACTION_MOVE:
x = (int)ev.getX();
@@ -201,9 +160,7 @@ public class KeyButtonView extends ImageView {
if (mCode != 0) {
sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED);
}
- if (mSupportsLongpress) {
- removeCallbacks(mCheckLongPress);
- }
+ removeCallbacks(mCheckLongPress);
break;
case MotionEvent.ACTION_UP:
final boolean doIt = isPressed();
@@ -222,9 +179,7 @@ public class KeyButtonView extends ImageView {
performClick();
}
}
- if (mSupportsLongpress) {
- removeCallbacks(mCheckLongPress);
- }
+ removeCallbacks(mCheckLongPress);
break;
}
@@ -248,6 +203,11 @@ public class KeyButtonView extends ImageView {
InputManager.getInstance().injectInputEvent(ev,
InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
}
+
+ public void abortCurrentGesture() {
+ setPressed(false);
+ mGestureAborted = true;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java
index 0b3575f..d4eb553 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java
@@ -16,21 +16,59 @@
package com.android.systemui.statusbar.policy;
+import android.app.ActivityManager;
+import android.content.Context;
+
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.settings.CurrentUserTracker;
+
import java.util.ArrayList;
-public final class KeyguardMonitor {
+public final class KeyguardMonitor extends KeyguardUpdateMonitorCallback {
private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
+ private final Context mContext;
+ private final CurrentUserTracker mUserTracker;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+
+ private int mCurrentUser;
private boolean mShowing;
private boolean mSecure;
+ private boolean mTrusted;
+
+ private boolean mListening;
+
+ public KeyguardMonitor(Context context) {
+ mContext = context;
+ mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+ mUserTracker = new CurrentUserTracker(mContext) {
+ @Override
+ public void onUserSwitched(int newUserId) {
+ mCurrentUser = newUserId;
+ updateTrustedState();
+ }
+ };
+ }
public void addCallback(Callback callback) {
mCallbacks.add(callback);
+ if (mCallbacks.size() != 0 && !mListening) {
+ mListening = true;
+ mCurrentUser = ActivityManager.getCurrentUser();
+ updateTrustedState();
+ mKeyguardUpdateMonitor.registerCallback(this);
+ mUserTracker.startTracking();
+ }
}
public void removeCallback(Callback callback) {
- mCallbacks.remove(callback);
+ if (mCallbacks.remove(callback) && mCallbacks.size() == 0 && mListening) {
+ mListening = false;
+ mKeyguardUpdateMonitor.removeCallback(this);
+ mUserTracker.stopTracking();
+ }
}
public boolean isShowing() {
@@ -41,10 +79,28 @@ public final class KeyguardMonitor {
return mSecure;
}
+ public boolean isTrusted() {
+ return mTrusted;
+ }
+
public void notifyKeyguardState(boolean showing, boolean secure) {
if (mShowing == showing && mSecure == secure) return;
mShowing = showing;
mSecure = secure;
+ notifyKeyguardChanged();
+ }
+
+ @Override
+ public void onTrustChanged(int userId) {
+ updateTrustedState();
+ notifyKeyguardChanged();
+ }
+
+ private void updateTrustedState() {
+ mTrusted = mKeyguardUpdateMonitor.getUserHasTrust(mCurrentUser);
+ }
+
+ private void notifyKeyguardChanged() {
for (Callback callback : mCallbacks) {
callback.onKeyguardChanged();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
index 1460e5f..5cf6156 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
@@ -30,7 +30,7 @@ import android.view.ViewStub;
import android.view.animation.AnimationUtils;
import android.widget.FrameLayout;
-import com.android.keyguard.AppearAnimationUtils;
+import com.android.settingslib.animation.AppearAnimationUtils;
import com.android.systemui.R;
import com.android.systemui.qs.tiles.UserDetailItemView;
import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherScrim.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherScrim.java
index a5fc2fe..353e07d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherScrim.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherScrim.java
@@ -19,7 +19,6 @@ package com.android.systemui.statusbar.policy;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
-import android.graphics.LightingColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.RadialGradient;
@@ -48,7 +47,7 @@ public class KeyguardUserSwitcherScrim extends Drawable
public KeyguardUserSwitcherScrim(View host) {
host.addOnLayoutChangeListener(this);
- mDarkColor = host.getResources().getColor(
+ mDarkColor = host.getContext().getColor(
R.color.keyguard_user_switcher_background_gradient_color);
}
@@ -77,7 +76,7 @@ public class KeyguardUserSwitcherScrim extends Drawable
}
@Override
- public void setColorFilter(ColorFilter cf) {
+ public void setColorFilter(ColorFilter colorFilter) {
}
@Override
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 d8d7042..93a8fd8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
@@ -26,6 +26,8 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.location.LocationManager;
import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -56,31 +58,21 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio
private ArrayList<LocationSettingsChangeCallback> mSettingsChangeCallbacks =
new ArrayList<LocationSettingsChangeCallback>();
+ private final H mHandler = new H();
- public LocationControllerImpl(Context context) {
+ public LocationControllerImpl(Context context, Looper bgLooper) {
mContext = context;
+ // Register to listen for changes in location settings.
IntentFilter filter = new IntentFilter();
filter.addAction(LocationManager.HIGH_POWER_REQUEST_CHANGE_ACTION);
- context.registerReceiverAsUser(this, UserHandle.ALL, filter, null, null);
+ filter.addAction(LocationManager.MODE_CHANGED_ACTION);
+ context.registerReceiverAsUser(this, UserHandle.ALL, filter, null, new Handler(bgLooper));
mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
mStatusBarManager
= (StatusBarManager) context.getSystemService(Context.STATUS_BAR_SERVICE);
- // Register to listen for changes in location settings.
- IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(LocationManager.MODE_CHANGED_ACTION);
- context.registerReceiverAsUser(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (LocationManager.MODE_CHANGED_ACTION.equals(action)) {
- locationSettingsChanged();
- }
- }
- }, UserHandle.ALL, intentFilter, null, new Handler());
-
// Examine the current location state and initialize the status view.
updateActiveLocationRequests();
refreshViews();
@@ -91,7 +83,7 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio
*/
public void addSettingsChangedCallback(LocationSettingsChangeCallback cb) {
mSettingsChangeCallbacks.add(cb);
- locationSettingsChanged(cb);
+ mHandler.sendEmptyMessage(H.MSG_LOCATION_SETTINGS_CHANGED);
}
public void removeSettingsChangedCallback(LocationSettingsChangeCallback cb) {
@@ -181,8 +173,8 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio
// Updates the status view based on the current state of location requests.
private void refreshViews() {
if (mAreActiveLocationRequests) {
- mStatusBarManager.setIcon(LOCATION_STATUS_ICON_PLACEHOLDER, LOCATION_STATUS_ICON_ID, 0,
- mContext.getString(R.string.accessibility_location_active));
+ mStatusBarManager.setIcon(LOCATION_STATUS_ICON_PLACEHOLDER, LOCATION_STATUS_ICON_ID,
+ 0, mContext.getString(R.string.accessibility_location_active));
} else {
mStatusBarManager.removeIcon(LOCATION_STATUS_ICON_PLACEHOLDER);
}
@@ -197,22 +189,33 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio
}
}
- private void locationSettingsChanged() {
- boolean isEnabled = isLocationEnabled();
- for (LocationSettingsChangeCallback cb : mSettingsChangeCallbacks) {
- cb.onLocationSettingsChanged(isEnabled);
- }
- }
-
- private void locationSettingsChanged(LocationSettingsChangeCallback cb) {
- cb.onLocationSettingsChanged(isLocationEnabled());
- }
-
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (LocationManager.HIGH_POWER_REQUEST_CHANGE_ACTION.equals(action)) {
updateActiveLocationRequests();
+ } else if (LocationManager.MODE_CHANGED_ACTION.equals(action)) {
+ mHandler.sendEmptyMessage(H.MSG_LOCATION_SETTINGS_CHANGED);
+ }
+ }
+
+ private final class H extends Handler {
+ private static final int MSG_LOCATION_SETTINGS_CHANGED = 1;
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_LOCATION_SETTINGS_CHANGED:
+ locationSettingsChanged();
+ break;
+ }
+ }
+
+ private void locationSettingsChanged() {
+ boolean isEnabled = isLocationEnabled();
+ for (LocationSettingsChangeCallback cb : mSettingsChangeCallbacks) {
+ cb.onLocationSettingsChanged(isEnabled);
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileDataControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileDataControllerImpl.java
index f2b2f66..a7fdadc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileDataControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileDataControllerImpl.java
@@ -162,7 +162,7 @@ public class MobileDataControllerImpl implements NetworkController.MobileDataCon
usage.warningLevel = DEFAULT_WARNING_LEVEL;
}
if (usage != null) {
- usage.carrier = mNetworkController.getMobileNetworkName();
+ usage.carrier = mNetworkController.getMobileDataNetworkName();
}
return usage;
} catch (RemoteException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
new file mode 100644
index 0000000..b1c650e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -0,0 +1,542 @@
+/*
+ * 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.statusbar.policy;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.NetworkCapabilities;
+import android.os.Looper;
+import android.telephony.PhoneStateListener;
+import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+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.systemui.R;
+import com.android.systemui.statusbar.policy.NetworkController.IconState;
+import com.android.systemui.statusbar.policy.NetworkControllerImpl.Config;
+import com.android.systemui.statusbar.policy.NetworkControllerImpl.SubscriptionDefaults;
+
+import java.io.PrintWriter;
+import java.util.BitSet;
+import java.util.Objects;
+
+
+public class MobileSignalController extends SignalController<
+ MobileSignalController.MobileState, MobileSignalController.MobileIconGroup> {
+ private final TelephonyManager mPhone;
+ private final SubscriptionDefaults mDefaults;
+ private final String mNetworkNameDefault;
+ private final String mNetworkNameSeparator;
+ @VisibleForTesting
+ final PhoneStateListener mPhoneStateListener;
+ // Save entire info for logging, we only use the id.
+ private final SubscriptionInfo mSubscriptionInfo;
+
+ // @VisibleForDemoMode
+ final SparseArray<MobileIconGroup> mNetworkToIconLookup;
+
+ // Since some pieces of the phone state are interdependent we store it locally,
+ // this could potentially become part of MobileState for simplification/complication
+ // of code.
+ private int mDataNetType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+ private int mDataState = TelephonyManager.DATA_DISCONNECTED;
+ private ServiceState mServiceState;
+ private SignalStrength mSignalStrength;
+ private MobileIconGroup mDefaultIcons;
+ private Config mConfig;
+
+ // TODO: Reduce number of vars passed in, if we have the NetworkController, probably don't
+ // need listener lists anymore.
+ public MobileSignalController(Context context, Config config, boolean hasMobileData,
+ TelephonyManager phone, CallbackHandler callbackHandler,
+ NetworkControllerImpl networkController, SubscriptionInfo info,
+ SubscriptionDefaults defaults, Looper receiverLooper) {
+ super("MobileSignalController(" + info.getSubscriptionId() + ")", context,
+ NetworkCapabilities.TRANSPORT_CELLULAR, callbackHandler,
+ networkController);
+ mNetworkToIconLookup = new SparseArray<>();
+ mConfig = config;
+ mPhone = phone;
+ mDefaults = defaults;
+ mSubscriptionInfo = info;
+ mPhoneStateListener = new MobilePhoneStateListener(info.getSubscriptionId(),
+ receiverLooper);
+ mNetworkNameSeparator = getStringIfExists(R.string.status_bar_network_name_separator);
+ mNetworkNameDefault = getStringIfExists(
+ com.android.internal.R.string.lockscreen_carrier_default);
+
+ mapIconSets();
+
+ mLastState.networkName = mCurrentState.networkName = mNetworkNameDefault;
+ mLastState.networkNameData = mCurrentState.networkNameData = mNetworkNameDefault;
+ mLastState.enabled = mCurrentState.enabled = hasMobileData;
+ mLastState.iconGroup = mCurrentState.iconGroup = mDefaultIcons;
+ // Get initial data sim state.
+ updateDataSim();
+ }
+
+ public void setConfiguration(Config config) {
+ mConfig = config;
+ mapIconSets();
+ updateTelephony();
+ }
+
+ public int getDataContentDescription() {
+ return getIcons().mDataContentDescription;
+ }
+
+ public void setAirplaneMode(boolean airplaneMode) {
+ mCurrentState.airplaneMode = airplaneMode;
+ notifyListenersIfNecessary();
+ }
+
+ @Override
+ public void updateConnectivity(BitSet connectedTransports, BitSet validatedTransports) {
+ boolean isValidated = validatedTransports.get(mTransportType);
+ mCurrentState.isDefault = connectedTransports.get(mTransportType);
+ // Only show this as not having connectivity if we are default.
+ mCurrentState.inetCondition = (isValidated || !mCurrentState.isDefault) ? 1 : 0;
+ notifyListenersIfNecessary();
+ }
+
+ public void setCarrierNetworkChangeMode(boolean carrierNetworkChangeMode) {
+ mCurrentState.carrierNetworkChangeMode = carrierNetworkChangeMode;
+ updateTelephony();
+ }
+
+ /**
+ * Start listening for phone state changes.
+ */
+ public void registerListener() {
+ mPhone.listen(mPhoneStateListener,
+ PhoneStateListener.LISTEN_SERVICE_STATE
+ | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS
+ | PhoneStateListener.LISTEN_CALL_STATE
+ | PhoneStateListener.LISTEN_DATA_CONNECTION_STATE
+ | PhoneStateListener.LISTEN_DATA_ACTIVITY
+ | PhoneStateListener.LISTEN_CARRIER_NETWORK_CHANGE);
+ }
+
+ /**
+ * Stop listening for phone state changes.
+ */
+ public void unregisterListener() {
+ mPhone.listen(mPhoneStateListener, 0);
+ }
+
+ /**
+ * Produce a mapping of data network types to icon groups for simple and quick use in
+ * updateTelephony.
+ */
+ private void mapIconSets() {
+ mNetworkToIconLookup.clear();
+
+ mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_0, TelephonyIcons.THREE_G);
+ mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_A, TelephonyIcons.THREE_G);
+ mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_B, TelephonyIcons.THREE_G);
+ mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EHRPD, TelephonyIcons.THREE_G);
+ mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UMTS, TelephonyIcons.THREE_G);
+
+ if (!mConfig.showAtLeast3G) {
+ mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UNKNOWN,
+ TelephonyIcons.UNKNOWN);
+ mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EDGE, TelephonyIcons.E);
+ mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_CDMA, TelephonyIcons.ONE_X);
+ mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_1xRTT, TelephonyIcons.ONE_X);
+
+ mDefaultIcons = TelephonyIcons.G;
+ } else {
+ mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UNKNOWN,
+ TelephonyIcons.THREE_G);
+ mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EDGE,
+ TelephonyIcons.THREE_G);
+ mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_CDMA,
+ TelephonyIcons.THREE_G);
+ mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_1xRTT,
+ TelephonyIcons.THREE_G);
+ mDefaultIcons = TelephonyIcons.THREE_G;
+ }
+
+ MobileIconGroup hGroup = TelephonyIcons.THREE_G;
+ if (mConfig.hspaDataDistinguishable) {
+ hGroup = TelephonyIcons.H;
+ }
+ mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSDPA, hGroup);
+ mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSUPA, hGroup);
+ mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSPA, hGroup);
+ mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSPAP, hGroup);
+
+ if (mConfig.show4gForLte) {
+ mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE, TelephonyIcons.FOUR_G);
+ } else {
+ mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE, TelephonyIcons.LTE);
+ }
+ mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_IWLAN, TelephonyIcons.FOUR_G);
+ }
+
+ @Override
+ public void notifyListeners() {
+ MobileIconGroup icons = getIcons();
+
+ String contentDescription = getStringIfExists(getContentDescription());
+ String dataContentDescription = getStringIfExists(icons.mDataContentDescription);
+
+ // Show icon in QS when we are connected or need to show roaming.
+ boolean showDataIcon = mCurrentState.dataConnected
+ || mCurrentState.iconGroup == TelephonyIcons.ROAMING;
+ IconState statusIcon = new IconState(mCurrentState.enabled && !mCurrentState.airplaneMode,
+ getCurrentIconId(), contentDescription);
+
+ int qsTypeIcon = 0;
+ IconState qsIcon = null;
+ String description = null;
+ // Only send data sim callbacks to QS.
+ if (mCurrentState.dataSim) {
+ qsTypeIcon = showDataIcon ? icons.mQsDataType : 0;
+ qsIcon = new IconState(mCurrentState.enabled
+ && !mCurrentState.isEmergency, getQsCurrentIconId(), contentDescription);
+ description = mCurrentState.isEmergency ? null : mCurrentState.networkName;
+ }
+ boolean activityIn = mCurrentState.dataConnected
+ && !mCurrentState.carrierNetworkChangeMode
+ && mCurrentState.activityIn;
+ boolean activityOut = mCurrentState.dataConnected
+ && !mCurrentState.carrierNetworkChangeMode
+ && mCurrentState.activityOut;
+ showDataIcon &= mCurrentState.isDefault;
+ int typeIcon = showDataIcon ? icons.mDataType : 0;
+ mCallbackHandler.setMobileDataIndicators(statusIcon, qsIcon, typeIcon, qsTypeIcon,
+ activityIn, activityOut, dataContentDescription, description, icons.mIsWide,
+ mSubscriptionInfo.getSubscriptionId());
+ }
+
+ @Override
+ protected MobileState cleanState() {
+ return new MobileState();
+ }
+
+ private boolean hasService() {
+ if (mServiceState != null) {
+ // Consider the device to be in service if either voice or data
+ // service is available. Some SIM cards are marketed as data-only
+ // and do not support voice service, and on these SIM cards, we
+ // want to show signal bars for data service as well as the "no
+ // service" or "emergency calls only" text that indicates that voice
+ // is not available.
+ switch (mServiceState.getVoiceRegState()) {
+ case ServiceState.STATE_POWER_OFF:
+ return false;
+ case ServiceState.STATE_OUT_OF_SERVICE:
+ case ServiceState.STATE_EMERGENCY_ONLY:
+ return mServiceState.getDataRegState() == ServiceState.STATE_IN_SERVICE;
+ default:
+ return true;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ private boolean isCdma() {
+ return (mSignalStrength != null) && !mSignalStrength.isGsm();
+ }
+
+ public boolean isEmergencyOnly() {
+ return (mServiceState != null && mServiceState.isEmergencyOnly());
+ }
+
+ private boolean isRoaming() {
+ if (isCdma()) {
+ final int iconMode = mServiceState.getCdmaEriIconMode();
+ return mServiceState.getCdmaEriIconIndex() != EriInfo.ROAMING_INDICATOR_OFF
+ && (iconMode == EriInfo.ROAMING_ICON_MODE_NORMAL
+ || iconMode == EriInfo.ROAMING_ICON_MODE_FLASH);
+ } else {
+ return mServiceState != null && mServiceState.getRoaming();
+ }
+ }
+
+ private boolean isCarrierNetworkChangeActive() {
+ return mCurrentState.carrierNetworkChangeMode;
+ }
+
+ public void handleBroadcast(Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION)) {
+ updateNetworkName(intent.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false),
+ intent.getStringExtra(TelephonyIntents.EXTRA_SPN),
+ intent.getStringExtra(TelephonyIntents.EXTRA_DATA_SPN),
+ intent.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false),
+ intent.getStringExtra(TelephonyIntents.EXTRA_PLMN));
+ notifyListenersIfNecessary();
+ } else if (action.equals(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)) {
+ updateDataSim();
+ notifyListenersIfNecessary();
+ }
+ }
+
+ private void updateDataSim() {
+ int defaultDataSub = mDefaults.getDefaultDataSubId();
+ if (SubscriptionManager.isValidSubscriptionId(defaultDataSub)) {
+ mCurrentState.dataSim = defaultDataSub == mSubscriptionInfo.getSubscriptionId();
+ } else {
+ // There doesn't seem to be a data sim selected, however if
+ // there isn't a MobileSignalController with dataSim set, then
+ // QS won't get any callbacks and will be blank. Instead
+ // lets just assume we are the data sim (which will basically
+ // show one at random) in QS until one is selected. The user
+ // should pick one soon after, so we shouldn't be in this state
+ // for long.
+ mCurrentState.dataSim = true;
+ }
+ }
+
+ /**
+ * Updates the network's name based on incoming spn and plmn.
+ */
+ void updateNetworkName(boolean showSpn, String spn, String dataSpn,
+ boolean showPlmn, String plmn) {
+ if (CHATTY) {
+ Log.d("CarrierLabel", "updateNetworkName showSpn=" + showSpn
+ + " spn=" + spn + " dataSpn=" + dataSpn
+ + " showPlmn=" + showPlmn + " plmn=" + plmn);
+ }
+ StringBuilder str = new StringBuilder();
+ StringBuilder strData = new StringBuilder();
+ if (showPlmn && plmn != null) {
+ str.append(plmn);
+ strData.append(plmn);
+ }
+ if (showSpn && spn != null) {
+ if (str.length() != 0) {
+ str.append(mNetworkNameSeparator);
+ }
+ str.append(spn);
+ }
+ if (str.length() != 0) {
+ mCurrentState.networkName = str.toString();
+ } else {
+ mCurrentState.networkName = mNetworkNameDefault;
+ }
+ if (showSpn && dataSpn != null) {
+ if (strData.length() != 0) {
+ strData.append(mNetworkNameSeparator);
+ }
+ strData.append(dataSpn);
+ }
+ if (strData.length() != 0) {
+ mCurrentState.networkNameData = strData.toString();
+ } else {
+ mCurrentState.networkNameData = mNetworkNameDefault;
+ }
+ }
+
+ /**
+ * Updates the current state based on mServiceState, mSignalStrength, mDataNetType,
+ * mDataState, and mSimState. It should be called any time one of these is updated.
+ * This will call listeners if necessary.
+ */
+ private final void updateTelephony() {
+ if (DEBUG) {
+ Log.d(mTag, "updateTelephonySignalStrength: hasService=" + hasService()
+ + " ss=" + mSignalStrength);
+ }
+ mCurrentState.connected = hasService() && mSignalStrength != null;
+ if (mCurrentState.connected) {
+ if (!mSignalStrength.isGsm() && mConfig.alwaysShowCdmaRssi) {
+ mCurrentState.level = mSignalStrength.getCdmaLevel();
+ } else {
+ mCurrentState.level = mSignalStrength.getLevel();
+ }
+ }
+ if (mNetworkToIconLookup.indexOfKey(mDataNetType) >= 0) {
+ mCurrentState.iconGroup = mNetworkToIconLookup.get(mDataNetType);
+ } else {
+ mCurrentState.iconGroup = mDefaultIcons;
+ }
+ mCurrentState.dataConnected = mCurrentState.connected
+ && mDataState == TelephonyManager.DATA_CONNECTED;
+
+ if (isCarrierNetworkChangeActive()) {
+ mCurrentState.iconGroup = TelephonyIcons.CARRIER_NETWORK_CHANGE;
+ } else if (isRoaming()) {
+ mCurrentState.iconGroup = TelephonyIcons.ROAMING;
+ }
+ if (isEmergencyOnly() != mCurrentState.isEmergency) {
+ mCurrentState.isEmergency = isEmergencyOnly();
+ mNetworkController.recalculateEmergency();
+ }
+ // Fill in the network name if we think we have it.
+ if (mCurrentState.networkName == mNetworkNameDefault && mServiceState != null
+ && mServiceState.getOperatorAlphaShort() != null) {
+ mCurrentState.networkName = mServiceState.getOperatorAlphaShort();
+ }
+
+ notifyListenersIfNecessary();
+ }
+
+ @VisibleForTesting
+ void setActivity(int activity) {
+ mCurrentState.activityIn = activity == TelephonyManager.DATA_ACTIVITY_INOUT
+ || activity == TelephonyManager.DATA_ACTIVITY_IN;
+ mCurrentState.activityOut = activity == TelephonyManager.DATA_ACTIVITY_INOUT
+ || activity == TelephonyManager.DATA_ACTIVITY_OUT;
+ notifyListenersIfNecessary();
+ }
+
+ @Override
+ public void dump(PrintWriter pw) {
+ super.dump(pw);
+ pw.println(" mSubscription=" + mSubscriptionInfo + ",");
+ pw.println(" mServiceState=" + mServiceState + ",");
+ pw.println(" mSignalStrength=" + mSignalStrength + ",");
+ pw.println(" mDataState=" + mDataState + ",");
+ pw.println(" mDataNetType=" + mDataNetType + ",");
+ }
+
+ class MobilePhoneStateListener extends PhoneStateListener {
+ public MobilePhoneStateListener(int subId, Looper looper) {
+ super(subId, looper);
+ }
+
+ @Override
+ public void onSignalStrengthsChanged(SignalStrength signalStrength) {
+ if (DEBUG) {
+ Log.d(mTag, "onSignalStrengthsChanged signalStrength=" + signalStrength +
+ ((signalStrength == null) ? "" : (" level=" + signalStrength.getLevel())));
+ }
+ mSignalStrength = signalStrength;
+ updateTelephony();
+ }
+
+ @Override
+ public void onServiceStateChanged(ServiceState state) {
+ if (DEBUG) {
+ Log.d(mTag, "onServiceStateChanged voiceState=" + state.getVoiceRegState()
+ + " dataState=" + state.getDataRegState());
+ }
+ mServiceState = state;
+ updateTelephony();
+ }
+
+ @Override
+ public void onDataConnectionStateChanged(int state, int networkType) {
+ if (DEBUG) {
+ Log.d(mTag, "onDataConnectionStateChanged: state=" + state
+ + " type=" + networkType);
+ }
+ mDataState = state;
+ mDataNetType = networkType;
+ updateTelephony();
+ }
+
+ @Override
+ public void onDataActivity(int direction) {
+ if (DEBUG) {
+ Log.d(mTag, "onDataActivity: direction=" + direction);
+ }
+ setActivity(direction);
+ }
+
+ @Override
+ public void onCarrierNetworkChange(boolean active) {
+ if (DEBUG) {
+ Log.d(mTag, "onCarrierNetworkChange: active=" + active);
+ }
+ mCurrentState.carrierNetworkChangeMode = active;
+
+ updateTelephony();
+ }
+ };
+
+ static class MobileIconGroup extends SignalController.IconGroup {
+ final int mDataContentDescription; // mContentDescriptionDataType
+ final int mDataType;
+ final boolean mIsWide;
+ final int mQsDataType;
+
+ public MobileIconGroup(String name, int[][] sbIcons, int[][] qsIcons, int[] contentDesc,
+ int sbNullState, int qsNullState, int sbDiscState, int qsDiscState,
+ int discContentDesc, int dataContentDesc, int dataType, boolean isWide,
+ int qsDataType) {
+ super(name, sbIcons, qsIcons, contentDesc, sbNullState, qsNullState, sbDiscState,
+ qsDiscState, discContentDesc);
+ mDataContentDescription = dataContentDesc;
+ mDataType = dataType;
+ mIsWide = isWide;
+ mQsDataType = qsDataType;
+ }
+ }
+
+ static class MobileState extends SignalController.State {
+ String networkName;
+ String networkNameData;
+ boolean dataSim;
+ boolean dataConnected;
+ boolean isEmergency;
+ boolean airplaneMode;
+ boolean carrierNetworkChangeMode;
+ boolean isDefault;
+
+ @Override
+ public void copyFrom(State s) {
+ super.copyFrom(s);
+ MobileState state = (MobileState) s;
+ dataSim = state.dataSim;
+ networkName = state.networkName;
+ networkNameData = state.networkNameData;
+ dataConnected = state.dataConnected;
+ isDefault = state.isDefault;
+ isEmergency = state.isEmergency;
+ airplaneMode = state.airplaneMode;
+ carrierNetworkChangeMode = state.carrierNetworkChangeMode;
+ }
+
+ @Override
+ protected void toString(StringBuilder builder) {
+ super.toString(builder);
+ builder.append(',');
+ builder.append("dataSim=").append(dataSim).append(',');
+ builder.append("networkName=").append(networkName).append(',');
+ builder.append("networkNameData=").append(networkNameData).append(',');
+ builder.append("dataConnected=").append(dataConnected).append(',');
+ builder.append("isDefault=").append(isDefault).append(',');
+ builder.append("isEmergency=").append(isEmergency).append(',');
+ builder.append("airplaneMode=").append(airplaneMode).append(',');
+ builder.append("carrierNetworkChangeMode=").append(carrierNetworkChangeMode);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return super.equals(o)
+ && Objects.equals(((MobileState) o).networkName, networkName)
+ && Objects.equals(((MobileState) o).networkNameData, networkNameData)
+ && ((MobileState) o).dataSim == dataSim
+ && ((MobileState) o).dataConnected == dataConnected
+ && ((MobileState) o).isEmergency == isEmergency
+ && ((MobileState) o).airplaneMode == airplaneMode
+ && ((MobileState) o).carrierNetworkChangeMode == carrierNetworkChangeMode
+ && ((MobileState) o).isDefault == isDefault;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
index 3cffc85..38656ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
@@ -16,30 +16,56 @@
package com.android.systemui.statusbar.policy;
+import android.content.Context;
import android.content.Intent;
+import android.telephony.SubscriptionInfo;
+
+import com.android.settingslib.wifi.AccessPoint;
+
+import java.util.List;
public interface NetworkController {
boolean hasMobileDataFeature();
- void addNetworkSignalChangedCallback(NetworkSignalChangedCallback cb);
- void removeNetworkSignalChangedCallback(NetworkSignalChangedCallback cb);
+ void addSignalCallback(SignalCallback cb);
+ void removeSignalCallback(SignalCallback cb);
void setWifiEnabled(boolean enabled);
void onUserSwitched(int newUserId);
AccessPointController getAccessPointController();
MobileDataController getMobileDataController();
- public interface NetworkSignalChangedCallback {
- void onWifiSignalChanged(boolean enabled, boolean connected, int wifiSignalIconId,
- boolean activityIn, boolean activityOut,
- String wifiSignalContentDescriptionId, String description);
- void onMobileDataSignalChanged(boolean enabled, int mobileSignalIconId,
- String mobileSignalContentDescriptionId, int dataTypeIconId,
- boolean activityIn, boolean activityOut,
- String dataTypeContentDescriptionId, String description,
- boolean isDataTypeIconWide);
- void onNoSimVisibleChanged(boolean visible);
- void onAirplaneModeChanged(boolean enabled);
- void onMobileDataEnabled(boolean enabled);
+ public interface SignalCallback {
+ void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon,
+ boolean activityIn, boolean activityOut, String description);
+
+ void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType,
+ int qsType, boolean activityIn, boolean activityOut, String typeContentDescription,
+ String description, boolean isWide, int subId);
+ void setSubs(List<SubscriptionInfo> subs);
+ void setNoSims(boolean show);
+
+ void setEthernetIndicators(IconState icon);
+
+ void setIsAirplaneMode(IconState icon);
+
+ void setMobileDataEnabled(boolean enabled);
+ }
+
+ public static class IconState {
+ public final boolean visible;
+ public final int icon;
+ public final String contentDescription;
+
+ public IconState(boolean visible, int icon, String contentDescription) {
+ this.visible = visible;
+ this.icon = icon;
+ this.contentDescription = contentDescription;
+ }
+
+ public IconState(boolean visible, int icon, int contentDescription,
+ Context context) {
+ this(visible, icon, context.getString(contentDescription));
+ }
}
/**
@@ -50,25 +76,14 @@ public interface NetworkController {
void addAccessPointCallback(AccessPointCallback callback);
void removeAccessPointCallback(AccessPointCallback callback);
void scanForAccessPoints();
+ int getIcon(AccessPoint ap);
boolean connect(AccessPoint ap);
boolean canConfigWifi();
public interface AccessPointCallback {
- void onAccessPointsChanged(AccessPoint[] accessPoints);
+ void onAccessPointsChanged(List<AccessPoint> accessPoints);
void onSettingsActivityTriggered(Intent settingsIntent);
}
-
- public static class AccessPoint {
- public static final int NO_NETWORK = -1; // see WifiManager
-
- public int networkId;
- public int iconId;
- public String ssid;
- public boolean isConnected;
- public boolean isConfigured;
- public boolean hasSecurity;
- public int level; // 0 - 5
- }
}
/**
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 9a7f21e..ff0e8a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -17,10 +17,7 @@
package com.android.systemui.statusbar.policy;
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
-import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
-import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
-import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
-import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -28,34 +25,23 @@ import android.content.IntentFilter;
import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.NetworkCapabilities;
-import android.net.NetworkInfo;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
-import android.os.Message;
-import android.os.Messenger;
+import android.os.Looper;
import android.provider.Settings;
-import android.telephony.PhoneStateListener;
-import android.telephony.ServiceState;
-import android.telephony.SignalStrength;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
-import android.text.format.DateFormat;
import android.util.Log;
-import android.util.SparseArray;
+import android.util.MathUtils;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.IccCardConstants;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.TelephonyIntents;
-import com.android.internal.telephony.cdma.EriInfo;
-import com.android.internal.util.AsyncChannel;
import com.android.systemui.DemoMode;
import com.android.systemui.R;
@@ -69,7 +55,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
-import java.util.Objects;
/** Platform implementation of the network controller. **/
public class NetworkControllerImpl extends BroadcastReceiver
@@ -78,13 +63,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
static final String TAG = "NetworkController";
static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
// additional diagnostics, but not logspew
- static final boolean CHATTY = Log.isLoggable(TAG + ".Chat", Log.DEBUG);
- // Save the previous SignalController.States of all SignalControllers for dumps.
- static final boolean RECORD_HISTORY = true;
- // If RECORD_HISTORY how many to save, must be a power of 2.
- static final int HISTORY_SIZE = 16;
-
- private static final int INET_CONDITION_THRESHOLD = 50;
+ static final boolean CHATTY = Log.isLoggable(TAG + "Chat", Log.DEBUG);
private final Context mContext;
private final TelephonyManager mPhone;
@@ -92,11 +71,16 @@ public class NetworkControllerImpl extends BroadcastReceiver
private final ConnectivityManager mConnectivityManager;
private final SubscriptionManager mSubscriptionManager;
private final boolean mHasMobileDataFeature;
+ private final SubscriptionDefaults mSubDefaults;
private Config mConfig;
// Subcontrollers.
@VisibleForTesting
final WifiSignalController mWifiSignalController;
+
+ @VisibleForTesting
+ final EthernetSignalController mEthernetSignalController;
+
@VisibleForTesting
final Map<Integer, MobileSignalController> mMobileSignalControllers =
new HashMap<Integer, MobileSignalController>();
@@ -106,12 +90,6 @@ public class NetworkControllerImpl extends BroadcastReceiver
private final AccessPointControllerImpl mAccessPoints;
private final MobileDataControllerImpl mMobileDataController;
- // Network types that replace the carrier label if the device does not support mobile data.
- private boolean mBluetoothTethered = false;
- private boolean mEthernetConnected = false;
-
- // state of inet connection
- private boolean mConnected = false;
private boolean mInetCondition; // Used for Logging and demo.
// BitSets indicating which network transport types (e.g., TRANSPORT_WIFI, TRANSPORT_MOBILE) are
@@ -124,50 +102,57 @@ public class NetworkControllerImpl extends BroadcastReceiver
private boolean mHasNoSims;
private Locale mLocale = null;
// This list holds our ordering.
- private List<SubscriptionInfo> mCurrentSubscriptions
- = new ArrayList<SubscriptionInfo>();
-
- // All the callbacks.
- private ArrayList<EmergencyListener> mEmergencyListeners = new ArrayList<EmergencyListener>();
- private ArrayList<CarrierLabelListener> mCarrierListeners =
- new ArrayList<CarrierLabelListener>();
- private ArrayList<SignalCluster> mSignalClusters = new ArrayList<SignalCluster>();
- private ArrayList<NetworkSignalChangedCallback> mSignalsChangedCallbacks =
- new ArrayList<NetworkSignalChangedCallback>();
+ private List<SubscriptionInfo> mCurrentSubscriptions = new ArrayList<>();
+
@VisibleForTesting
boolean mListening;
// The current user ID.
private int mCurrentUserId;
+ private OnSubscriptionsChangedListener mSubscriptionListener;
+
+ // Handler that all broadcasts are received on.
+ private final Handler mReceiverHandler;
+ // Handler that all callbacks are made on.
+ private final CallbackHandler mCallbackHandler;
+
/**
* Construct this controller object and register for updates.
*/
- public NetworkControllerImpl(Context context) {
+ public NetworkControllerImpl(Context context, Looper bgLooper) {
this(context, (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE),
(TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE),
(WifiManager) context.getSystemService(Context.WIFI_SERVICE),
- SubscriptionManager.from(context), Config.readConfig(context),
- new AccessPointControllerImpl(context), new MobileDataControllerImpl(context));
- registerListeners();
+ SubscriptionManager.from(context), Config.readConfig(context), bgLooper,
+ new CallbackHandler(),
+ new AccessPointControllerImpl(context, bgLooper),
+ new MobileDataControllerImpl(context),
+ new SubscriptionDefaults());
+ mReceiverHandler.post(mRegisterListeners);
}
@VisibleForTesting
NetworkControllerImpl(Context context, ConnectivityManager connectivityManager,
TelephonyManager telephonyManager, WifiManager wifiManager,
- SubscriptionManager subManager, Config config,
+ SubscriptionManager subManager, Config config, Looper bgLooper,
+ CallbackHandler callbackHandler,
AccessPointControllerImpl accessPointController,
- MobileDataControllerImpl mobileDataController) {
+ MobileDataControllerImpl mobileDataController,
+ SubscriptionDefaults defaultsHandler) {
mContext = context;
mConfig = config;
+ mReceiverHandler = new Handler(bgLooper);
+ mCallbackHandler = callbackHandler;
mSubscriptionManager = subManager;
+ mSubDefaults = defaultsHandler;
mConnectivityManager = connectivityManager;
mHasMobileDataFeature =
mConnectivityManager.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
// telephony
- mPhone = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ mPhone = telephonyManager;
// wifi
mWifiManager = wifiManager;
@@ -180,21 +165,25 @@ public class NetworkControllerImpl extends BroadcastReceiver
mMobileDataController.setCallback(new MobileDataControllerImpl.Callback() {
@Override
public void onMobileDataEnabled(boolean enabled) {
- notifyMobileDataEnabled(enabled);
+ mCallbackHandler.setMobileDataEnabled(enabled);
}
});
mWifiSignalController = new WifiSignalController(mContext, mHasMobileDataFeature,
- mSignalsChangedCallbacks, mSignalClusters, this);
+ mCallbackHandler, this);
+
+ mEthernetSignalController = new EthernetSignalController(mContext, mCallbackHandler, this);
// AIRPLANE_MODE_CHANGED is sent at boot; we've probably already missed it
updateAirplaneMode(true /* force callback */);
- mAccessPoints.setNetworkController(this);
}
private void registerListeners() {
for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
mobileSignalController.registerListener();
}
+ if (mSubscriptionListener == null) {
+ mSubscriptionListener = new SubListener();
+ }
mSubscriptionManager.addOnSubscriptionsChangedListener(mSubscriptionListener);
// broadcasts
@@ -206,11 +195,11 @@ public class NetworkControllerImpl extends BroadcastReceiver
filter.addAction(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
filter.addAction(TelephonyIntents.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED);
filter.addAction(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION);
- filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE);
+ filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
filter.addAction(ConnectivityManager.INET_CONDITION_ACTION);
filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- mContext.registerReceiver(this, filter);
+ mContext.registerReceiver(this, filter, null, mReceiverHandler);
mListening = true;
updateMobileControllers();
@@ -240,20 +229,8 @@ public class NetworkControllerImpl extends BroadcastReceiver
}
public void addEmergencyListener(EmergencyListener listener) {
- mEmergencyListeners.add(listener);
- listener.setEmergencyCallsOnly(isEmergencyOnly());
- }
-
- public void addCarrierLabel(CarrierLabelListener listener) {
- mCarrierListeners.add(listener);
- refreshCarrierLabel();
- }
-
- private void notifyMobileDataEnabled(boolean enabled) {
- final int length = mSignalsChangedCallbacks.size();
- for (int i = 0; i < length; i++) {
- mSignalsChangedCallbacks.get(i).onMobileDataEnabled(enabled);
- }
+ mCallbackHandler.setListening(listener, true);
+ mCallbackHandler.setEmergencyCallsOnly(isEmergencyOnly());
}
public boolean hasMobileDataFeature() {
@@ -265,7 +242,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
}
private MobileSignalController getDataController() {
- int dataSubId = SubscriptionManager.getDefaultDataSubId();
+ int dataSubId = mSubDefaults.getDefaultDataSubId();
if (!SubscriptionManager.isValidSubscriptionId(dataSubId)) {
if (DEBUG) Log.e(TAG, "No data sim selected");
return mDefaultSignalController;
@@ -277,23 +254,25 @@ public class NetworkControllerImpl extends BroadcastReceiver
return mDefaultSignalController;
}
- public String getMobileNetworkName() {
+ public String getMobileDataNetworkName() {
MobileSignalController controller = getDataController();
- return controller != null ? controller.getState().networkName : "";
+ return controller != null ? controller.getState().networkNameData : "";
}
public boolean isEmergencyOnly() {
- int voiceSubId = SubscriptionManager.getDefaultVoiceSubId();
+ int voiceSubId = mSubDefaults.getDefaultVoiceSubId();
if (!SubscriptionManager.isValidSubscriptionId(voiceSubId)) {
for (MobileSignalController mobileSignalController :
mMobileSignalControllers.values()) {
- if (!mobileSignalController.isEmergencyOnly()) {
+ if (!mobileSignalController.getState().isEmergency) {
+ if (DEBUG) Log.d(TAG, "Found emergency " + mobileSignalController.mTag);
return false;
}
}
}
if (mMobileSignalControllers.containsKey(voiceSubId)) {
- return mMobileSignalControllers.get(voiceSubId).isEmergencyOnly();
+ if (DEBUG) Log.d(TAG, "Getting emergency from " + voiceSubId);
+ return mMobileSignalControllers.get(voiceSubId).getState().isEmergency;
}
if (DEBUG) Log.e(TAG, "Cannot find controller for voice sub: " + voiceSubId);
// Something is wrong, better assume we can't make calls...
@@ -305,40 +284,25 @@ public class NetworkControllerImpl extends BroadcastReceiver
* so we should recheck and send out the state to listeners.
*/
void recalculateEmergency() {
- final boolean emergencyOnly = isEmergencyOnly();
- final int length = mEmergencyListeners.size();
- for (int i = 0; i < length; i++) {
- mEmergencyListeners.get(i).setEmergencyCallsOnly(emergencyOnly);
- }
- // If the emergency has a chance to change, then so does the carrier
- // label.
- refreshCarrierLabel();
- }
-
- public void addSignalCluster(SignalCluster cluster) {
- mSignalClusters.add(cluster);
- cluster.setSubs(mCurrentSubscriptions);
- cluster.setIsAirplaneMode(mAirplaneMode, TelephonyIcons.FLIGHT_MODE_ICON,
- R.string.accessibility_airplane_mode);
- cluster.setNoSims(mHasNoSims);
- mWifiSignalController.notifyListeners();
- for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
- mobileSignalController.notifyListeners();
- }
+ mCallbackHandler.setEmergencyCallsOnly(isEmergencyOnly());
}
- public void addNetworkSignalChangedCallback(NetworkSignalChangedCallback cb) {
- mSignalsChangedCallbacks.add(cb);
- cb.onAirplaneModeChanged(mAirplaneMode);
- cb.onNoSimVisibleChanged(mHasNoSims);
+ public void addSignalCallback(SignalCallback cb) {
+ mCallbackHandler.setListening(cb, true);
+ mCallbackHandler.setSubs(mCurrentSubscriptions);
+ mCallbackHandler.setIsAirplaneMode(new IconState(mAirplaneMode,
+ TelephonyIcons.FLIGHT_MODE_ICON, R.string.accessibility_airplane_mode, mContext));
+ mCallbackHandler.setNoSims(mHasNoSims);
mWifiSignalController.notifyListeners();
+ mEthernetSignalController.notifyListeners();
for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
mobileSignalController.notifyListeners();
}
}
- public void removeNetworkSignalChangedCallback(NetworkSignalChangedCallback cb) {
- mSignalsChangedCallbacks.remove(cb);
+ @Override
+ public void removeSignalCallback(SignalCallback cb) {
+ mCallbackHandler.setListening(cb, false);
}
@Override
@@ -364,7 +328,6 @@ public class NetworkControllerImpl extends BroadcastReceiver
mCurrentUserId = newUserId;
mAccessPoints.onUserSwitched(newUserId);
updateConnectivity();
- refreshCarrierLabel();
}
@Override
@@ -373,17 +336,15 @@ public class NetworkControllerImpl extends BroadcastReceiver
Log.d(TAG, "onReceive: intent=" + intent);
}
final String action = intent.getAction();
- if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE) ||
+ if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION) ||
action.equals(ConnectivityManager.INET_CONDITION_ACTION)) {
updateConnectivity();
- refreshCarrierLabel();
} else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
mConfig = Config.readConfig(mContext);
handleConfigurationChanged();
} else if (action.equals(Intent.ACTION_AIRPLANE_MODE_CHANGED)) {
refreshLocale();
updateAirplaneMode(false);
- refreshCarrierLabel();
} else if (action.equals(TelephonyIntents.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED)) {
// We are using different subs now, we might be able to make calls.
recalculateEmergency();
@@ -419,13 +380,17 @@ public class NetworkControllerImpl extends BroadcastReceiver
mobileSignalController.setConfiguration(mConfig);
}
refreshLocale();
- refreshCarrierLabel();
}
private void updateMobileControllers() {
if (!mListening) {
return;
}
+ doUpdateMobileControllers();
+ }
+
+ @VisibleForTesting
+ void doUpdateMobileControllers() {
List<SubscriptionInfo> subscriptions = mSubscriptionManager.getActiveSubscriptionInfoList();
if (subscriptions == null) {
subscriptions = Collections.emptyList();
@@ -440,6 +405,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
}
setCurrentSubscriptions(subscriptions);
updateNoSims();
+ recalculateEmergency();
}
@VisibleForTesting
@@ -447,7 +413,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
boolean hasNoSims = mHasMobileDataFeature && mMobileSignalControllers.size() == 0;
if (hasNoSims != mHasNoSims) {
mHasNoSims = hasNoSims;
- notifyListeners();
+ mCallbackHandler.setNoSims(mHasNoSims);
}
}
@@ -461,10 +427,6 @@ public class NetworkControllerImpl extends BroadcastReceiver
: lhs.getSimSlotIndex() - rhs.getSimSlotIndex();
}
});
- final int length = mSignalClusters.size();
- for (int i = 0; i < length; i++) {
- mSignalClusters.get(i).setSubs(subscriptions);
- }
mCurrentSubscriptions = subscriptions;
HashMap<Integer, MobileSignalController> cachedControllers =
@@ -478,8 +440,8 @@ public class NetworkControllerImpl extends BroadcastReceiver
mMobileSignalControllers.put(subId, cachedControllers.remove(subId));
} else {
MobileSignalController controller = new MobileSignalController(mContext, mConfig,
- mHasMobileDataFeature, mPhone, mSignalsChangedCallbacks, mSignalClusters,
- this, subscriptions.get(i));
+ mHasMobileDataFeature, mPhone, mCallbackHandler,
+ this, subscriptions.get(i), mSubDefaults, mReceiverHandler.getLooper());
mMobileSignalControllers.put(subId, controller);
if (subscriptions.get(i).getSimSlotIndex() == 0) {
mDefaultSignalController = controller;
@@ -497,6 +459,9 @@ public class NetworkControllerImpl extends BroadcastReceiver
cachedControllers.get(key).unregisterListener();
}
}
+ mCallbackHandler.setSubs(subscriptions);
+ notifyAllListeners();
+
// There may be new MobileSignalControllers around, make sure they get the current
// inet condition and airplane mode.
pushConnectivityToSignals();
@@ -525,7 +490,6 @@ public class NetworkControllerImpl extends BroadcastReceiver
mobileSignalController.setAirplaneMode(mAirplaneMode);
}
notifyListeners();
- refreshCarrierLabel();
}
}
@@ -547,6 +511,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
mobileSignalController.notifyListeners();
}
mWifiSignalController.notifyListeners();
+ mEthernetSignalController.notifyListeners();
}
/**
@@ -555,17 +520,9 @@ public class NetworkControllerImpl extends BroadcastReceiver
* notifyAllListeners.
*/
private void notifyListeners() {
- int length = mSignalClusters.size();
- for (int i = 0; i < length; i++) {
- mSignalClusters.get(i).setIsAirplaneMode(mAirplaneMode, TelephonyIcons.FLIGHT_MODE_ICON,
- R.string.accessibility_airplane_mode);
- mSignalClusters.get(i).setNoSims(mHasNoSims);
- }
- int signalsChangedLength = mSignalsChangedCallbacks.size();
- for (int i = 0; i < signalsChangedLength; i++) {
- mSignalsChangedCallbacks.get(i).onAirplaneModeChanged(mAirplaneMode);
- mSignalsChangedCallbacks.get(i).onNoSimVisibleChanged(mHasNoSims);
- }
+ mCallbackHandler.setIsAirplaneMode(new IconState(mAirplaneMode,
+ TelephonyIcons.FLIGHT_MODE_ICON, R.string.accessibility_airplane_mode, mContext));
+ mCallbackHandler.setNoSims(mHasNoSims);
}
/**
@@ -589,10 +546,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
Log.d(TAG, "updateConnectivity: mValidatedTransports=" + mValidatedTransports);
}
- mConnected = !mConnectedTransports.isEmpty();
mInetCondition = !mValidatedTransports.isEmpty();
- mBluetoothTethered = mConnectedTransports.get(TRANSPORT_BLUETOOTH);
- mEthernetConnected = mConnectedTransports.get(TRANSPORT_ETHERNET);
pushConnectivityToSignals();
}
@@ -603,65 +557,10 @@ public class NetworkControllerImpl extends BroadcastReceiver
private void pushConnectivityToSignals() {
// We want to update all the icons, all at once, for any condition change
for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
- mobileSignalController.setInetCondition(
- mInetCondition ? 1 : 0,
- mValidatedTransports.get(mobileSignalController.getTransportType()) ? 1 : 0);
- }
- mWifiSignalController.setInetCondition(
- mValidatedTransports.get(mWifiSignalController.getTransportType()) ? 1 : 0);
- }
-
- /**
- * Recalculate and update the carrier label.
- */
- void refreshCarrierLabel() {
- Context context = mContext;
-
- WifiSignalController.WifiState wifiState = mWifiSignalController.getState();
- String label = "";
- for (MobileSignalController controller : mMobileSignalControllers.values()) {
- label = controller.getLabel(label, mConnected, mHasMobileDataFeature);
- }
-
- // TODO Simplify this ugliness, some of the flows below shouldn't be possible anymore
- // but stay for the sake of history.
- if (mBluetoothTethered && !mHasMobileDataFeature) {
- label = mContext.getString(R.string.bluetooth_tethered);
- }
-
- if (mEthernetConnected && !mHasMobileDataFeature) {
- label = context.getString(R.string.ethernet_label);
- }
-
- if (mAirplaneMode && !isEmergencyOnly()) {
- // combined values from connected wifi take precedence over airplane mode
- if (wifiState.connected && mHasMobileDataFeature) {
- // Suppress "No internet connection." from mobile if wifi connected.
- label = "";
- } else {
- if (!mHasMobileDataFeature) {
- label = context.getString(
- R.string.status_bar_settings_signal_meter_disconnected);
- }
- }
- } else if (!isMobileDataConnected() && !wifiState.connected && !mBluetoothTethered &&
- !mEthernetConnected && !mHasMobileDataFeature) {
- // Pretty much no connection.
- label = context.getString(R.string.status_bar_settings_signal_meter_disconnected);
- }
-
- // for mobile devices, we always show mobile connection info here (SPN/PLMN)
- // for other devices, we show whatever network is connected
- // This is determined above by references to mHasMobileDataFeature.
- int length = mCarrierListeners.size();
- for (int i = 0; i < length; i++) {
- mCarrierListeners.get(i).setCarrierLabel(label);
+ mobileSignalController.updateConnectivity(mConnectedTransports, mValidatedTransports);
}
- }
-
- private boolean isMobileDataConnected() {
- MobileSignalController controller = getDataController();
- return controller != null ? controller.getState().dataConnected : false;
+ mWifiSignalController.updateConnectivity(mConnectedTransports, mValidatedTransports);
+ mEthernetSignalController.updateConnectivity(mConnectedTransports, mValidatedTransports);
}
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
@@ -671,10 +570,6 @@ public class NetworkControllerImpl extends BroadcastReceiver
pw.print(" hasVoiceCallingFeature()=");
pw.println(hasVoiceCallingFeature());
- pw.println(" - Bluetooth ----");
- pw.print(" mBtReverseTethered=");
- pw.println(mBluetoothTethered);
-
pw.println(" - connectivity ------");
pw.print(" mConnectedTransports=");
pw.println(mConnectedTransports);
@@ -691,10 +586,14 @@ public class NetworkControllerImpl extends BroadcastReceiver
mobileSignalController.dump(pw);
}
mWifiSignalController.dump(pw);
+
+ mEthernetSignalController.dump(pw);
+
+ mAccessPoints.dump(pw);
}
private boolean mDemoMode;
- private int mDemoInetCondition;
+ private boolean mDemoInetCondition;
private WifiSignalController.WifiState mDemoWifiState;
@Override
@@ -703,7 +602,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
if (DEBUG) Log.d(TAG, "Entering demo mode");
unregisterListeners();
mDemoMode = true;
- mDemoInetCondition = mInetCondition ? 1 : 0;
+ mDemoInetCondition = mInetCondition;
mDemoWifiState = mWifiSignalController.getState();
} else if (mDemoMode && command.equals(COMMAND_EXIT)) {
if (DEBUG) Log.d(TAG, "Exiting demo mode");
@@ -715,25 +614,30 @@ public class NetworkControllerImpl extends BroadcastReceiver
controller.resetLastState();
}
mWifiSignalController.resetLastState();
- registerListeners();
+ mReceiverHandler.post(mRegisterListeners);
notifyAllListeners();
- refreshCarrierLabel();
} else if (mDemoMode && command.equals(COMMAND_NETWORK)) {
String airplane = args.getString("airplane");
if (airplane != null) {
boolean show = airplane.equals("show");
- int length = mSignalClusters.size();
- for (int i = 0; i < length; i++) {
- mSignalClusters.get(i).setIsAirplaneMode(show, TelephonyIcons.FLIGHT_MODE_ICON,
- R.string.accessibility_airplane_mode);
- }
+ mCallbackHandler.setIsAirplaneMode(new IconState(show,
+ TelephonyIcons.FLIGHT_MODE_ICON, R.string.accessibility_airplane_mode,
+ mContext));
}
String fully = args.getString("fully");
if (fully != null) {
- mDemoInetCondition = Boolean.parseBoolean(fully) ? 1 : 0;
- mWifiSignalController.setInetCondition(mDemoInetCondition);
+ mDemoInetCondition = Boolean.parseBoolean(fully);
+ BitSet connected = new BitSet();
+
+ if (mDemoInetCondition) {
+ connected.set(mWifiSignalController.mTransportType);
+ }
+ mWifiSignalController.updateConnectivity(connected, connected);
for (MobileSignalController controller : mMobileSignalControllers.values()) {
- controller.setInetCondition(mDemoInetCondition, mDemoInetCondition);
+ if (mDemoInetCondition) {
+ connected.set(controller.mTransportType);
+ }
+ controller.updateConnectivity(connected, connected);
}
}
String wifi = args.getString("wifi");
@@ -750,32 +654,21 @@ public class NetworkControllerImpl extends BroadcastReceiver
}
String sims = args.getString("sims");
if (sims != null) {
- int num = Integer.parseInt(sims);
- List<SubscriptionInfo> subs = new ArrayList<SubscriptionInfo>();
+ int num = MathUtils.constrain(Integer.parseInt(sims), 1, 8);
+ List<SubscriptionInfo> subs = new ArrayList<>();
if (num != mMobileSignalControllers.size()) {
mMobileSignalControllers.clear();
int start = mSubscriptionManager.getActiveSubscriptionInfoCountMax();
for (int i = start /* get out of normal index range */; i < start + num; i++) {
- SubscriptionInfo info = new SubscriptionInfo(i, "", i, "", "", 0, 0, "", 0,
- null, 0, 0, "");
- subs.add(info);
- mMobileSignalControllers.put(i, new MobileSignalController(mContext,
- mConfig, mHasMobileDataFeature, mPhone, mSignalsChangedCallbacks,
- mSignalClusters, this, info));
+ subs.add(addSignalController(i, i));
}
}
- final int n = mSignalClusters.size();
- for (int i = 0; i < n; i++) {
- mSignalClusters.get(i).setSubs(subs);
- }
+ mCallbackHandler.setSubs(subs);
}
String nosim = args.getString("nosim");
if (nosim != null) {
- boolean show = nosim.equals("show");
- final int n = mSignalClusters.size();
- for (int i = 0; i < n; i++) {
- mSignalClusters.get(i).setNoSims(show);
- }
+ mHasNoSims = nosim.equals("show");
+ mCallbackHandler.setNoSims(mHasNoSims);
}
String mobile = args.getString("mobile");
if (mobile != null) {
@@ -783,6 +676,16 @@ public class NetworkControllerImpl extends BroadcastReceiver
String datatype = args.getString("datatype");
String slotString = args.getString("slot");
int slot = TextUtils.isEmpty(slotString) ? 0 : Integer.parseInt(slotString);
+ slot = MathUtils.constrain(slot, 0, 8);
+ // Ensure we have enough sim slots
+ List<SubscriptionInfo> subs = new ArrayList<>();
+ while (mMobileSignalControllers.size() <= slot) {
+ int nextSlot = mMobileSignalControllers.size();
+ subs.add(addSignalController(nextSlot, nextSlot));
+ }
+ if (!subs.isEmpty()) {
+ mCallbackHandler.setSubs(subs);
+ }
// Hack to index linearly for easy use.
MobileSignalController controller = mMobileSignalControllers
.values().toArray(new MobileSignalController[0])[slot];
@@ -809,995 +712,55 @@ public class NetworkControllerImpl extends BroadcastReceiver
controller.getState().enabled = show;
controller.notifyListeners();
}
- refreshCarrierLabel();
- }
- }
-
- private final OnSubscriptionsChangedListener mSubscriptionListener =
- new OnSubscriptionsChangedListener() {
- @Override
- public void onSubscriptionsChanged() {
- updateMobileControllers();
- };
- };
-
- // TODO: Move to its own file.
- static class WifiSignalController extends
- SignalController<WifiSignalController.WifiState, SignalController.IconGroup> {
- private final WifiManager mWifiManager;
- private final AsyncChannel mWifiChannel;
- private final boolean mHasMobileData;
-
- public WifiSignalController(Context context, boolean hasMobileData,
- List<NetworkSignalChangedCallback> signalCallbacks,
- List<SignalCluster> signalClusters, NetworkControllerImpl networkController) {
- super("WifiSignalController", context, NetworkCapabilities.TRANSPORT_WIFI,
- signalCallbacks, signalClusters, networkController);
- mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
- mHasMobileData = hasMobileData;
- Handler handler = new WifiHandler();
- mWifiChannel = new AsyncChannel();
- Messenger wifiMessenger = mWifiManager.getWifiServiceMessenger();
- if (wifiMessenger != null) {
- mWifiChannel.connect(context, handler, wifiMessenger);
- }
- // WiFi only has one state.
- mCurrentState.iconGroup = mLastState.iconGroup = new IconGroup(
- "Wi-Fi Icons",
- WifiIcons.WIFI_SIGNAL_STRENGTH,
- WifiIcons.QS_WIFI_SIGNAL_STRENGTH,
- AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH,
- WifiIcons.WIFI_NO_NETWORK,
- WifiIcons.QS_WIFI_NO_NETWORK,
- WifiIcons.WIFI_NO_NETWORK,
- WifiIcons.QS_WIFI_NO_NETWORK,
- AccessibilityContentDescriptions.WIFI_NO_CONNECTION
- );
- }
-
- @Override
- protected WifiState cleanState() {
- return new WifiState();
- }
-
- @Override
- public void notifyListeners() {
- // only show wifi in the cluster if connected or if wifi-only
- boolean wifiVisible = mCurrentState.enabled
- && (mCurrentState.connected || !mHasMobileData);
- String wifiDesc = wifiVisible ? mCurrentState.ssid : null;
- boolean ssidPresent = wifiVisible && mCurrentState.ssid != null;
- String contentDescription = getStringIfExists(getContentDescription());
- int length = mSignalsChangedCallbacks.size();
- for (int i = 0; i < length; i++) {
- mSignalsChangedCallbacks.get(i).onWifiSignalChanged(mCurrentState.enabled,
- mCurrentState.connected, getQsCurrentIconId(),
- ssidPresent && mCurrentState.activityIn,
- ssidPresent && mCurrentState.activityOut, contentDescription, wifiDesc);
- }
-
- int signalClustersLength = mSignalClusters.size();
- for (int i = 0; i < signalClustersLength; i++) {
- mSignalClusters.get(i).setWifiIndicators(wifiVisible, getCurrentIconId(),
- contentDescription);
- }
- }
-
- /**
- * Extract wifi state directly from broadcasts about changes in wifi state.
- */
- public void handleBroadcast(Intent intent) {
- String action = intent.getAction();
- if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
- mCurrentState.enabled = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
- WifiManager.WIFI_STATE_UNKNOWN) == WifiManager.WIFI_STATE_ENABLED;
- } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
- final NetworkInfo networkInfo = (NetworkInfo)
- intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
- mCurrentState.connected = networkInfo != null && networkInfo.isConnected();
- // If Connected grab the signal strength and ssid.
- if (mCurrentState.connected) {
- // try getting it out of the intent first
- WifiInfo info = intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO) != null
- ? (WifiInfo) intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO)
- : mWifiManager.getConnectionInfo();
- if (info != null) {
- mCurrentState.ssid = getSsid(info);
- } else {
- mCurrentState.ssid = null;
- }
- } else if (!mCurrentState.connected) {
- mCurrentState.ssid = null;
- }
- } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) {
- // Default to -200 as its below WifiManager.MIN_RSSI.
- mCurrentState.rssi = intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200);
- mCurrentState.level = WifiManager.calculateSignalLevel(
- mCurrentState.rssi, WifiIcons.WIFI_LEVEL_COUNT);
- }
-
- notifyListenersIfNecessary();
- }
-
- private String getSsid(WifiInfo info) {
- String ssid = info.getSSID();
- if (ssid != null) {
- return ssid;
- }
- // OK, it's not in the connectionInfo; we have to go hunting for it
- List<WifiConfiguration> networks = mWifiManager.getConfiguredNetworks();
- int length = networks.size();
- for (int i = 0; i < length; i++) {
- if (networks.get(i).networkId == info.getNetworkId()) {
- return networks.get(i).SSID;
- }
- }
- return null;
- }
-
- @VisibleForTesting
- void setActivity(int wifiActivity) {
- mCurrentState.activityIn = wifiActivity == WifiManager.DATA_ACTIVITY_INOUT
- || wifiActivity == WifiManager.DATA_ACTIVITY_IN;
- mCurrentState.activityOut = wifiActivity == WifiManager.DATA_ACTIVITY_INOUT
- || wifiActivity == WifiManager.DATA_ACTIVITY_OUT;
- notifyListenersIfNecessary();
- }
-
- /**
- * Handler to receive the data activity on wifi.
- */
- class WifiHandler extends Handler {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
- if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
- mWifiChannel.sendMessage(Message.obtain(this,
- AsyncChannel.CMD_CHANNEL_FULL_CONNECTION));
- } else {
- Log.e(mTag, "Failed to connect to wifi");
- }
- break;
- case WifiManager.DATA_ACTIVITY_NOTIFICATION:
- setActivity(msg.arg1);
- break;
- default:
- // Ignore
- break;
+ String carrierNetworkChange = args.getString("carriernetworkchange");
+ if (carrierNetworkChange != null) {
+ boolean show = carrierNetworkChange.equals("show");
+ for (MobileSignalController controller : mMobileSignalControllers.values()) {
+ controller.setCarrierNetworkChangeMode(show);
}
}
}
-
- static class WifiState extends SignalController.State {
- String ssid;
-
- @Override
- public void copyFrom(State s) {
- super.copyFrom(s);
- WifiState state = (WifiState) s;
- ssid = state.ssid;
- }
-
- @Override
- protected void toString(StringBuilder builder) {
- super.toString(builder);
- builder.append(',').append("ssid=").append(ssid);
- }
-
- @Override
- public boolean equals(Object o) {
- return super.equals(o)
- && Objects.equals(((WifiState) o).ssid, ssid);
- }
- }
}
- // TODO: Move to its own file.
- public static class MobileSignalController extends SignalController<
- MobileSignalController.MobileState, MobileSignalController.MobileIconGroup> {
- private final TelephonyManager mPhone;
- private final String mNetworkNameDefault;
- private final String mNetworkNameSeparator;
- @VisibleForTesting
- final PhoneStateListener mPhoneStateListener;
- // Save entire info for logging, we only use the id.
- private final SubscriptionInfo mSubscriptionInfo;
-
- // @VisibleForDemoMode
- final SparseArray<MobileIconGroup> mNetworkToIconLookup;
-
- // Since some pieces of the phone state are interdependent we store it locally,
- // this could potentially become part of MobileState for simplification/complication
- // of code.
- private IccCardConstants.State mSimState = IccCardConstants.State.READY;
- private int mDataNetType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
- private int mDataState = TelephonyManager.DATA_DISCONNECTED;
- private ServiceState mServiceState;
- private SignalStrength mSignalStrength;
- private MobileIconGroup mDefaultIcons;
- private Config mConfig;
-
- // TODO: Reduce number of vars passed in, if we have the NetworkController, probably don't
- // need listener lists anymore.
- public MobileSignalController(Context context, Config config, boolean hasMobileData,
- TelephonyManager phone, List<NetworkSignalChangedCallback> signalCallbacks,
- List<SignalCluster> signalClusters, NetworkControllerImpl networkController,
- SubscriptionInfo info) {
- super("MobileSignalController(" + info.getSubscriptionId() + ")", context,
- NetworkCapabilities.TRANSPORT_CELLULAR, signalCallbacks, signalClusters,
- networkController);
- mNetworkToIconLookup = new SparseArray<>();
- mConfig = config;
- mPhone = phone;
- mSubscriptionInfo = info;
- mPhoneStateListener = new MobilePhoneStateListener(info.getSubscriptionId());
- mNetworkNameSeparator = getStringIfExists(R.string.status_bar_network_name_separator);
- mNetworkNameDefault = getStringIfExists(
- com.android.internal.R.string.lockscreen_carrier_default);
-
- mapIconSets();
-
- mLastState.networkName = mCurrentState.networkName = mNetworkNameDefault;
- mLastState.enabled = mCurrentState.enabled = hasMobileData;
- mLastState.iconGroup = mCurrentState.iconGroup = mDefaultIcons;
- // Get initial data sim state.
- updateDataSim();
- }
-
- public void setConfiguration(Config config) {
- mConfig = config;
- mapIconSets();
- updateTelephony();
- }
-
- /**
- * Get (the mobile parts of) the carrier string.
- *
- * @param currentLabel can be used for concatenation, currently just empty
- * @param connected whether the device has connection to the internet at all
- * @param isMobileLabel whether to always return the network or just when data is connected
- */
- public String getLabel(String currentLabel, boolean connected, boolean isMobileLabel) {
- if (!mCurrentState.enabled) {
- return "";
- } else {
- String mobileLabel = "";
- // We want to show the carrier name if in service and either:
- // - We are connected to mobile data, or
- // - We are not connected to mobile data, as long as the *reason* packets are not
- // being routed over that link is that we have better connectivity via wifi.
- // If data is disconnected for some other reason but wifi (or ethernet/bluetooth)
- // is connected, we show nothing.
- // Otherwise (nothing connected) we show "No internet connection".
- if (mCurrentState.dataConnected) {
- mobileLabel = mCurrentState.networkName;
- } else if (connected || mCurrentState.isEmergency) {
- if (mCurrentState.connected || mCurrentState.isEmergency) {
- // The isEmergencyOnly test covers the case of a phone with no SIM
- mobileLabel = mCurrentState.networkName;
- }
- } else {
- mobileLabel = mContext.getString(
- R.string.status_bar_settings_signal_meter_disconnected);
- }
-
- if (currentLabel.length() != 0) {
- currentLabel = currentLabel + mNetworkNameSeparator;
- }
- // Now for things that should only be shown when actually using mobile data.
- if (isMobileLabel) {
- return currentLabel + mobileLabel;
- } else {
- return currentLabel
- + (mCurrentState.dataConnected ? mobileLabel : currentLabel);
- }
- }
- }
-
- public int getDataContentDescription() {
- return getIcons().mDataContentDescription;
- }
-
- public void setAirplaneMode(boolean airplaneMode) {
- mCurrentState.airplaneMode = airplaneMode;
- notifyListenersIfNecessary();
- }
-
- public void setInetCondition(int inetCondition, int inetConditionForNetwork) {
- // For mobile data, use general inet condition for phone signal indexing,
- // and network specific for data indexing (I think this might be a bug, but
- // keeping for now).
- // TODO: Update with explanation of why.
- mCurrentState.inetForNetwork = inetConditionForNetwork;
- setInetCondition(inetCondition);
- }
-
- /**
- * Start listening for phone state changes.
- */
- public void registerListener() {
- mPhone.listen(mPhoneStateListener,
- PhoneStateListener.LISTEN_SERVICE_STATE
- | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS
- | PhoneStateListener.LISTEN_CALL_STATE
- | PhoneStateListener.LISTEN_DATA_CONNECTION_STATE
- | PhoneStateListener.LISTEN_DATA_ACTIVITY);
- }
-
- /**
- * Stop listening for phone state changes.
- */
- public void unregisterListener() {
- mPhone.listen(mPhoneStateListener, 0);
- }
-
- /**
- * Produce a mapping of data network types to icon groups for simple and quick use in
- * updateTelephony.
- */
- private void mapIconSets() {
- mNetworkToIconLookup.clear();
-
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_0, TelephonyIcons.THREE_G);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_A, TelephonyIcons.THREE_G);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_B, TelephonyIcons.THREE_G);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EHRPD, TelephonyIcons.THREE_G);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UMTS, TelephonyIcons.THREE_G);
-
- if (!mConfig.showAtLeast3G) {
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UNKNOWN,
- TelephonyIcons.UNKNOWN);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EDGE, TelephonyIcons.E);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_CDMA, TelephonyIcons.ONE_X);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_1xRTT, TelephonyIcons.ONE_X);
-
- mDefaultIcons = TelephonyIcons.G;
- } else {
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UNKNOWN,
- TelephonyIcons.THREE_G);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EDGE,
- TelephonyIcons.THREE_G);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_CDMA,
- TelephonyIcons.THREE_G);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_1xRTT,
- TelephonyIcons.THREE_G);
- mDefaultIcons = TelephonyIcons.THREE_G;
- }
-
- MobileIconGroup hGroup = TelephonyIcons.THREE_G;
- if (mConfig.hspaDataDistinguishable) {
- hGroup = TelephonyIcons.H;
- }
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSDPA, hGroup);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSUPA, hGroup);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSPA, hGroup);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSPAP, hGroup);
-
- if (mConfig.show4gForLte) {
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE, TelephonyIcons.FOUR_G);
- } else {
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE, TelephonyIcons.LTE);
- }
- }
-
- @Override
- public void notifyListeners() {
- MobileIconGroup icons = getIcons();
-
- String contentDescription = getStringIfExists(getContentDescription());
- String dataContentDescription = getStringIfExists(icons.mDataContentDescription);
-
- boolean showDataIcon = mCurrentState.dataConnected && mCurrentState.inetForNetwork != 0
- || mCurrentState.iconGroup == TelephonyIcons.ROAMING;
-
- // Only send data sim callbacks to QS.
- if (mCurrentState.dataSim) {
- int qsTypeIcon = showDataIcon ? icons.mQsDataType[mCurrentState.inetForNetwork] : 0;
- int length = mSignalsChangedCallbacks.size();
- for (int i = 0; i < length; i++) {
- mSignalsChangedCallbacks.get(i).onMobileDataSignalChanged(mCurrentState.enabled
- && !mCurrentState.isEmergency,
- getQsCurrentIconId(), contentDescription,
- qsTypeIcon,
- mCurrentState.dataConnected && mCurrentState.activityIn,
- mCurrentState.dataConnected && mCurrentState.activityOut,
- dataContentDescription,
- mCurrentState.isEmergency ? null : mCurrentState.networkName,
- // Only wide if actually showing something.
- icons.mIsWide && qsTypeIcon != 0);
- }
- }
- int typeIcon = showDataIcon ? icons.mDataType : 0;
- int signalClustersLength = mSignalClusters.size();
- for (int i = 0; i < signalClustersLength; i++) {
- mSignalClusters.get(i).setMobileDataIndicators(
- mCurrentState.enabled && !mCurrentState.airplaneMode,
- getCurrentIconId(),
- typeIcon,
- contentDescription,
- dataContentDescription,
- // Only wide if actually showing something.
- icons.mIsWide && typeIcon != 0,
- mSubscriptionInfo.getSubscriptionId());
- }
- }
-
- @Override
- protected MobileState cleanState() {
- return new MobileState();
- }
-
- private boolean hasService() {
- if (mServiceState != null) {
- // Consider the device to be in service if either voice or data
- // service is available. Some SIM cards are marketed as data-only
- // and do not support voice service, and on these SIM cards, we
- // want to show signal bars for data service as well as the "no
- // service" or "emergency calls only" text that indicates that voice
- // is not available.
- switch (mServiceState.getVoiceRegState()) {
- case ServiceState.STATE_POWER_OFF:
- return false;
- case ServiceState.STATE_OUT_OF_SERVICE:
- case ServiceState.STATE_EMERGENCY_ONLY:
- return mServiceState.getDataRegState() == ServiceState.STATE_IN_SERVICE;
- default:
- return true;
- }
- } else {
- return false;
- }
- }
-
- private boolean isCdma() {
- return (mSignalStrength != null) && !mSignalStrength.isGsm();
- }
-
- public boolean isEmergencyOnly() {
- return (mServiceState != null && mServiceState.isEmergencyOnly());
- }
-
- private boolean isRoaming() {
- if (isCdma()) {
- final int iconMode = mServiceState.getCdmaEriIconMode();
- return mServiceState.getCdmaEriIconIndex() != EriInfo.ROAMING_INDICATOR_OFF
- && (iconMode == EriInfo.ROAMING_ICON_MODE_NORMAL
- || iconMode == EriInfo.ROAMING_ICON_MODE_FLASH);
- } else {
- return mServiceState != null && mServiceState.getRoaming();
- }
- }
-
- public void handleBroadcast(Intent intent) {
- String action = intent.getAction();
- if (action.equals(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION)) {
- updateNetworkName(intent.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false),
- intent.getStringExtra(TelephonyIntents.EXTRA_SPN),
- intent.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false),
- intent.getStringExtra(TelephonyIntents.EXTRA_PLMN));
- notifyListenersIfNecessary();
- } else if (action.equals(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)) {
- updateDataSim();
- }
- }
-
- private void updateDataSim() {
- int defaultDataSub = SubscriptionManager.getDefaultDataSubId();
- if (SubscriptionManager.isValidSubscriptionId(defaultDataSub)) {
- mCurrentState.dataSim = defaultDataSub == mSubscriptionInfo.getSubscriptionId();
- } else {
- // There doesn't seem to be a data sim selected, however if
- // there isn't a MobileSignalController with dataSim set, then
- // QS won't get any callbacks and will be blank. Instead
- // lets just assume we are the data sim (which will basically
- // show one at random) in QS until one is selected. The user
- // should pick one soon after, so we shouldn't be in this state
- // for long.
- mCurrentState.dataSim = true;
- }
- notifyListenersIfNecessary();
- }
-
- /**
- * Updates the network's name based on incoming spn and plmn.
- */
- void updateNetworkName(boolean showSpn, String spn, boolean showPlmn, String plmn) {
- if (CHATTY) {
- Log.d("CarrierLabel", "updateNetworkName showSpn=" + showSpn + " spn=" + spn
- + " showPlmn=" + showPlmn + " plmn=" + plmn);
- }
- StringBuilder str = new StringBuilder();
- if (showPlmn && plmn != null) {
- str.append(plmn);
- }
- if (showSpn && spn != null) {
- if (str.length() != 0) {
- str.append(mNetworkNameSeparator);
- }
- str.append(spn);
- }
- if (str.length() != 0) {
- mCurrentState.networkName = str.toString();
- } else {
- mCurrentState.networkName = mNetworkNameDefault;
- }
- }
-
- /**
- * Updates the current state based on mServiceState, mSignalStrength, mDataNetType,
- * mDataState, and mSimState. It should be called any time one of these is updated.
- * This will call listeners if necessary.
- */
- private final void updateTelephony() {
- if (DEBUG) {
- Log.d(TAG, "updateTelephonySignalStrength: hasService=" + hasService()
- + " ss=" + mSignalStrength);
- }
- mCurrentState.connected = hasService() && mSignalStrength != null;
- if (mCurrentState.connected) {
- if (!mSignalStrength.isGsm() && mConfig.alwaysShowCdmaRssi) {
- mCurrentState.level = mSignalStrength.getCdmaLevel();
- } else {
- mCurrentState.level = mSignalStrength.getLevel();
- }
- }
- if (mNetworkToIconLookup.indexOfKey(mDataNetType) >= 0) {
- mCurrentState.iconGroup = mNetworkToIconLookup.get(mDataNetType);
- } else {
- mCurrentState.iconGroup = mDefaultIcons;
- }
- mCurrentState.dataConnected = mCurrentState.connected
- && mDataState == TelephonyManager.DATA_CONNECTED;
-
- if (isRoaming()) {
- mCurrentState.iconGroup = TelephonyIcons.ROAMING;
- }
- if (isEmergencyOnly() != mCurrentState.isEmergency) {
- mCurrentState.isEmergency = isEmergencyOnly();
- mNetworkController.recalculateEmergency();
- }
- // Fill in the network name if we think we have it.
- if (mCurrentState.networkName == mNetworkNameDefault && mServiceState != null
- && mServiceState.getOperatorAlphaShort() != null) {
- mCurrentState.networkName = mServiceState.getOperatorAlphaShort();
- }
- notifyListenersIfNecessary();
- }
-
- @VisibleForTesting
- void setActivity(int activity) {
- mCurrentState.activityIn = activity == TelephonyManager.DATA_ACTIVITY_INOUT
- || activity == TelephonyManager.DATA_ACTIVITY_IN;
- mCurrentState.activityOut = activity == TelephonyManager.DATA_ACTIVITY_INOUT
- || activity == TelephonyManager.DATA_ACTIVITY_OUT;
- notifyListenersIfNecessary();
- }
+ private SubscriptionInfo addSignalController(int id, int simSlotIndex) {
+ SubscriptionInfo info = new SubscriptionInfo(id, "", simSlotIndex, "", "", 0, 0, "", 0,
+ null, 0, 0, "");
+ mMobileSignalControllers.put(id, new MobileSignalController(mContext,
+ mConfig, mHasMobileDataFeature, mPhone, mCallbackHandler, this, info,
+ mSubDefaults, mReceiverHandler.getLooper()));
+ return info;
+ }
+ private class SubListener extends OnSubscriptionsChangedListener {
@Override
- public void dump(PrintWriter pw) {
- super.dump(pw);
- pw.println(" mSubscription=" + mSubscriptionInfo + ",");
- pw.println(" mServiceState=" + mServiceState + ",");
- pw.println(" mSignalStrength=" + mSignalStrength + ",");
- pw.println(" mDataState=" + mDataState + ",");
- pw.println(" mDataNetType=" + mDataNetType + ",");
- }
-
- class MobilePhoneStateListener extends PhoneStateListener {
- public MobilePhoneStateListener(int subId) {
- super(subId);
- }
-
- @Override
- public void onSignalStrengthsChanged(SignalStrength signalStrength) {
- if (DEBUG) {
- Log.d(mTag, "onSignalStrengthsChanged signalStrength=" + signalStrength +
- ((signalStrength == null) ? "" : (" level=" + signalStrength.getLevel())));
- }
- mSignalStrength = signalStrength;
- updateTelephony();
- }
-
- @Override
- public void onServiceStateChanged(ServiceState state) {
- if (DEBUG) {
- Log.d(mTag, "onServiceStateChanged voiceState=" + state.getVoiceRegState()
- + " dataState=" + state.getDataRegState());
- }
- mServiceState = state;
- updateTelephony();
- }
-
- @Override
- public void onDataConnectionStateChanged(int state, int networkType) {
- if (DEBUG) {
- Log.d(mTag, "onDataConnectionStateChanged: state=" + state
- + " type=" + networkType);
- }
- mDataState = state;
- mDataNetType = networkType;
- updateTelephony();
- }
-
- @Override
- public void onDataActivity(int direction) {
- if (DEBUG) {
- Log.d(mTag, "onDataActivity: direction=" + direction);
- }
- setActivity(direction);
- }
- };
-
- static class MobileIconGroup extends SignalController.IconGroup {
- final int mDataContentDescription; // mContentDescriptionDataType
- final int mDataType;
- final boolean mIsWide;
- final int[] mQsDataType;
-
- public MobileIconGroup(String name, int[][] sbIcons, int[][] qsIcons, int[] contentDesc,
- int sbNullState, int qsNullState, int sbDiscState, int qsDiscState,
- int discContentDesc, int dataContentDesc, int dataType, boolean isWide,
- int[] qsDataType) {
- super(name, sbIcons, qsIcons, contentDesc, sbNullState, qsNullState, sbDiscState,
- qsDiscState, discContentDesc);
- mDataContentDescription = dataContentDesc;
- mDataType = dataType;
- mIsWide = isWide;
- mQsDataType = qsDataType;
- }
- }
-
- static class MobileState extends SignalController.State {
- String networkName;
- boolean dataSim;
- boolean dataConnected;
- boolean isEmergency;
- boolean airplaneMode;
- int inetForNetwork;
-
- @Override
- public void copyFrom(State s) {
- super.copyFrom(s);
- MobileState state = (MobileState) s;
- dataSim = state.dataSim;
- networkName = state.networkName;
- dataConnected = state.dataConnected;
- inetForNetwork = state.inetForNetwork;
- isEmergency = state.isEmergency;
- airplaneMode = state.airplaneMode;
- }
-
- @Override
- protected void toString(StringBuilder builder) {
- super.toString(builder);
- builder.append(',');
- builder.append("dataSim=").append(dataSim).append(',');
- builder.append("networkName=").append(networkName).append(',');
- builder.append("dataConnected=").append(dataConnected).append(',');
- builder.append("inetForNetwork=").append(inetForNetwork).append(',');
- builder.append("isEmergency=").append(isEmergency).append(',');
- builder.append("airplaneMode=").append(airplaneMode);
- }
-
- @Override
- public boolean equals(Object o) {
- return super.equals(o)
- && Objects.equals(((MobileState) o).networkName, networkName)
- && ((MobileState) o).dataSim == dataSim
- && ((MobileState) o).dataConnected == dataConnected
- && ((MobileState) o).isEmergency == isEmergency
- && ((MobileState) o).airplaneMode == airplaneMode
- && ((MobileState) o).inetForNetwork == inetForNetwork;
- }
+ public void onSubscriptionsChanged() {
+ updateMobileControllers();
}
}
/**
- * Common base class for handling signal for both wifi and mobile data.
+ * Used to register listeners from the BG Looper, this way the PhoneStateListeners that
+ * get created will also run on the BG Looper.
*/
- static abstract class SignalController<T extends SignalController.State,
- I extends SignalController.IconGroup> {
- protected final String mTag;
- protected final T mCurrentState;
- protected final T mLastState;
- protected final int mTransportType;
- protected final Context mContext;
- // The owner of the SignalController (i.e. NetworkController will maintain the following
- // lists and call notifyListeners whenever the list has changed to ensure everyone
- // is aware of current state.
- protected final List<NetworkSignalChangedCallback> mSignalsChangedCallbacks;
- protected final List<SignalCluster> mSignalClusters;
- protected final NetworkControllerImpl mNetworkController;
-
- // Save the previous HISTORY_SIZE states for logging.
- private final State[] mHistory;
- // Where to copy the next state into.
- private int mHistoryIndex;
-
- public SignalController(String tag, Context context, int type,
- List<NetworkSignalChangedCallback> signalCallbacks,
- List<SignalCluster> signalClusters, NetworkControllerImpl networkController) {
- mTag = TAG + "." + tag;
- mNetworkController = networkController;
- mTransportType = type;
- mContext = context;
- mSignalsChangedCallbacks = signalCallbacks;
- mSignalClusters = signalClusters;
- mCurrentState = cleanState();
- mLastState = cleanState();
- if (RECORD_HISTORY) {
- mHistory = new State[HISTORY_SIZE];
- for (int i = 0; i < HISTORY_SIZE; i++) {
- mHistory[i] = cleanState();
- }
- }
- }
-
- public T getState() {
- return mCurrentState;
- }
-
- public int getTransportType() {
- return mTransportType;
- }
-
- public void setInetCondition(int inetCondition) {
- mCurrentState.inetCondition = inetCondition;
- notifyListenersIfNecessary();
- }
-
- /**
- * Used at the end of demo mode to clear out any ugly state that it has created.
- * Since we haven't had any callbacks, then isDirty will not have been triggered,
- * so we can just take the last good state directly from there.
- *
- * Used for demo mode.
- */
- void resetLastState() {
- mCurrentState.copyFrom(mLastState);
- }
-
- /**
- * Determines if the state of this signal controller has changed and
- * needs to trigger callbacks related to it.
- */
- public boolean isDirty() {
- if (!mLastState.equals(mCurrentState)) {
- if (DEBUG) {
- Log.d(mTag, "Change in state from: " + mLastState + "\n"
- + "\tto: " + mCurrentState);
- }
- return true;
- }
- return false;
- }
-
- public void saveLastState() {
- if (RECORD_HISTORY) {
- recordLastState();
- }
- // Updates the current time.
- mCurrentState.time = System.currentTimeMillis();
- mLastState.copyFrom(mCurrentState);
- }
-
- /**
- * Gets the signal icon for QS based on current state of connected, enabled, and level.
- */
- public int getQsCurrentIconId() {
- if (mCurrentState.connected) {
- return getIcons().mQsIcons[mCurrentState.inetCondition][mCurrentState.level];
- } else if (mCurrentState.enabled) {
- return getIcons().mQsDiscState;
- } else {
- return getIcons().mQsNullState;
- }
- }
-
- /**
- * Gets the signal icon for SB based on current state of connected, enabled, and level.
- */
- public int getCurrentIconId() {
- if (mCurrentState.connected) {
- return getIcons().mSbIcons[mCurrentState.inetCondition][mCurrentState.level];
- } else if (mCurrentState.enabled) {
- return getIcons().mSbDiscState;
- } else {
- return getIcons().mSbNullState;
- }
- }
-
- /**
- * Gets the content description id for the signal based on current state of connected and
- * level.
- */
- public int getContentDescription() {
- if (mCurrentState.connected) {
- return getIcons().mContentDesc[mCurrentState.level];
- } else {
- return getIcons().mDiscContentDesc;
- }
- }
-
- public void notifyListenersIfNecessary() {
- if (isDirty()) {
- saveLastState();
- notifyListeners();
- mNetworkController.refreshCarrierLabel();
- }
- }
-
- /**
- * Returns the resource if resId is not 0, and an empty string otherwise.
- */
- protected String getStringIfExists(int resId) {
- return resId != 0 ? mContext.getString(resId) : "";
- }
-
- protected I getIcons() {
- return (I) mCurrentState.iconGroup;
- }
-
- /**
- * Saves the last state of any changes, so we can log the current
- * and last value of any state data.
- */
- protected void recordLastState() {
- mHistory[mHistoryIndex++ & (HISTORY_SIZE - 1)].copyFrom(mLastState);
- }
-
- public void dump(PrintWriter pw) {
- pw.println(" - " + mTag + " -----");
- pw.println(" Current State: " + mCurrentState);
- if (RECORD_HISTORY) {
- // Count up the states that actually contain time stamps, and only display those.
- int size = 0;
- for (int i = 0; i < HISTORY_SIZE; i++) {
- if (mHistory[i].time != 0) size++;
- }
- // Print out the previous states in ordered number.
- for (int i = mHistoryIndex + HISTORY_SIZE - 1;
- i >= mHistoryIndex + HISTORY_SIZE - size; i--) {
- pw.println(" Previous State(" + (mHistoryIndex + HISTORY_SIZE - i) + ": "
- + mHistory[i & (HISTORY_SIZE - 1)]);
- }
- }
- }
-
- /**
- * Trigger callbacks based on current state. The callbacks should be completely
- * based on current state, and only need to be called in the scenario where
- * mCurrentState != mLastState.
- */
- public abstract void notifyListeners();
-
- /**
- * Generate a blank T.
- */
- protected abstract T cleanState();
-
- /*
- * Holds icons for a given state. Arrays are generally indexed as inet
- * state (full connectivity or not) first, and second dimension as
- * signal strength.
- */
- static class IconGroup {
- final int[][] mSbIcons;
- final int[][] mQsIcons;
- final int[] mContentDesc;
- final int mSbNullState;
- final int mQsNullState;
- final int mSbDiscState;
- final int mQsDiscState;
- final int mDiscContentDesc;
- // For logging.
- final String mName;
-
- public IconGroup(String name, int[][] sbIcons, int[][] qsIcons, int[] contentDesc,
- int sbNullState, int qsNullState, int sbDiscState, int qsDiscState,
- int discContentDesc) {
- mName = name;
- mSbIcons = sbIcons;
- mQsIcons = qsIcons;
- mContentDesc = contentDesc;
- mSbNullState = sbNullState;
- mQsNullState = qsNullState;
- mSbDiscState = sbDiscState;
- mQsDiscState = qsDiscState;
- mDiscContentDesc = discContentDesc;
- }
-
- @Override
- public String toString() {
- return "IconGroup(" + mName + ")";
- }
- }
-
- static class State {
- boolean connected;
- boolean enabled;
- boolean activityIn;
- boolean activityOut;
- int level;
- IconGroup iconGroup;
- int inetCondition;
- int rssi; // Only for logging.
-
- // Not used for comparison, just used for logging.
- long time;
-
- public void copyFrom(State state) {
- connected = state.connected;
- enabled = state.enabled;
- level = state.level;
- iconGroup = state.iconGroup;
- inetCondition = state.inetCondition;
- activityIn = state.activityIn;
- activityOut = state.activityOut;
- rssi = state.rssi;
- time = state.time;
- }
-
- @Override
- public String toString() {
- if (time != 0) {
- StringBuilder builder = new StringBuilder();
- toString(builder);
- return builder.toString();
- } else {
- return "Empty " + getClass().getSimpleName();
- }
- }
-
- protected void toString(StringBuilder builder) {
- builder.append("connected=").append(connected).append(',')
- .append("enabled=").append(enabled).append(',')
- .append("level=").append(level).append(',')
- .append("inetCondition=").append(inetCondition).append(',')
- .append("iconGroup=").append(iconGroup).append(',')
- .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));
- }
-
- @Override
- public boolean equals(Object o) {
- if (!o.getClass().equals(getClass())) {
- return false;
- }
- State other = (State) o;
- return other.connected == connected
- && other.enabled == enabled
- && other.level == level
- && other.inetCondition == inetCondition
- && other.iconGroup == iconGroup
- && other.activityIn == activityIn
- && other.activityOut == activityOut
- && other.rssi == rssi;
- }
+ private final Runnable mRegisterListeners = new Runnable() {
+ @Override
+ public void run() {
+ registerListeners();
}
- }
-
- public interface SignalCluster {
- void setWifiIndicators(boolean visible, int strengthIcon, String contentDescription);
-
- void setMobileDataIndicators(boolean visible, int strengthIcon, int typeIcon,
- String contentDescription, String typeContentDescription, boolean isTypeIconWide,
- int subId);
- void setSubs(List<SubscriptionInfo> subs);
- void setNoSims(boolean show);
-
- void setIsAirplaneMode(boolean is, int airplaneIcon, int contentDescription);
- }
+ };
public interface EmergencyListener {
void setEmergencyCallsOnly(boolean emergencyOnly);
}
- public interface CarrierLabelListener {
- void setCarrierLabel(String label);
+ public static class SubscriptionDefaults {
+ public int getDefaultVoiceSubId() {
+ return SubscriptionManager.getDefaultVoiceSubId();
+ }
+
+ public int getDefaultDataSubId() {
+ return SubscriptionManager.getDefaultDataSubId();
+ }
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PreviewInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PreviewInflater.java
index 030cd6d..93d0ec3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PreviewInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PreviewInflater.java
@@ -16,16 +16,19 @@
package com.android.systemui.statusbar.policy;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import android.os.UserHandle;
+import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.statusbar.phone.KeyguardPreviewContainer;
import java.util.List;
@@ -49,6 +52,15 @@ public class PreviewInflater {
public View inflatePreview(Intent intent) {
WidgetInfo info = getWidgetInfo(intent);
+ return inflatePreview(info);
+ }
+
+ public View inflatePreviewFromService(ComponentName componentName) {
+ WidgetInfo info = getWidgetInfoFromService(componentName);
+ return inflatePreview(info);
+ }
+
+ private KeyguardPreviewContainer inflatePreview(WidgetInfo info) {
if (info == null) {
return null;
}
@@ -76,46 +88,79 @@ public class PreviewInflater {
return widgetView;
}
- private WidgetInfo getWidgetInfo(Intent intent) {
+ private WidgetInfo getWidgetInfoFromService(ComponentName componentName) {
+ PackageManager packageManager = mContext.getPackageManager();
+ // Look for the preview specified in the service meta-data
+ try {
+ Bundle metaData = packageManager.getServiceInfo(
+ componentName, PackageManager.GET_META_DATA).metaData;
+ return getWidgetInfoFromMetaData(componentName.getPackageName(), metaData);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "Failed to load preview; " + componentName.flattenToShortString()
+ + " not found", e);
+ }
+ return null;
+ }
+
+ private WidgetInfo getWidgetInfoFromMetaData(String contextPackage,
+ Bundle metaData) {
+ if (metaData == null) {
+ return null;
+ }
+ int layoutId = metaData.getInt(META_DATA_KEYGUARD_LAYOUT);
+ if (layoutId == 0) {
+ return null;
+ }
WidgetInfo info = new WidgetInfo();
+ info.contextPackage = contextPackage;
+ info.layoutId = layoutId;
+ return info;
+ }
+
+ private WidgetInfo getWidgetInfo(Intent intent) {
PackageManager packageManager = mContext.getPackageManager();
final List<ResolveInfo> appList = packageManager.queryIntentActivitiesAsUser(
- intent, PackageManager.MATCH_DEFAULT_ONLY, mLockPatternUtils.getCurrentUser());
+ intent, PackageManager.MATCH_DEFAULT_ONLY, KeyguardUpdateMonitor.getCurrentUser());
if (appList.size() == 0) {
return null;
}
ResolveInfo resolved = packageManager.resolveActivityAsUser(intent,
PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_META_DATA,
- mLockPatternUtils.getCurrentUser());
+ KeyguardUpdateMonitor.getCurrentUser());
if (wouldLaunchResolverActivity(resolved, appList)) {
return null;
}
if (resolved == null || resolved.activityInfo == null) {
return null;
}
- if (resolved.activityInfo.metaData == null || resolved.activityInfo.metaData.isEmpty()) {
- return null;
- }
- int layoutId = resolved.activityInfo.metaData.getInt(META_DATA_KEYGUARD_LAYOUT);
- if (layoutId == 0) {
- return null;
- }
- info.contextPackage = resolved.activityInfo.packageName;
- info.layoutId = layoutId;
- return info;
+ return getWidgetInfoFromMetaData(resolved.activityInfo.packageName,
+ resolved.activityInfo.metaData);
}
public static boolean wouldLaunchResolverActivity(Context ctx, Intent intent,
int currentUserId) {
+ return getTargetActivityInfo(ctx, intent, currentUserId) == null;
+ }
+
+ /**
+ * @return the target activity info of the intent it resolves to a specific package or
+ * {@code null} if it resolved to the resolver activity
+ */
+ public static ActivityInfo getTargetActivityInfo(Context ctx, Intent intent,
+ int currentUserId) {
PackageManager packageManager = ctx.getPackageManager();
final List<ResolveInfo> appList = packageManager.queryIntentActivitiesAsUser(
intent, PackageManager.MATCH_DEFAULT_ONLY, currentUserId);
if (appList.size() == 0) {
- return false;
+ return null;
}
ResolveInfo resolved = packageManager.resolveActivityAsUser(intent,
PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_META_DATA, currentUserId);
- return wouldLaunchResolverActivity(resolved, appList);
+ if (resolved == null || wouldLaunchResolverActivity(resolved, appList)) {
+ return null;
+ } else {
+ return resolved.activityInfo;
+ }
}
private static boolean wouldLaunchResolverActivity(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
new file mode 100644
index 0000000..7d721c2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -0,0 +1,191 @@
+/*
+ * 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.statusbar.policy;
+
+import com.android.systemui.R;
+
+import android.annotation.NonNull;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.app.RemoteInput;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.FrameLayout;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+/**
+ * Host for the remote input.
+ */
+public class RemoteInputView extends FrameLayout implements View.OnClickListener {
+
+ private static final String TAG = "RemoteInput";
+
+ private RemoteEditText mEditText;
+ private ProgressBar mProgressBar;
+ private PendingIntent mPendingIntent;
+ private RemoteInput mRemoteInput;
+ private Notification.Action mAction;
+
+ public RemoteInputView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ mProgressBar = (ProgressBar) findViewById(R.id.remote_input_progress);
+
+ mEditText = (RemoteEditText) getChildAt(0);
+ mEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+ @Override
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+
+ // Check if this was the result of hitting the enter key
+ final boolean isSoftImeEvent = event == null
+ && (actionId == EditorInfo.IME_ACTION_DONE
+ || actionId == EditorInfo.IME_ACTION_NEXT
+ || actionId == EditorInfo.IME_ACTION_SEND);
+ final boolean isKeyboardEnterKey = event != null
+ && KeyEvent.isConfirmKey(event.getKeyCode())
+ && event.getAction() == KeyEvent.ACTION_DOWN;
+
+ if (isSoftImeEvent || isKeyboardEnterKey) {
+ sendRemoteInput();
+ return true;
+ }
+ return false;
+ }
+ });
+ mEditText.setOnClickListener(this);
+ mEditText.setInnerFocusable(false);
+ }
+
+ private void sendRemoteInput() {
+ Bundle results = new Bundle();
+ results.putString(mRemoteInput.getResultKey(), mEditText.getText().toString());
+ Intent fillInIntent = new Intent();
+ RemoteInput.addResultsToIntent(mAction.getRemoteInputs(), fillInIntent,
+ results);
+
+ mEditText.setEnabled(false);
+ mProgressBar.setVisibility(VISIBLE);
+
+ try {
+ mPendingIntent.send(mContext, 0, fillInIntent);
+ } catch (PendingIntent.CanceledException e) {
+ Log.i(TAG, "Unable to send remote input result", e);
+ }
+ }
+
+ public static RemoteInputView inflate(Context context, ViewGroup root,
+ Notification.Action action, RemoteInput remoteInput) {
+ RemoteInputView v = (RemoteInputView)
+ LayoutInflater.from(context).inflate(R.layout.remote_input, root, false);
+
+ v.mEditText.setHint(action.title);
+ v.mPendingIntent = action.actionIntent;
+ v.mRemoteInput = remoteInput;
+ v.mAction = action;
+
+ return v;
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (v == mEditText) {
+ if (!mEditText.isFocusable()) {
+ mEditText.setInnerFocusable(true);
+ InputMethodManager imm = InputMethodManager.getInstance();
+ if (imm != null) {
+ imm.viewClicked(mEditText);
+ imm.showSoftInput(mEditText, 0);
+ }
+ }
+ }
+ }
+
+ /**
+ * An EditText that changes appearance based on whether it's focusable and becomes
+ * un-focusable whenever the user navigates away from it or it becomes invisible.
+ */
+ public static class RemoteEditText extends EditText {
+
+ private final Drawable mBackground;
+
+ public RemoteEditText(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mBackground = getBackground();
+ }
+
+ private void defocusIfNeeded() {
+ if (isFocusable() && isEnabled()) {
+ setInnerFocusable(false);
+ }
+ }
+
+ @Override
+ protected void onVisibilityChanged(View changedView, int visibility) {
+ super.onVisibilityChanged(changedView, visibility);
+
+ if (!isShown()) {
+ defocusIfNeeded();
+ }
+ }
+
+ @Override
+ protected void onFocusLost() {
+ super.onFocusLost();
+ defocusIfNeeded();
+ }
+
+ @Override
+ public boolean onKeyPreIme(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ defocusIfNeeded();
+ }
+ return super.onKeyPreIme(keyCode, event);
+ }
+
+
+ void setInnerFocusable(boolean focusable) {
+ setFocusableInTouchMode(focusable);
+ setFocusable(focusable);
+ setCursorVisible(focusable);
+
+ if (focusable) {
+ requestFocus();
+ setBackground(mBackground);
+ } else {
+ setBackground(null);
+ }
+
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
index 6148feb..40984d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
@@ -22,10 +22,8 @@ public interface SecurityController {
String getDeviceOwnerName();
String getProfileOwnerName();
boolean isVpnEnabled();
- String getVpnApp();
- boolean isLegacyVpn();
- String getLegacyVpnName();
- void disconnectFromVpn();
+ String getPrimaryVpnName();
+ String getProfileVpnName();
void onUserSwitched(int newUserId);
void addCallback(SecurityControllerCallback callback);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index f0dd943..b505d9d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -19,6 +19,7 @@ import android.app.ActivityManager;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.UserInfo;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.IConnectivityManager;
@@ -27,10 +28,15 @@ import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.os.UserManager;
import android.text.TextUtils;
import android.util.Log;
+import android.util.SparseArray;
import com.android.internal.net.VpnConfig;
+import com.android.internal.net.VpnInfo;
+import com.android.systemui.R;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -50,16 +56,15 @@ public class SecurityControllerImpl implements SecurityController {
private final Context mContext;
private final ConnectivityManager mConnectivityManager;
- private final IConnectivityManager mConnectivityService = IConnectivityManager.Stub.asInterface(
- ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
+ private final IConnectivityManager mConnectivityManagerService;
private final DevicePolicyManager mDevicePolicyManager;
+ private final UserManager mUserManager;
private final ArrayList<SecurityControllerCallback> mCallbacks
= new ArrayList<SecurityControllerCallback>();
- private VpnConfig mVpnConfig;
- private String mVpnName;
- private int mCurrentVpnNetworkId = NO_NETWORK;
+ private SparseArray<VpnConfig> mCurrentVpns = new SparseArray<>();
private int mCurrentUserId;
+ private int mVpnUserId;
public SecurityControllerImpl(Context context) {
mContext = context;
@@ -67,17 +72,28 @@ public class SecurityControllerImpl implements SecurityController {
context.getSystemService(Context.DEVICE_POLICY_SERVICE);
mConnectivityManager = (ConnectivityManager)
context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ mConnectivityManagerService = IConnectivityManager.Stub.asInterface(
+ ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
+ mUserManager = (UserManager)
+ context.getSystemService(Context.USER_SERVICE);
// TODO: re-register network callback on user change.
mConnectivityManager.registerNetworkCallback(REQUEST, mNetworkCallback);
- mCurrentUserId = ActivityManager.getCurrentUser();
+ onUserSwitched(ActivityManager.getCurrentUser());
}
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("SecurityController state:");
- pw.print(" mCurrentVpnNetworkId="); pw.println(mCurrentVpnNetworkId);
- pw.print(" mVpnConfig="); pw.println(mVpnConfig);
- pw.print(" mVpnName="); pw.println(mVpnName);
+ pw.print(" mCurrentVpns={");
+ for (int i = 0 ; i < mCurrentVpns.size(); i++) {
+ if (i > 0) {
+ pw.print(", ");
+ }
+ pw.print(mCurrentVpns.keyAt(i));
+ pw.print('=');
+ pw.print(mCurrentVpns.valueAt(i).user);
+ }
+ pw.println("}");
}
@Override
@@ -86,56 +102,58 @@ public class SecurityControllerImpl implements SecurityController {
}
@Override
- public boolean hasProfileOwner() {
- return !TextUtils.isEmpty(mDevicePolicyManager.getProfileOwnerNameAsUser(mCurrentUserId));
- }
-
- @Override
public String getDeviceOwnerName() {
return mDevicePolicyManager.getDeviceOwnerName();
}
@Override
- public String getProfileOwnerName() {
- return mDevicePolicyManager.getProfileOwnerNameAsUser(mCurrentUserId);
- }
-
-
- @Override
- public boolean isVpnEnabled() {
- return mCurrentVpnNetworkId != NO_NETWORK;
+ public boolean hasProfileOwner() {
+ return mDevicePolicyManager.getProfileOwnerAsUser(mCurrentUserId) != null;
}
@Override
- public boolean isLegacyVpn() {
- return mVpnConfig.legacy;
+ public String getProfileOwnerName() {
+ for (UserInfo profile : mUserManager.getProfiles(mCurrentUserId)) {
+ String name = mDevicePolicyManager.getProfileOwnerNameAsUser(profile.id);
+ if (name != null) {
+ return name;
+ }
+ }
+ return null;
}
@Override
- public String getVpnApp() {
- return mVpnName;
+ public String getPrimaryVpnName() {
+ VpnConfig cfg = mCurrentVpns.get(mVpnUserId);
+ if (cfg != null) {
+ return getNameForVpnConfig(cfg, new UserHandle(mVpnUserId));
+ } else {
+ return null;
+ }
}
@Override
- public String getLegacyVpnName() {
- return mVpnConfig.session;
+ public String getProfileVpnName() {
+ for (UserInfo profile : mUserManager.getProfiles(mVpnUserId)) {
+ if (profile.id == mVpnUserId) {
+ continue;
+ }
+ VpnConfig cfg = mCurrentVpns.get(profile.id);
+ if (cfg != null) {
+ return getNameForVpnConfig(cfg, profile.getUserHandle());
+ }
+ }
+ return null;
}
@Override
- public void disconnectFromVpn() {
- try {
- if (isLegacyVpn()) {
- mConnectivityService.prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN);
- } else {
- // Prevent this app from initiating VPN connections in the future without user
- // intervention.
- mConnectivityService.setVpnPackageAuthorization(false);
-
- mConnectivityService.prepareVpn(mVpnConfig.user, VpnConfig.LEGACY_VPN);
+ public boolean isVpnEnabled() {
+ for (UserInfo profile : mUserManager.getProfiles(mVpnUserId)) {
+ if (mCurrentVpns.get(profile.id) != null) {
+ return true;
}
- } catch (Exception e) {
- Log.e(TAG, "Unable to disconnect from VPN", e);
}
+ return false;
}
@Override
@@ -155,14 +173,28 @@ public class SecurityControllerImpl implements SecurityController {
@Override
public void onUserSwitched(int newUserId) {
mCurrentUserId = newUserId;
+ if (mUserManager.getUserInfo(newUserId).isRestricted()) {
+ // VPN for a restricted profile is routed through its owner user
+ mVpnUserId = UserHandle.USER_OWNER;
+ } else {
+ mVpnUserId = mCurrentUserId;
+ }
fireCallbacks();
}
- private void setCurrentNetid(int netId) {
- if (netId != mCurrentVpnNetworkId) {
- mCurrentVpnNetworkId = netId;
- updateState();
- fireCallbacks();
+ private String getNameForVpnConfig(VpnConfig cfg, UserHandle user) {
+ if (cfg.legacy) {
+ return mContext.getString(R.string.legacy_vpn_name);
+ }
+ // The package name for an active VPN is stored in the 'user' field of its VpnConfig
+ final String vpnPackage = cfg.user;
+ try {
+ Context userContext = mContext.createPackageContextAsUser(mContext.getPackageName(),
+ 0 /* flags */, user);
+ return VpnConfig.getVpnLabel(userContext, vpnPackage).toString();
+ } catch (NameNotFoundException nnfe) {
+ Log.e(TAG, "Package " + vpnPackage + " is not present", nnfe);
+ return null;
}
}
@@ -173,27 +205,29 @@ public class SecurityControllerImpl implements SecurityController {
}
private void updateState() {
+ // Find all users with an active VPN
+ SparseArray<VpnConfig> vpns = new SparseArray<>();
try {
- mVpnConfig = mConnectivityService.getVpnConfig();
-
- if (mVpnConfig != null && !mVpnConfig.legacy) {
- mVpnName = VpnConfig.getVpnLabel(mContext, mVpnConfig.user).toString();
+ for (UserInfo user : mUserManager.getUsers()) {
+ VpnConfig cfg = mConnectivityManagerService.getVpnConfig(user.id);
+ if (cfg != null) {
+ vpns.put(user.id, cfg);
+ }
}
- } catch (RemoteException | NameNotFoundException e) {
- Log.w(TAG, "Unable to get current VPN", e);
+ } catch (RemoteException rme) {
+ // Roll back to previous state
+ Log.e(TAG, "Unable to list active VPNs", rme);
+ return;
}
+ mCurrentVpns = vpns;
}
private final NetworkCallback mNetworkCallback = new NetworkCallback() {
@Override
public void onAvailable(Network network) {
- NetworkCapabilities networkCapabilities =
- mConnectivityManager.getNetworkCapabilities(network);
- if (DEBUG) Log.d(TAG, "onAvailable " + network.netId + " : " + networkCapabilities);
- if (networkCapabilities != null &&
- networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) {
- setCurrentNetid(network.netId);
- }
+ if (DEBUG) Log.d(TAG, "onAvailable " + network.netId);
+ updateState();
+ fireCallbacks();
};
// TODO Find another way to receive VPN lost. This may be delayed depending on
@@ -201,10 +235,8 @@ public class SecurityControllerImpl implements SecurityController {
@Override
public void onLost(Network network) {
if (DEBUG) Log.d(TAG, "onLost " + network.netId);
- if (mCurrentVpnNetworkId == network.netId) {
- setCurrentNetid(NO_NETWORK);
- }
+ updateState();
+ fireCallbacks();
};
};
-
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalCallbackAdapter.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalCallbackAdapter.java
new file mode 100644
index 0000000..dce889f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalCallbackAdapter.java
@@ -0,0 +1,63 @@
+/*
+ * 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.statusbar.policy;
+
+import android.telephony.SubscriptionInfo;
+
+import com.android.systemui.statusbar.policy.NetworkController.IconState;
+import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+
+import java.util.List;
+
+
+/**
+ * Provides empty implementations of SignalCallback for those that only want some of
+ * the callbacks.
+ */
+public class SignalCallbackAdapter implements SignalCallback {
+
+ @Override
+ public void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon,
+ boolean activityIn, boolean activityOut, String description) {
+ }
+
+ @Override
+ public void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType,
+ int qsType, boolean activityIn, boolean activityOut, String typeContentDescription,
+ String description, boolean isWide, int subId) {
+ }
+
+ @Override
+ public void setSubs(List<SubscriptionInfo> subs) {
+ }
+
+ @Override
+ public void setNoSims(boolean show) {
+ }
+
+ @Override
+ public void setEthernetIndicators(IconState icon) {
+ }
+
+ @Override
+ public void setIsAirplaneMode(IconState icon) {
+ }
+
+ @Override
+ public void setMobileDataEnabled(boolean enabled) {
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
new file mode 100644
index 0000000..5e9447e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
@@ -0,0 +1,314 @@
+/*
+ * 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.statusbar.policy;
+
+import static com.android.systemui.statusbar.policy.NetworkControllerImpl.TAG;
+
+import android.content.Context;
+import android.text.format.DateFormat;
+import android.util.Log;
+
+import java.io.PrintWriter;
+import java.util.BitSet;
+
+
+/**
+ * Common base class for handling signal for both wifi and mobile data.
+ */
+public abstract class SignalController<T extends SignalController.State,
+ I extends SignalController.IconGroup> {
+ // Save the previous SignalController.States of all SignalControllers for dumps.
+ static final boolean RECORD_HISTORY = true;
+ // If RECORD_HISTORY how many to save, must be a power of 2.
+ static final int HISTORY_SIZE = 64;
+
+ protected static final boolean DEBUG = NetworkControllerImpl.DEBUG;
+ protected static final boolean CHATTY = NetworkControllerImpl.CHATTY;
+
+ protected final String mTag;
+ protected final T mCurrentState;
+ protected final T mLastState;
+ protected final int mTransportType;
+ protected final Context mContext;
+ // The owner of the SignalController (i.e. NetworkController will maintain the following
+ // lists and call notifyListeners whenever the list has changed to ensure everyone
+ // is aware of current state.
+ protected final NetworkControllerImpl mNetworkController;
+
+ protected final CallbackHandler mCallbackHandler;
+
+ // Save the previous HISTORY_SIZE states for logging.
+ private final State[] mHistory;
+ // Where to copy the next state into.
+ private int mHistoryIndex;
+
+ public SignalController(String tag, Context context, int type, CallbackHandler callbackHandler,
+ NetworkControllerImpl networkController) {
+ mTag = TAG + "." + tag;
+ mNetworkController = networkController;
+ mTransportType = type;
+ mContext = context;
+ mCallbackHandler = callbackHandler;
+ mCurrentState = cleanState();
+ mLastState = cleanState();
+ if (RECORD_HISTORY) {
+ mHistory = new State[HISTORY_SIZE];
+ for (int i = 0; i < HISTORY_SIZE; i++) {
+ mHistory[i] = cleanState();
+ }
+ }
+ }
+
+ public T getState() {
+ return mCurrentState;
+ }
+
+ public void updateConnectivity(BitSet connectedTransports, BitSet validatedTransports) {
+ mCurrentState.inetCondition = validatedTransports.get(mTransportType) ? 1 : 0;
+ notifyListenersIfNecessary();
+ }
+
+ /**
+ * Used at the end of demo mode to clear out any ugly state that it has created.
+ * Since we haven't had any callbacks, then isDirty will not have been triggered,
+ * so we can just take the last good state directly from there.
+ *
+ * Used for demo mode.
+ */
+ public void resetLastState() {
+ mCurrentState.copyFrom(mLastState);
+ }
+
+ /**
+ * Determines if the state of this signal controller has changed and
+ * needs to trigger callbacks related to it.
+ */
+ public boolean isDirty() {
+ if (!mLastState.equals(mCurrentState)) {
+ if (DEBUG) {
+ Log.d(mTag, "Change in state from: " + mLastState + "\n"
+ + "\tto: " + mCurrentState);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public void saveLastState() {
+ if (RECORD_HISTORY) {
+ recordLastState();
+ }
+ // Updates the current time.
+ mCurrentState.time = System.currentTimeMillis();
+ mLastState.copyFrom(mCurrentState);
+ }
+
+ /**
+ * Gets the signal icon for QS based on current state of connected, enabled, and level.
+ */
+ public int getQsCurrentIconId() {
+ if (mCurrentState.connected) {
+ return getIcons().mQsIcons[mCurrentState.inetCondition][mCurrentState.level];
+ } else if (mCurrentState.enabled) {
+ return getIcons().mQsDiscState;
+ } else {
+ return getIcons().mQsNullState;
+ }
+ }
+
+ /**
+ * Gets the signal icon for SB based on current state of connected, enabled, and level.
+ */
+ public int getCurrentIconId() {
+ if (mCurrentState.connected) {
+ return getIcons().mSbIcons[mCurrentState.inetCondition][mCurrentState.level];
+ } else if (mCurrentState.enabled) {
+ return getIcons().mSbDiscState;
+ } else {
+ return getIcons().mSbNullState;
+ }
+ }
+
+ /**
+ * Gets the content description id for the signal based on current state of connected and
+ * level.
+ */
+ public int getContentDescription() {
+ if (mCurrentState.connected) {
+ return getIcons().mContentDesc[mCurrentState.level];
+ } else {
+ return getIcons().mDiscContentDesc;
+ }
+ }
+
+ public void notifyListenersIfNecessary() {
+ if (isDirty()) {
+ saveLastState();
+ notifyListeners();
+ }
+ }
+
+ /**
+ * Returns the resource if resId is not 0, and an empty string otherwise.
+ */
+ protected String getStringIfExists(int resId) {
+ return resId != 0 ? mContext.getString(resId) : "";
+ }
+
+ protected I getIcons() {
+ return (I) mCurrentState.iconGroup;
+ }
+
+ /**
+ * Saves the last state of any changes, so we can log the current
+ * and last value of any state data.
+ */
+ protected void recordLastState() {
+ mHistory[mHistoryIndex++ & (HISTORY_SIZE - 1)].copyFrom(mLastState);
+ }
+
+ public void dump(PrintWriter pw) {
+ pw.println(" - " + mTag + " -----");
+ pw.println(" Current State: " + mCurrentState);
+ if (RECORD_HISTORY) {
+ // Count up the states that actually contain time stamps, and only display those.
+ int size = 0;
+ for (int i = 0; i < HISTORY_SIZE; i++) {
+ if (mHistory[i].time != 0) size++;
+ }
+ // Print out the previous states in ordered number.
+ for (int i = mHistoryIndex + HISTORY_SIZE - 1;
+ i >= mHistoryIndex + HISTORY_SIZE - size; i--) {
+ pw.println(" Previous State(" + (mHistoryIndex + HISTORY_SIZE - i) + "): "
+ + mHistory[i & (HISTORY_SIZE - 1)]);
+ }
+ }
+ }
+
+ /**
+ * Trigger callbacks based on current state. The callbacks should be completely
+ * based on current state, and only need to be called in the scenario where
+ * mCurrentState != mLastState.
+ */
+ public abstract void notifyListeners();
+
+ /**
+ * Generate a blank T.
+ */
+ protected abstract T cleanState();
+
+ /*
+ * Holds icons for a given state. Arrays are generally indexed as inet
+ * state (full connectivity or not) first, and second dimension as
+ * signal strength.
+ */
+ static class IconGroup {
+ final int[][] mSbIcons;
+ final int[][] mQsIcons;
+ final int[] mContentDesc;
+ final int mSbNullState;
+ final int mQsNullState;
+ final int mSbDiscState;
+ final int mQsDiscState;
+ final int mDiscContentDesc;
+ // For logging.
+ final String mName;
+
+ public IconGroup(String name, int[][] sbIcons, int[][] qsIcons, int[] contentDesc,
+ int sbNullState, int qsNullState, int sbDiscState, int qsDiscState,
+ int discContentDesc) {
+ mName = name;
+ mSbIcons = sbIcons;
+ mQsIcons = qsIcons;
+ mContentDesc = contentDesc;
+ mSbNullState = sbNullState;
+ mQsNullState = qsNullState;
+ mSbDiscState = sbDiscState;
+ mQsDiscState = qsDiscState;
+ mDiscContentDesc = discContentDesc;
+ }
+
+ @Override
+ public String toString() {
+ return "IconGroup(" + mName + ")";
+ }
+ }
+
+ static class State {
+ boolean connected;
+ boolean enabled;
+ boolean activityIn;
+ boolean activityOut;
+ int level;
+ IconGroup iconGroup;
+ int inetCondition;
+ int rssi; // Only for logging.
+
+ // Not used for comparison, just used for logging.
+ long time;
+
+ public void copyFrom(State state) {
+ connected = state.connected;
+ enabled = state.enabled;
+ level = state.level;
+ iconGroup = state.iconGroup;
+ inetCondition = state.inetCondition;
+ activityIn = state.activityIn;
+ activityOut = state.activityOut;
+ rssi = state.rssi;
+ time = state.time;
+ }
+
+ @Override
+ public String toString() {
+ if (time != 0) {
+ StringBuilder builder = new StringBuilder();
+ toString(builder);
+ return builder.toString();
+ } else {
+ return "Empty " + getClass().getSimpleName();
+ }
+ }
+
+ protected void toString(StringBuilder builder) {
+ builder.append("connected=").append(connected).append(',')
+ .append("enabled=").append(enabled).append(',')
+ .append("level=").append(level).append(',')
+ .append("inetCondition=").append(inetCondition).append(',')
+ .append("iconGroup=").append(iconGroup).append(',')
+ .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));
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!o.getClass().equals(getClass())) {
+ return false;
+ }
+ State other = (State) o;
+ return other.connected == connected
+ && other.enabled == enabled
+ && other.level == level
+ && other.inetCondition == inetCondition
+ && other.iconGroup == iconGroup
+ && other.activityIn == activityIn
+ && other.activityOut == activityOut
+ && other.rssi == rssi;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
index 4091619..8a27653 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
@@ -17,7 +17,7 @@
package com.android.systemui.statusbar.policy;
import com.android.systemui.R;
-import com.android.systemui.statusbar.policy.NetworkControllerImpl.MobileSignalController.MobileIconGroup;
+import com.android.systemui.statusbar.policy.MobileSignalController.MobileIconGroup;
class TelephonyIcons {
//***** Signal strength icons
@@ -68,11 +68,35 @@ class TelephonyIcons {
R.drawable.stat_sys_signal_4_fully }
};
- static final int[] QS_DATA_R = {
- R.drawable.ic_qs_signal_r,
- R.drawable.ic_qs_signal_r
+ //CarrierNetworkChange
+ static final int[][] TELEPHONY_CARRIER_NETWORK_CHANGE = {
+ { R.drawable.stat_sys_signal_carrier_network_change_animation,
+ R.drawable.stat_sys_signal_carrier_network_change_animation,
+ R.drawable.stat_sys_signal_carrier_network_change_animation,
+ R.drawable.stat_sys_signal_carrier_network_change_animation,
+ R.drawable.stat_sys_signal_carrier_network_change_animation },
+ { R.drawable.stat_sys_signal_carrier_network_change_animation,
+ R.drawable.stat_sys_signal_carrier_network_change_animation,
+ R.drawable.stat_sys_signal_carrier_network_change_animation,
+ R.drawable.stat_sys_signal_carrier_network_change_animation,
+ R.drawable.stat_sys_signal_carrier_network_change_animation }
+ };
+
+ static final int[][] QS_TELEPHONY_CARRIER_NETWORK_CHANGE = {
+ { R.drawable.ic_qs_signal_carrier_network_change_animation,
+ R.drawable.ic_qs_signal_carrier_network_change_animation,
+ R.drawable.ic_qs_signal_carrier_network_change_animation,
+ R.drawable.ic_qs_signal_carrier_network_change_animation,
+ R.drawable.ic_qs_signal_carrier_network_change_animation },
+ { R.drawable.ic_qs_signal_carrier_network_change_animation,
+ R.drawable.ic_qs_signal_carrier_network_change_animation,
+ R.drawable.ic_qs_signal_carrier_network_change_animation,
+ R.drawable.ic_qs_signal_carrier_network_change_animation,
+ R.drawable.ic_qs_signal_carrier_network_change_animation }
};
+ static final int QS_DATA_R = R.drawable.ic_qs_signal_r;
+
//***** Data connection icons
//GSM/UMTS
@@ -87,10 +111,7 @@ class TelephonyIcons {
R.drawable.stat_sys_data_fully_connected_g }
};
- static final int[] QS_DATA_G = {
- R.drawable.ic_qs_signal_g,
- R.drawable.ic_qs_signal_g
- };
+ static final int QS_DATA_G = R.drawable.ic_qs_signal_g;
static final int[][] DATA_3G = {
{ R.drawable.stat_sys_data_fully_connected_3g,
@@ -103,10 +124,7 @@ class TelephonyIcons {
R.drawable.stat_sys_data_fully_connected_3g }
};
- static final int[] QS_DATA_3G = {
- R.drawable.ic_qs_signal_3g,
- R.drawable.ic_qs_signal_3g
- };
+ static final int QS_DATA_3G = R.drawable.ic_qs_signal_3g;
static final int[][] DATA_E = {
{ R.drawable.stat_sys_data_fully_connected_e,
@@ -119,10 +137,7 @@ class TelephonyIcons {
R.drawable.stat_sys_data_fully_connected_e }
};
- static final int[] QS_DATA_E = {
- R.drawable.ic_qs_signal_e,
- R.drawable.ic_qs_signal_e
- };
+ static final int QS_DATA_E = R.drawable.ic_qs_signal_e;
//3.5G
static final int[][] DATA_H = {
@@ -136,10 +151,7 @@ class TelephonyIcons {
R.drawable.stat_sys_data_fully_connected_h }
};
- static final int[] QS_DATA_H = {
- R.drawable.ic_qs_signal_h,
- R.drawable.ic_qs_signal_h
- };
+ static final int QS_DATA_H = R.drawable.ic_qs_signal_h;
//CDMA
// Use 3G icons for EVDO data and 1x icons for 1XRTT data
@@ -154,10 +166,7 @@ class TelephonyIcons {
R.drawable.stat_sys_data_fully_connected_1x }
};
- static final int[] QS_DATA_1X = {
- R.drawable.ic_qs_signal_1x,
- R.drawable.ic_qs_signal_1x
- };
+ static final int QS_DATA_1X = R.drawable.ic_qs_signal_1x;
// LTE and eHRPD
static final int[][] DATA_4G = {
@@ -171,10 +180,7 @@ class TelephonyIcons {
R.drawable.stat_sys_data_fully_connected_4g }
};
- static final int[] QS_DATA_4G = {
- R.drawable.ic_qs_signal_4g,
- R.drawable.ic_qs_signal_4g
- };
+ static final int QS_DATA_4G = R.drawable.ic_qs_signal_4g;
// LTE branded "LTE"
static final int[][] DATA_LTE = {
@@ -188,10 +194,7 @@ class TelephonyIcons {
R.drawable.stat_sys_data_fully_connected_lte }
};
- static final int[] QS_DATA_LTE = {
- R.drawable.ic_qs_signal_lte,
- R.drawable.ic_qs_signal_lte
- };
+ static final int QS_DATA_LTE = R.drawable.ic_qs_signal_lte;
static final int FLIGHT_MODE_ICON = R.drawable.stat_sys_airplane_mode;
static final int ROAMING_ICON = R.drawable.stat_sys_data_fully_connected_roam;
@@ -202,11 +205,30 @@ class TelephonyIcons {
static final int ICON_3G = R.drawable.stat_sys_data_fully_connected_3g;
static final int ICON_4G = R.drawable.stat_sys_data_fully_connected_4g;
static final int ICON_1X = R.drawable.stat_sys_data_fully_connected_1x;
+ static final int ICON_CARRIER_NETWORK_CHANGE =
+ R.drawable.stat_sys_signal_carrier_network_change_animation;
static final int QS_ICON_LTE = R.drawable.ic_qs_signal_lte;
static final int QS_ICON_3G = R.drawable.ic_qs_signal_3g;
static final int QS_ICON_4G = R.drawable.ic_qs_signal_4g;
static final int QS_ICON_1X = R.drawable.ic_qs_signal_1x;
+ static final int QS_ICON_CARRIER_NETWORK_CHANGE =
+ R.drawable.ic_qs_signal_carrier_network_change_animation;
+
+ static final MobileIconGroup CARRIER_NETWORK_CHANGE = new MobileIconGroup(
+ "CARRIER_NETWORK_CHANGE",
+ TelephonyIcons.TELEPHONY_CARRIER_NETWORK_CHANGE,
+ TelephonyIcons.QS_TELEPHONY_CARRIER_NETWORK_CHANGE,
+ AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
+ 0, 0,
+ TelephonyIcons.ICON_CARRIER_NETWORK_CHANGE,
+ TelephonyIcons.QS_ICON_CARRIER_NETWORK_CHANGE,
+ AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
+ R.string.accessibility_carrier_network_change_mode,
+ 0,
+ false,
+ 0
+ );
static final MobileIconGroup THREE_G = new MobileIconGroup(
"3G",
@@ -232,7 +254,7 @@ class TelephonyIcons {
TelephonyIcons.TELEPHONY_NO_NETWORK,
TelephonyIcons.QS_TELEPHONY_NO_NETWORK,
AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
- 0, 0, false, new int[2]
+ 0, 0, false, 0
);
static final MobileIconGroup E = new MobileIconGroup(
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 4ac41a1..41fc967 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -19,6 +19,9 @@ package com.android.systemui.statusbar.policy;
import android.app.ActivityManager;
import android.app.ActivityManagerNative;
import android.app.Dialog;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
@@ -40,6 +43,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
+import com.android.internal.logging.MetricsLogger;
import com.android.internal.util.UserIcons;
import com.android.systemui.BitmapHelper;
import com.android.systemui.GuestResumeSessionReceiver;
@@ -63,6 +67,7 @@ public class UserSwitcherController {
private static final boolean DEBUG = false;
private static final String SIMPLE_USER_SWITCHER_GLOBAL_SETTING =
"lockscreenSimpleUserSwitcher";
+ private static final String ACTION_REMOVE_GUEST = "com.android.systemui.REMOVE_GUEST";
private final Context mContext;
private final UserManager mUserManager;
@@ -89,6 +94,7 @@ public class UserSwitcherController {
filter.addAction(Intent.ACTION_USER_INFO_CHANGED);
filter.addAction(Intent.ACTION_USER_SWITCHED);
filter.addAction(Intent.ACTION_USER_STOPPING);
+ filter.addAction(ACTION_REMOVE_GUEST);
mContext.registerReceiverAsUser(mReceiver, UserHandle.OWNER, filter,
null /* permission */, null /* scheduler */);
@@ -296,6 +302,22 @@ public class UserSwitcherController {
Log.v(TAG, "Broadcast: a=" + intent.getAction()
+ " user=" + intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1));
}
+ if (ACTION_REMOVE_GUEST.equals(intent.getAction())) {
+ int currentUser = ActivityManager.getCurrentUser();
+ UserInfo userInfo = mUserManager.getUserInfo(currentUser);
+ if (userInfo != null && userInfo.isGuest()) {
+ showExitGuestDialog(currentUser);
+ }
+ return;
+ }
+ if (Intent.ACTION_USER_ADDED.equals(intent.getAction())) {
+ final int currentId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+ UserInfo userInfo = mUserManager.getUserInfo(currentId);
+ if (userInfo != null && userInfo.isGuest()) {
+ showGuestNotification(currentId);
+ }
+ }
+
if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
if (mExitGuestDialog != null && mExitGuestDialog.isShowing()) {
mExitGuestDialog.cancel();
@@ -329,6 +351,24 @@ public class UserSwitcherController {
}
refreshUsers(forcePictureLoadForId);
}
+
+ private void showGuestNotification(int guestUserId) {
+ PendingIntent removeGuestPI = PendingIntent.getBroadcastAsUser(mContext,
+ 0, new Intent(ACTION_REMOVE_GUEST), 0, UserHandle.OWNER);
+ Notification notification = new Notification.Builder(mContext)
+ .setVisibility(Notification.VISIBILITY_SECRET)
+ .setPriority(Notification.PRIORITY_MIN)
+ .setSmallIcon(R.drawable.ic_person)
+ .setContentTitle(mContext.getString(R.string.guest_notification_title))
+ .setContentText(mContext.getString(R.string.guest_notification_text))
+ .setShowWhen(false)
+ .addAction(R.drawable.ic_delete,
+ mContext.getString(R.string.guest_notification_remove_action),
+ removeGuestPI)
+ .build();
+ NotificationManager.from(mContext).notifyAsUser(null, 0, notification,
+ new UserHandle(guestUserId));
+ }
};
private final ContentObserver mSettingsObserver = new ContentObserver(new Handler()) {
@@ -371,7 +411,8 @@ public class UserSwitcherController {
@Override
public int getCount() {
boolean secureKeyguardShowing = mController.mKeyguardMonitor.isShowing()
- && mController.mKeyguardMonitor.isSecure();
+ && mController.mKeyguardMonitor.isSecure()
+ && !mController.mKeyguardMonitor.isTrusted();
if (!secureKeyguardShowing) {
return mController.mUsers.size();
}
@@ -509,6 +550,11 @@ public class UserSwitcherController {
@Override
public void setToggleState(boolean state) {
}
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsLogger.QS_USERDETAIL;
+ }
};
private final KeyguardMonitor.Callback mCallback = new KeyguardMonitor.Callback() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
new file mode 100644
index 0000000..9b1e72a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
@@ -0,0 +1,200 @@
+/*
+ * 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.statusbar.policy;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Messenger;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.AsyncChannel;
+import com.android.systemui.statusbar.policy.NetworkController.IconState;
+
+import java.util.List;
+import java.util.Objects;
+
+
+public class WifiSignalController extends
+ SignalController<WifiSignalController.WifiState, SignalController.IconGroup> {
+ private final WifiManager mWifiManager;
+ private final AsyncChannel mWifiChannel;
+ private final boolean mHasMobileData;
+
+ public WifiSignalController(Context context, boolean hasMobileData,
+ CallbackHandler callbackHandler, NetworkControllerImpl networkController) {
+ super("WifiSignalController", context, NetworkCapabilities.TRANSPORT_WIFI,
+ callbackHandler, networkController);
+ mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+ mHasMobileData = hasMobileData;
+ Handler handler = new WifiHandler();
+ mWifiChannel = new AsyncChannel();
+ Messenger wifiMessenger = mWifiManager.getWifiServiceMessenger();
+ if (wifiMessenger != null) {
+ mWifiChannel.connect(context, handler, wifiMessenger);
+ }
+ // WiFi only has one state.
+ mCurrentState.iconGroup = mLastState.iconGroup = new IconGroup(
+ "Wi-Fi Icons",
+ WifiIcons.WIFI_SIGNAL_STRENGTH,
+ WifiIcons.QS_WIFI_SIGNAL_STRENGTH,
+ AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH,
+ WifiIcons.WIFI_NO_NETWORK,
+ WifiIcons.QS_WIFI_NO_NETWORK,
+ WifiIcons.WIFI_NO_NETWORK,
+ WifiIcons.QS_WIFI_NO_NETWORK,
+ AccessibilityContentDescriptions.WIFI_NO_CONNECTION
+ );
+ }
+
+ @Override
+ protected WifiState cleanState() {
+ return new WifiState();
+ }
+
+ @Override
+ public void notifyListeners() {
+ // only show wifi in the cluster if connected or if wifi-only
+ boolean wifiVisible = mCurrentState.enabled
+ && (mCurrentState.connected || !mHasMobileData);
+ String wifiDesc = wifiVisible ? mCurrentState.ssid : null;
+ boolean ssidPresent = wifiVisible && mCurrentState.ssid != null;
+ String contentDescription = getStringIfExists(getContentDescription());
+
+ IconState statusIcon = new IconState(wifiVisible, getCurrentIconId(), contentDescription);
+ IconState qsIcon = new IconState(mCurrentState.connected, getQsCurrentIconId(),
+ contentDescription);
+ mCallbackHandler.setWifiIndicators(mCurrentState.enabled, statusIcon, qsIcon,
+ ssidPresent && mCurrentState.activityIn, ssidPresent && mCurrentState.activityOut,
+ wifiDesc);
+ }
+
+ /**
+ * Extract wifi state directly from broadcasts about changes in wifi state.
+ */
+ public void handleBroadcast(Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
+ mCurrentState.enabled = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
+ WifiManager.WIFI_STATE_UNKNOWN) == WifiManager.WIFI_STATE_ENABLED;
+ } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+ final NetworkInfo networkInfo = (NetworkInfo)
+ intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
+ mCurrentState.connected = networkInfo != null && networkInfo.isConnected();
+ // If Connected grab the signal strength and ssid.
+ if (mCurrentState.connected) {
+ // try getting it out of the intent first
+ WifiInfo info = intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO) != null
+ ? (WifiInfo) intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO)
+ : mWifiManager.getConnectionInfo();
+ if (info != null) {
+ mCurrentState.ssid = getSsid(info);
+ } else {
+ mCurrentState.ssid = null;
+ }
+ } else if (!mCurrentState.connected) {
+ mCurrentState.ssid = null;
+ }
+ } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) {
+ // Default to -200 as its below WifiManager.MIN_RSSI.
+ mCurrentState.rssi = intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200);
+ mCurrentState.level = WifiManager.calculateSignalLevel(
+ mCurrentState.rssi, WifiIcons.WIFI_LEVEL_COUNT);
+ }
+
+ notifyListenersIfNecessary();
+ }
+
+ private String getSsid(WifiInfo info) {
+ String ssid = info.getSSID();
+ if (ssid != null) {
+ return ssid;
+ }
+ // OK, it's not in the connectionInfo; we have to go hunting for it
+ List<WifiConfiguration> networks = mWifiManager.getConfiguredNetworks();
+ int length = networks.size();
+ for (int i = 0; i < length; i++) {
+ if (networks.get(i).networkId == info.getNetworkId()) {
+ return networks.get(i).SSID;
+ }
+ }
+ return null;
+ }
+
+ @VisibleForTesting
+ void setActivity(int wifiActivity) {
+ mCurrentState.activityIn = wifiActivity == WifiManager.DATA_ACTIVITY_INOUT
+ || wifiActivity == WifiManager.DATA_ACTIVITY_IN;
+ mCurrentState.activityOut = wifiActivity == WifiManager.DATA_ACTIVITY_INOUT
+ || wifiActivity == WifiManager.DATA_ACTIVITY_OUT;
+ notifyListenersIfNecessary();
+ }
+
+ /**
+ * Handler to receive the data activity on wifi.
+ */
+ private class WifiHandler extends Handler {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
+ if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
+ mWifiChannel.sendMessage(Message.obtain(this,
+ AsyncChannel.CMD_CHANNEL_FULL_CONNECTION));
+ } else {
+ Log.e(mTag, "Failed to connect to wifi");
+ }
+ break;
+ case WifiManager.DATA_ACTIVITY_NOTIFICATION:
+ setActivity(msg.arg1);
+ break;
+ default:
+ // Ignore
+ break;
+ }
+ }
+ }
+
+ static class WifiState extends SignalController.State {
+ String ssid;
+
+ @Override
+ public void copyFrom(State s) {
+ super.copyFrom(s);
+ WifiState state = (WifiState) s;
+ ssid = state.ssid;
+ }
+
+ @Override
+ protected void toString(StringBuilder builder) {
+ super.toString(builder);
+ builder.append(',').append("ssid=").append(ssid);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return super.equals(o)
+ && Objects.equals(((WifiState) o).ssid, ssid);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java
index 600b750..b2df40a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java
@@ -17,27 +17,35 @@
package com.android.systemui.statusbar.policy;
import android.content.ComponentName;
+import android.net.Uri;
import android.service.notification.Condition;
+import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.ZenRule;
public interface ZenModeController {
void addCallback(Callback callback);
void removeCallback(Callback callback);
- void setZen(int zen);
+ void setZen(int zen, Uri conditionId, String reason);
int getZen();
void requestConditions(boolean request);
- void setExitCondition(Condition exitCondition);
- Condition getExitCondition();
+ ZenRule getManualRule();
+ ZenModeConfig getConfig();
long getNextAlarm();
void setUserId(int userId);
boolean isZenAvailable();
ComponentName getEffectsSuppressor();
+ boolean isCountdownConditionSupported();
+ int getCurrentUser();
+ boolean isVolumeRestricted();
public static class Callback {
public void onZenChanged(int zen) {}
- public void onExitConditionChanged(Condition exitCondition) {}
public void onConditionsChanged(Condition[] conditions) {}
public void onNextAlarmChanged() {}
public void onZenAvailableChanged(boolean available) {}
public void onEffectsSupressorChanged() {}
+ public void onManualRuleChanged(ZenRule rule) {}
+ public void onConfigChanged(ZenModeConfig config) {}
}
+
} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index 37ed7d8..c07f1a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -16,8 +16,8 @@
package com.android.systemui.statusbar.policy;
+import android.app.ActivityManager;
import android.app.AlarmManager;
-import android.app.INotificationManager;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -28,14 +28,14 @@ import android.content.IntentFilter;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
-import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.UserHandle;
+import android.os.UserManager;
import android.provider.Settings.Global;
import android.provider.Settings.Secure;
import android.service.notification.Condition;
import android.service.notification.IConditionListener;
import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.ZenRule;
import android.util.Log;
import android.util.Slog;
@@ -43,6 +43,7 @@ import com.android.systemui.qs.GlobalSetting;
import java.util.ArrayList;
import java.util.LinkedHashMap;
+import java.util.Objects;
/** Platform implementation of the zen mode controller. **/
public class ZenModeControllerImpl implements ZenModeController {
@@ -53,14 +54,16 @@ public class ZenModeControllerImpl implements ZenModeController {
private final Context mContext;
private final GlobalSetting mModeSetting;
private final GlobalSetting mConfigSetting;
- private final INotificationManager mNoMan;
+ private final NotificationManager mNoMan;
private final LinkedHashMap<Uri, Condition> mConditions = new LinkedHashMap<Uri, Condition>();
private final AlarmManager mAlarmManager;
private final SetupObserver mSetupObserver;
+ private final UserManager mUserManager;
private int mUserId;
private boolean mRequesting;
private boolean mRegistered;
+ private ZenModeConfig mConfig;
public ZenModeControllerImpl(Context context, Handler handler) {
mContext = context;
@@ -73,16 +76,23 @@ public class ZenModeControllerImpl implements ZenModeController {
mConfigSetting = new GlobalSetting(mContext, handler, Global.ZEN_MODE_CONFIG_ETAG) {
@Override
protected void handleValueChanged(int value) {
- fireExitConditionChanged();
+ updateZenModeConfig();
}
};
+ mNoMan = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ mConfig = mNoMan.getZenModeConfig();
mModeSetting.setListening(true);
mConfigSetting.setListening(true);
- mNoMan = INotificationManager.Stub.asInterface(
- ServiceManager.getService(Context.NOTIFICATION_SERVICE));
mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
mSetupObserver = new SetupObserver(handler);
mSetupObserver.register();
+ mUserManager = context.getSystemService(UserManager.class);
+ }
+
+ @Override
+ public boolean isVolumeRestricted() {
+ return mUserManager.hasUserRestriction(UserManager.DISALLOW_ADJUST_VOLUME,
+ new UserHandle(mUserId));
}
@Override
@@ -101,8 +111,8 @@ public class ZenModeControllerImpl implements ZenModeController {
}
@Override
- public void setZen(int zen) {
- mModeSetting.setValue(zen);
+ public void setZen(int zen, Uri conditionId, String reason) {
+ mNoMan.setZenMode(zen, conditionId, reason);
}
@Override
@@ -113,36 +123,20 @@ public class ZenModeControllerImpl implements ZenModeController {
@Override
public void requestConditions(boolean request) {
mRequesting = request;
- try {
- mNoMan.requestZenModeConditions(mListener, request ? Condition.FLAG_RELEVANT_NOW : 0);
- } catch (RemoteException e) {
- // noop
- }
+ mNoMan.requestZenModeConditions(mListener, request ? Condition.FLAG_RELEVANT_NOW : 0);
if (!mRequesting) {
mConditions.clear();
}
}
@Override
- public void setExitCondition(Condition exitCondition) {
- try {
- mNoMan.setZenModeCondition(exitCondition);
- } catch (RemoteException e) {
- // noop
- }
+ public ZenRule getManualRule() {
+ return mConfig == null ? null : mConfig.manualRule;
}
@Override
- public Condition getExitCondition() {
- try {
- final ZenModeConfig config = mNoMan.getZenModeConfig();
- if (config != null) {
- return config.exitCondition;
- }
- } catch (RemoteException e) {
- // noop
- }
- return null;
+ public ZenModeConfig getConfig() {
+ return mConfig;
}
@Override
@@ -169,6 +163,17 @@ public class ZenModeControllerImpl implements ZenModeController {
return NotificationManager.from(mContext).getEffectsSuppressor();
}
+ @Override
+ public boolean isCountdownConditionSupported() {
+ return NotificationManager.from(mContext)
+ .isSystemConditionProviderEnabled(ZenModeConfig.COUNTDOWN_PATH);
+ }
+
+ @Override
+ public int getCurrentUser() {
+ return ActivityManager.getCurrentUser();
+ }
+
private void fireNextAlarmChanged() {
for (Callback cb : mCallbacks) {
cb.onNextAlarmChanged();
@@ -199,11 +204,15 @@ public class ZenModeControllerImpl implements ZenModeController {
}
}
- private void fireExitConditionChanged() {
- final Condition exitCondition = getExitCondition();
- if (DEBUG) Slog.d(TAG, "exitCondition changed: " + exitCondition);
+ private void fireManualRuleChanged(ZenRule rule) {
for (Callback cb : mCallbacks) {
- cb.onExitConditionChanged(exitCondition);
+ cb.onManualRuleChanged(rule);
+ }
+ }
+
+ private void fireConfigChanged(ZenModeConfig config) {
+ for (Callback cb : mCallbacks) {
+ cb.onConfigChanged(config);
}
}
@@ -217,6 +226,17 @@ public class ZenModeControllerImpl implements ZenModeController {
mConditions.values().toArray(new Condition[mConditions.values().size()]));
}
+ private void updateZenModeConfig() {
+ final ZenModeConfig config = mNoMan.getZenModeConfig();
+ if (Objects.equals(config, mConfig)) return;
+ final ZenRule oldRule = mConfig != null ? mConfig.manualRule : null;
+ mConfig = config;
+ fireConfigChanged(config);
+ final ZenRule newRule = config != null ? config.manualRule : null;
+ if (Objects.equals(oldRule, newRule)) return;
+ fireManualRuleChanged(newRule);
+ }
+
private final IConditionListener mListener = new IConditionListener.Stub() {
@Override
public void onConditionsReceived(Condition[] conditions) {
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 8e677f1..4a7ea96 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
@@ -17,7 +17,10 @@
package com.android.systemui.statusbar.stack;
import android.view.View;
+
import com.android.systemui.statusbar.ActivatableNotificationView;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
import java.util.ArrayList;
@@ -34,6 +37,12 @@ public class AmbientState {
private int mSpeedBumpIndex = -1;
private boolean mDark;
private boolean mHideSensitive;
+ private HeadsUpManager mHeadsUpManager;
+ private float mStackTranslation;
+ private int mLayoutHeight;
+ private int mTopPadding;
+ private boolean mShadeExpanded;
+ private float mMaxHeadsUpTranslation;
public int getScrollY() {
return mScrollY;
@@ -115,4 +124,63 @@ public class AmbientState {
public void setSpeedBumpIndex(int speedBumpIndex) {
mSpeedBumpIndex = speedBumpIndex;
}
+
+ public void setHeadsUpManager(HeadsUpManager headsUpManager) {
+ mHeadsUpManager = headsUpManager;
+ }
+
+ public float getStackTranslation() {
+ return mStackTranslation;
+ }
+
+ public void setStackTranslation(float stackTranslation) {
+ mStackTranslation = stackTranslation;
+ }
+
+ public int getLayoutHeight() {
+ return mLayoutHeight;
+ }
+
+ public void setLayoutHeight(int layoutHeight) {
+ mLayoutHeight = layoutHeight;
+ }
+
+ public float getTopPadding() {
+ return mTopPadding;
+ }
+
+ public void setTopPadding(int topPadding) {
+ mTopPadding = topPadding;
+ }
+
+ public int getInnerHeight() {
+ return mLayoutHeight - mTopPadding - getTopHeadsUpPushIn();
+ }
+
+ private int getTopHeadsUpPushIn() {
+ ExpandableNotificationRow topHeadsUpEntry = getTopHeadsUpEntry();
+ return topHeadsUpEntry != null ? topHeadsUpEntry.getHeadsUpHeight()
+ - topHeadsUpEntry.getMinHeight(): 0;
+ }
+
+ public boolean isShadeExpanded() {
+ return mShadeExpanded;
+ }
+
+ public void setShadeExpanded(boolean shadeExpanded) {
+ mShadeExpanded = shadeExpanded;
+ }
+
+ public void setMaxHeadsUpTranslation(float maxHeadsUpTranslation) {
+ mMaxHeadsUpTranslation = maxHeadsUpTranslation;
+ }
+
+ public float getMaxHeadsUpTranslation() {
+ return mMaxHeadsUpTranslation;
+ }
+
+ public ExpandableNotificationRow getTopHeadsUpEntry() {
+ HeadsUpManager.HeadsUpEntry topEntry = mHeadsUpManager.getTopEntry();
+ return topEntry == null ? null : topEntry.entry.row;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/HeadsUpAppearInterpolator.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/HeadsUpAppearInterpolator.java
new file mode 100644
index 0000000..05c0099
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/HeadsUpAppearInterpolator.java
@@ -0,0 +1,51 @@
+/*
+ * 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.statusbar.stack;
+
+import android.graphics.Path;
+import android.view.animation.PathInterpolator;
+
+/**
+ * An interpolator specifically designed for the appear animation of heads up notifications.
+ */
+public class HeadsUpAppearInterpolator extends PathInterpolator {
+ public HeadsUpAppearInterpolator() {
+ super(getAppearPath());
+ }
+
+ private static Path getAppearPath() {
+ Path path = new Path();
+ path.moveTo(0, 0);
+ float x1 = 250f;
+ float x2 = 150f;
+ float x3 = 100f;
+ float y1 = 90f;
+ float y2 = 78f;
+ float y3 = 80f;
+ float xTot = (x1 + x2 + x3);
+ path.cubicTo(x1 * 0.9f / xTot, 0f,
+ x1 * 0.8f / xTot, y1 / y3,
+ x1 / xTot , y1 / y3);
+ path.cubicTo((x1 + x2 * 0.4f) / xTot, y1 / y3,
+ (x1 + x2 * 0.2f) / xTot, y2 / y3,
+ (x1 + x2) / xTot, y2 / y3);
+ path.cubicTo((x1 + x2 + x3 * 0.4f) / xTot, y2 / y3,
+ (x1 + x2 + x3 * 0.2f) / xTot, 1f,
+ 1f, 1f);
+ return path;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
new file mode 100644
index 0000000..3c9e8cf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
@@ -0,0 +1,406 @@
+/*
+ * 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.statusbar.stack;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.ExpandableView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A container containing child notifications
+ */
+public class NotificationChildrenContainer extends ViewGroup {
+
+ private final int mChildPadding;
+ private final int mDividerHeight;
+ private final int mMaxNotificationHeight;
+ private final List<View> mDividers = new ArrayList<>();
+ private final List<ExpandableNotificationRow> mChildren = new ArrayList<>();
+ private final View mCollapseButton;
+ private final View mCollapseDivider;
+ private final int mCollapseButtonHeight;
+ private final int mNotificationAppearDistance;
+
+ public NotificationChildrenContainer(Context context) {
+ this(context, null);
+ }
+
+ public NotificationChildrenContainer(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ mChildPadding = getResources().getDimensionPixelSize(
+ R.dimen.notification_children_padding);
+ mDividerHeight = getResources().getDimensionPixelSize(
+ R.dimen.notification_children_divider_height);
+ mMaxNotificationHeight = getResources().getDimensionPixelSize(
+ R.dimen.notification_max_height);
+ mNotificationAppearDistance = getResources().getDimensionPixelSize(
+ R.dimen.notification_appear_distance);
+ LayoutInflater inflater = mContext.getSystemService(LayoutInflater.class);
+ mCollapseButton = inflater.inflate(R.layout.notification_collapse_button, this,
+ false);
+ mCollapseButtonHeight = getResources().getDimensionPixelSize(
+ R.dimen.notification_bottom_decor_height);
+ addView(mCollapseButton);
+ mCollapseDivider = inflateDivider();
+ addView(mCollapseDivider);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ int childCount = mChildren.size();
+ boolean firstChild = true;
+ for (int i = 0; i < childCount; i++) {
+ View child = mChildren.get(i);
+ boolean viewGone = child.getVisibility() == View.GONE;
+ if (i != 0) {
+ View divider = mDividers.get(i - 1);
+ int dividerVisibility = divider.getVisibility();
+ int newVisibility = viewGone ? INVISIBLE : VISIBLE;
+ if (dividerVisibility != newVisibility) {
+ divider.setVisibility(newVisibility);
+ }
+ }
+ if (viewGone) {
+ continue;
+ }
+ child.layout(0, 0, getWidth(), child.getMeasuredHeight());
+ if (!firstChild) {
+ mDividers.get(i - 1).layout(0, 0, getWidth(), mDividerHeight);
+ } else {
+ firstChild = false;
+ }
+ }
+ mCollapseButton.layout(0, 0, getWidth(), mCollapseButtonHeight);
+ mCollapseDivider.layout(0, mCollapseButtonHeight - mDividerHeight, getWidth(),
+ mCollapseButtonHeight);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int ownMaxHeight = mMaxNotificationHeight;
+ int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+ boolean hasFixedHeight = heightMode == MeasureSpec.EXACTLY;
+ boolean isHeightLimited = heightMode == MeasureSpec.AT_MOST;
+ if (hasFixedHeight || isHeightLimited) {
+ int size = MeasureSpec.getSize(heightMeasureSpec);
+ ownMaxHeight = Math.min(ownMaxHeight, size);
+ }
+ int newHeightSpec = MeasureSpec.makeMeasureSpec(ownMaxHeight, MeasureSpec.AT_MOST);
+ int dividerHeightSpec = MeasureSpec.makeMeasureSpec(mDividerHeight, MeasureSpec.EXACTLY);
+ int collapseButtonHeightSpec = MeasureSpec.makeMeasureSpec(mCollapseButtonHeight,
+ MeasureSpec.EXACTLY);
+ mCollapseButton.measure(widthMeasureSpec, collapseButtonHeightSpec);
+ mCollapseDivider.measure(widthMeasureSpec, dividerHeightSpec);
+ int height = mCollapseButtonHeight;
+ int childCount = mChildren.size();
+ boolean firstChild = true;
+ for (int i = 0; i < childCount; i++) {
+ View child = mChildren.get(i);
+ if (child.getVisibility() == View.GONE) {
+ continue;
+ }
+ child.measure(widthMeasureSpec, newHeightSpec);
+ height += child.getMeasuredHeight();
+ if (!firstChild) {
+ // layout the divider
+ View divider = mDividers.get(i - 1);
+ divider.measure(widthMeasureSpec, dividerHeightSpec);
+ height += mChildPadding;
+ } else {
+ firstChild = false;
+ }
+ }
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ height = hasFixedHeight ? ownMaxHeight
+ : isHeightLimited ? Math.min(ownMaxHeight, height)
+ : height;
+ setMeasuredDimension(width, height);
+ }
+
+ /**
+ * Add a child notification to this view.
+ *
+ * @param row the row to add
+ * @param childIndex the index to add it at, if -1 it will be added at the end
+ */
+ public void addNotification(ExpandableNotificationRow row, int childIndex) {
+ int newIndex = childIndex < 0 ? mChildren.size() : childIndex;
+ mChildren.add(newIndex, row);
+ addView(row);
+ if (mChildren.size() != 1) {
+ View divider = inflateDivider();
+ addView(divider);
+ mDividers.add(Math.max(newIndex - 1, 0), divider);
+ }
+ // TODO: adapt background corners
+ // TODO: fix overdraw
+ }
+
+ public void removeNotification(ExpandableNotificationRow row) {
+ int childIndex = mChildren.indexOf(row);
+ mChildren.remove(row);
+ removeView(row);
+ if (!mDividers.isEmpty()) {
+ View divider = mDividers.remove(Math.max(childIndex - 1, 0));
+ removeView(divider);
+ }
+ row.setSystemChildExpanded(false);
+ // TODO: adapt background corners
+ }
+
+ private View inflateDivider() {
+ return LayoutInflater.from(mContext).inflate(
+ R.layout.notification_children_divider, this, false);
+ }
+
+ public List<ExpandableNotificationRow> getNotificationChildren() {
+ return mChildren;
+ }
+
+ /**
+ * Apply the order given in the list to the children.
+ *
+ * @param childOrder the new list order
+ * @return whether the list order has changed
+ */
+ public boolean applyChildOrder(List<ExpandableNotificationRow> childOrder) {
+ if (childOrder == null) {
+ return false;
+ }
+ boolean result = false;
+ for (int i = 0; i < mChildren.size() && i < childOrder.size(); i++) {
+ ExpandableNotificationRow child = mChildren.get(i);
+ ExpandableNotificationRow desiredChild = childOrder.get(i);
+ if (child != desiredChild) {
+ mChildren.remove(desiredChild);
+ mChildren.add(i, desiredChild);
+ result = true;
+ }
+ }
+
+ // Let's make the first child expanded!
+ boolean first = true;
+ for (int i = 0; i < childOrder.size(); i++) {
+ ExpandableNotificationRow child = childOrder.get(i);
+ child.setSystemChildExpanded(first);
+ first = false;
+ }
+ return result;
+ }
+
+ public int getIntrinsicHeight() {
+ int childCount = mChildren.size();
+ int intrinsicHeight = 0;
+ int visibleChildren = 0;
+ for (int i = 0; i < childCount; i++) {
+ ExpandableNotificationRow child = mChildren.get(i);
+ if (child.getVisibility() == View.GONE) {
+ continue;
+ }
+ intrinsicHeight += child.getIntrinsicHeight();
+ visibleChildren++;
+ }
+ if (visibleChildren > 0) {
+ intrinsicHeight += (visibleChildren - 1) * mDividerHeight;
+ }
+ return intrinsicHeight;
+ }
+
+ /**
+ * Update the state of all its children based on a linear layout algorithm.
+ *
+ * @param resultState the state to update
+ * @param parentState the state of the parent
+ */
+ public void getState(StackScrollState resultState, StackViewState parentState) {
+ int childCount = mChildren.size();
+ int yPosition = mCollapseButtonHeight;
+ boolean firstChild = true;
+ for (int i = 0; i < childCount; i++) {
+ ExpandableNotificationRow child = mChildren.get(i);
+ if (child.getVisibility() == View.GONE) {
+ continue;
+ }
+ if (!firstChild) {
+ // There's a divider
+ yPosition += mChildPadding;
+ } else {
+ firstChild = false;
+ }
+ StackViewState childState = resultState.getViewStateForView(child);
+ int intrinsicHeight = child.getIntrinsicHeight();
+ childState.yTranslation = yPosition;
+ childState.zTranslation = 0;
+ childState.height = intrinsicHeight;
+ childState.dimmed = parentState.dimmed;
+ childState.dark = parentState.dark;
+ childState.hideSensitive = parentState.hideSensitive;
+ childState.belowSpeedBump = parentState.belowSpeedBump;
+ childState.scale = parentState.scale;
+ childState.clipTopAmount = 0;
+ childState.topOverLap = 0;
+ childState.location = parentState.location;
+ yPosition += intrinsicHeight;
+ }
+ }
+
+ public void applyState(StackScrollState state) {
+ int childCount = mChildren.size();
+ boolean firstChild = true;
+ ViewState dividerState = new ViewState();
+ for (int i = 0; i < childCount; i++) {
+ ExpandableNotificationRow child = mChildren.get(i);
+ StackViewState viewState = state.getViewStateForView(child);
+ if (child.getVisibility() == View.GONE) {
+ continue;
+ }
+ if (!firstChild) {
+ // layout the divider
+ View divider = mDividers.get(i - 1);
+ dividerState.initFrom(divider);
+ dividerState.yTranslation = (int) (viewState.yTranslation
+ - (mChildPadding + mDividerHeight) / 2.0f);
+ dividerState.alpha = 1;
+ state.applyViewState(divider, dividerState);
+ } else {
+ firstChild = false;
+ }
+ state.applyState(child, viewState);
+ }
+ }
+
+ public void setCollapseClickListener(OnClickListener collapseClickListener) {
+ mCollapseButton.setOnClickListener(collapseClickListener);
+ }
+
+ /**
+ * This is called when the children expansion has changed and positions the children properly
+ * for an appear animation.
+ *
+ * @param state the new state we animate to
+ */
+ public void prepareExpansionChanged(StackScrollState state) {
+ int childCount = mChildren.size();
+ boolean firstChild = true;
+ StackViewState sourceState = new StackViewState();
+ ViewState dividerState = new ViewState();
+ for (int i = 0; i < childCount; i++) {
+ ExpandableNotificationRow child = mChildren.get(i);
+ StackViewState viewState = state.getViewStateForView(child);
+ if (child.getVisibility() == View.GONE) {
+ continue;
+ }
+ if (!firstChild) {
+ // layout the divider
+ View divider = mDividers.get(i - 1);
+ dividerState.initFrom(divider);
+ dividerState.yTranslation = viewState.yTranslation
+ - (mChildPadding + mDividerHeight) / 2.0f + mNotificationAppearDistance;
+ dividerState.alpha = 0;
+ state.applyViewState(divider, dividerState);
+ } else {
+ firstChild = false;
+ }
+ sourceState.copyFrom(viewState);
+ sourceState.alpha = 0;
+ sourceState.yTranslation += mNotificationAppearDistance;
+ state.applyState(child, sourceState);
+ }
+ mCollapseButton.setAlpha(0);
+ mCollapseDivider.setAlpha(0);
+ mCollapseDivider.setTranslationY(mNotificationAppearDistance / 4);
+ }
+
+ public void startAnimationToState(StackScrollState state, StackStateAnimator stateAnimator,
+ boolean withDelays, long baseDelay, long duration) {
+ int childCount = mChildren.size();
+ boolean firstChild = true;
+ ViewState dividerState = new ViewState();
+ int notGoneIndex = 0;
+ for (int i = 0; i < childCount; i++) {
+ ExpandableNotificationRow child = mChildren.get(i);
+ StackViewState viewState = state.getViewStateForView(child);
+ if (child.getVisibility() == View.GONE) {
+ continue;
+ }
+ int difference = Math.min(StackStateAnimator.DELAY_EFFECT_MAX_INDEX_DIFFERENCE_CHILDREN,
+ notGoneIndex + 1);
+ long delay = withDelays
+ ? difference * StackStateAnimator.ANIMATION_DELAY_PER_ELEMENT_EXPAND_CHILDREN
+ : 0;
+ delay += baseDelay;
+ if (!firstChild) {
+ // layout the divider
+ View divider = mDividers.get(i - 1);
+ dividerState.initFrom(divider);
+ dividerState.yTranslation = viewState.yTranslation
+ - (mChildPadding + mDividerHeight) / 2.0f;
+ dividerState.alpha = 1;
+ stateAnimator.startViewAnimations(divider, dividerState, delay, duration);
+ } else {
+ firstChild = false;
+ }
+ stateAnimator.startStackAnimations(child, viewState, state, -1, delay);
+ notGoneIndex++;
+ }
+ dividerState.initFrom(mCollapseButton);
+ dividerState.alpha = 1.0f;
+ stateAnimator.startViewAnimations(mCollapseButton, dividerState, baseDelay, duration);
+ dividerState.initFrom(mCollapseDivider);
+ dividerState.alpha = 1.0f;
+ dividerState.yTranslation = 0.0f;
+ stateAnimator.startViewAnimations(mCollapseDivider, dividerState, baseDelay, duration);
+ }
+
+ public ExpandableNotificationRow getViewAtPosition(float y) {
+ // find the view under the pointer, accounting for GONE views
+ final int count = mChildren.size();
+ for (int childIdx = 0; childIdx < count; childIdx++) {
+ ExpandableNotificationRow slidingChild = mChildren.get(childIdx);
+ float childTop = slidingChild.getTranslationY();
+ float top = childTop + slidingChild.getClipTopAmount();
+ float bottom = childTop + slidingChild.getActualHeight();
+ if (y >= top && y <= bottom) {
+ return slidingChild;
+ }
+ }
+ return null;
+ }
+
+ public void setTintColor(int color) {
+ ExpandableNotificationRow.applyTint(mCollapseDivider, color);
+ }
+}
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 6dcbed6..7040864 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -24,6 +24,7 @@ import android.graphics.Paint;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.util.Log;
+import android.util.Pair;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
@@ -41,12 +42,16 @@ import com.android.systemui.statusbar.DismissView;
import com.android.systemui.statusbar.EmptyShadeView;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.ExpandableView;
+import com.android.systemui.statusbar.NotificationData;
+import com.android.systemui.statusbar.NotificationOverflowContainer;
import com.android.systemui.statusbar.SpeedBumpView;
import com.android.systemui.statusbar.StackScrollerDecorView;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
+import com.android.systemui.statusbar.phone.ScrimController;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.ScrollAdapter;
-import com.android.systemui.statusbar.stack.StackScrollState.ViewState;
import java.util.ArrayList;
import java.util.HashSet;
@@ -56,7 +61,7 @@ import java.util.HashSet;
*/
public class NotificationStackScrollLayout extends ViewGroup
implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter,
- ExpandableView.OnHeightChangedListener {
+ ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener {
private static final String TAG = "NotificationStackScrollLayout";
private static final boolean DEBUG = false;
@@ -119,15 +124,16 @@ public class NotificationStackScrollLayout extends ViewGroup
*/
private StackScrollState mCurrentStackScrollState = new StackScrollState(this);
private AmbientState mAmbientState = new AmbientState();
- private ArrayList<View> mChildrenToAddAnimated = new ArrayList<View>();
- private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<View>();
- private ArrayList<View> mSnappedBackChildren = new ArrayList<View>();
- private ArrayList<View> mDragAnimPendingChildren = new ArrayList<View>();
- private ArrayList<View> mChildrenChangingPositions = new ArrayList<View>();
+ private NotificationGroupManager mGroupManager;
+ private ArrayList<View> mChildrenToAddAnimated = new ArrayList<>();
+ private ArrayList<View> mAddedHeadsUpChildren = new ArrayList<>();
+ private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<>();
+ private ArrayList<View> mSnappedBackChildren = new ArrayList<>();
+ private ArrayList<View> mDragAnimPendingChildren = new ArrayList<>();
+ private ArrayList<View> mChildrenChangingPositions = new ArrayList<>();
private HashSet<View> mFromMoreCardAdditions = new HashSet<>();
- private ArrayList<AnimationEvent> mAnimationEvents
- = new ArrayList<AnimationEvent>();
- private ArrayList<View> mSwipedOutViews = new ArrayList<View>();
+ private ArrayList<AnimationEvent> mAnimationEvents = new ArrayList<>();
+ private ArrayList<View> mSwipedOutViews = new ArrayList<>();
private final StackStateAnimator mStateAnimator = new StackStateAnimator(this);
private boolean mAnimationsEnabled;
private boolean mChangePositionInProgress;
@@ -141,7 +147,6 @@ 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 OnOverscrollTopChangedListener mOverscrollTopChangedListener;
private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
@@ -158,6 +163,7 @@ public class NotificationStackScrollLayout extends ViewGroup
private boolean mChildrenUpdateRequested;
private SpeedBumpView mSpeedBumpView;
private boolean mIsExpansionChanging;
+ private boolean mPanelTracking;
private boolean mExpandingNotification;
private boolean mExpandedInThisMotion;
private boolean mScrollingEnabled;
@@ -169,7 +175,6 @@ public class NotificationStackScrollLayout extends ViewGroup
* Was the scroller scrolled to the top when the down motion was observed?
*/
private boolean mScrolledToTopOnFirstDown;
-
/**
* The minimal amount of over scroll which is needed in order to switch to the quick settings
* when over scrolling on a expanded card.
@@ -177,10 +182,12 @@ public class NotificationStackScrollLayout extends ViewGroup
private float mMinTopOverScrollToEscape;
private int mIntrinsicPadding;
private int mNotificationTopPadding;
+ private float mStackTranslation;
private float mTopPaddingOverflow;
private boolean mDontReportNextOverScroll;
private boolean mRequestViewResizeAnimationOnLayout;
private boolean mNeedViewResizeAnimation;
+ private View mExpandedGroupView;
private boolean mEverythingNeedsAnimation;
/**
@@ -201,7 +208,6 @@ public class NotificationStackScrollLayout extends ViewGroup
private boolean mDelegateToScrollView;
private boolean mDisallowScrollingInThisMotion;
private long mGoToFullShadeDelay;
-
private ViewTreeObserver.OnPreDrawListener mChildrenUpdater
= new ViewTreeObserver.OnPreDrawListener() {
@Override
@@ -214,6 +220,16 @@ public class NotificationStackScrollLayout extends ViewGroup
};
private PhoneStatusBar mPhoneStatusBar;
private int[] mTempInt2 = new int[2];
+ private boolean mGenerateChildOrderChangedEvent;
+ private HashSet<Runnable> mAnimationFinishedRunnables = new HashSet<>();
+ private HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations
+ = new HashSet<>();
+ private HeadsUpManager mHeadsUpManager;
+ private boolean mTrackingHeadsUp;
+ private ScrimController mScrimController;
+ private boolean mForceNoOverlappingRendering;
+ private NotificationOverflowContainer mOverflowContainer;
+ private final ArrayList<Pair<ExpandableNotificationRow, Boolean>> mTmpList = new ArrayList<>();
public NotificationStackScrollLayout(Context context) {
this(context, null);
@@ -309,7 +325,7 @@ public class NotificationStackScrollLayout extends ViewGroup
private void notifyHeightChangeListener(ExpandableView view) {
if (mOnHeightChangedListener != null) {
- mOnHeightChangedListener.onHeightChanged(view);
+ mOnHeightChangedListener.onHeightChanged(view, false /* needsAnimation */);
}
}
@@ -329,6 +345,9 @@ public class NotificationStackScrollLayout extends ViewGroup
float centerX = getWidth() / 2.0f;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
+ if (child.getVisibility() == GONE) {
+ continue;
+ }
float width = child.getMeasuredWidth();
float height = child.getMeasuredHeight();
child.layout((int) (centerX - width / 2.0f),
@@ -339,16 +358,18 @@ public class NotificationStackScrollLayout extends ViewGroup
setMaxLayoutHeight(getHeight());
updateContentHeight();
clampScrollPosition();
- requestAnimationOnViewResize();
+ if (mRequestViewResizeAnimationOnLayout) {
+ requestAnimationOnViewResize();
+ mRequestViewResizeAnimationOnLayout = false;
+ }
requestChildrenUpdate();
}
private void requestAnimationOnViewResize() {
- if (mRequestViewResizeAnimationOnLayout && mIsExpanded && mAnimationsEnabled) {
+ if (mIsExpanded && mAnimationsEnabled) {
mNeedViewResizeAnimation = true;
mNeedsAnimation = true;
}
- mRequestViewResizeAnimationOnLayout = false;
}
public void updateSpeedBumpIndex(int newIndex) {
@@ -375,15 +396,15 @@ public class NotificationStackScrollLayout extends ViewGroup
* Returns the location the given child is currently rendered at.
*
* @param child the child to get the location for
- * @return one of {@link ViewState}'s <code>LOCATION_*</code> constants
+ * @return one of {@link StackViewState}'s <code>LOCATION_*</code> constants
*/
public int getChildLocation(View child) {
- ViewState childViewState = mCurrentStackScrollState.getViewStateForView(child);
+ StackViewState childViewState = mCurrentStackScrollState.getViewStateForView(child);
if (childViewState == null) {
- return ViewState.LOCATION_UNKNOWN;
+ return StackViewState.LOCATION_UNKNOWN;
}
if (childViewState.gone) {
- return ViewState.LOCATION_GONE;
+ return StackViewState.LOCATION_GONE;
}
return childViewState.location;
}
@@ -394,8 +415,8 @@ public class NotificationStackScrollLayout extends ViewGroup
}
private void updateAlgorithmHeightAndPadding() {
- mStackScrollAlgorithm.setLayoutHeight(getLayoutHeight());
- mStackScrollAlgorithm.setTopPadding(mTopPadding);
+ mAmbientState.setLayoutHeight(getLayoutHeight());
+ mAmbientState.setTopPadding(mTopPadding);
}
/**
@@ -468,9 +489,13 @@ public class NotificationStackScrollLayout extends ViewGroup
int newStackHeight = (int) height;
int minStackHeight = getMinStackHeight();
int stackHeight;
- if (newStackHeight - mTopPadding - mTopPaddingOverflow >= minStackHeight
+ float paddingOffset;
+ boolean trackingHeadsUp = mTrackingHeadsUp || mHeadsUpManager.hasPinnedHeadsUp();
+ int normalUnfoldPositionStart = trackingHeadsUp ? mHeadsUpManager.getTopHeadsUpHeight()
+ : minStackHeight;
+ if (newStackHeight - mTopPadding - mTopPaddingOverflow >= normalUnfoldPositionStart
|| getNotGoneChildCount() == 0) {
- setTranslationY(mTopPaddingOverflow);
+ paddingOffset = mTopPaddingOverflow;
stackHeight = newStackHeight;
} else {
@@ -482,9 +507,13 @@ public class NotificationStackScrollLayout extends ViewGroup
float partiallyThere = (newStackHeight - mTopPadding - mTopPaddingOverflow)
/ minStackHeight;
partiallyThere = Math.max(0, partiallyThere);
- translationY += (1 - partiallyThere) * (mBottomStackPeekSize +
- mCollapseSecondCardPadding);
- setTranslationY(translationY - mTopPadding);
+ if (!trackingHeadsUp) {
+ translationY += (1 - partiallyThere) * (mBottomStackPeekSize +
+ mCollapseSecondCardPadding);
+ } else {
+ translationY = (int) (height - mHeadsUpManager.getTopHeadsUpHeight());
+ }
+ paddingOffset = translationY - mTopPadding;
stackHeight = (int) (height - (translationY - mTopPadding));
}
if (stackHeight != mCurrentStackHeight) {
@@ -492,6 +521,19 @@ public class NotificationStackScrollLayout extends ViewGroup
updateAlgorithmHeightAndPadding();
requestChildrenUpdate();
}
+ setStackTranslation(paddingOffset);
+ }
+
+ public float getStackTranslation() {
+ return mStackTranslation;
+ }
+
+ private void setStackTranslation(float stackTranslation) {
+ if (stackTranslation != mStackTranslation) {
+ mStackTranslation = stackTranslation;
+ mAmbientState.setStackTranslation(stackTranslation);
+ requestChildrenUpdate();
+ }
}
/**
@@ -533,11 +575,6 @@ public class NotificationStackScrollLayout extends ViewGroup
if (mDismissAllInProgress) {
return;
}
- if (DEBUG) Log.v(TAG, "onChildDismissed: " + v);
- final View veto = v.findViewById(R.id.veto);
- if (veto != null && veto.getVisibility() != View.GONE) {
- veto.performClick();
- }
setSwipingInProgress(false);
if (mDragAnimPendingChildren.contains(v)) {
// We start the swipe and finish it in the same frame, we don't want any animation
@@ -546,6 +583,17 @@ public class NotificationStackScrollLayout extends ViewGroup
}
mSwipedOutViews.add(v);
mAmbientState.onDragFinished(v);
+ if (v instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+ if (row.isHeadsUp()) {
+ mHeadsUpManager.addSwipedOutNotification(row.getStatusBarNotification().getKey());
+ }
+ }
+ final View veto = v.findViewById(R.id.veto);
+ if (veto != null && veto.getVisibility() != View.GONE) {
+ veto.performClick();
+ }
+ if (DEBUG) Log.v(TAG, "onChildDismissed: " + v);
}
@Override
@@ -565,28 +613,48 @@ public class NotificationStackScrollLayout extends ViewGroup
@Override
public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) {
+ if (!mIsExpanded && isPinnedHeadsUp(animView) && canChildBeDismissed(animView)) {
+ mScrimController.setTopHeadsUpDragAmount(animView,
+ Math.min(Math.abs(swipeProgress - 1.0f), 1.0f));
+ }
return false;
}
- @Override
- public float getFalsingThresholdFactor() {
- return mPhoneStatusBar.isScreenOnComingFromTouch() ? 1.5f : 1.0f;
- }
-
public void onBeginDrag(View v) {
setSwipingInProgress(true);
mAmbientState.onBeginDrag(v);
- if (mAnimationsEnabled) {
+ if (mAnimationsEnabled && (mIsExpanded || !isPinnedHeadsUp(v))) {
mDragAnimPendingChildren.add(v);
mNeedsAnimation = true;
}
requestChildrenUpdate();
}
+ public static boolean isPinnedHeadsUp(View v) {
+ if (v instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+ return row.isHeadsUp() && row.isPinned();
+ }
+ return false;
+ }
+
+ private boolean isHeadsUp(View v) {
+ if (v instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+ return row.isHeadsUp();
+ }
+ return false;
+ }
+
public void onDragCancelled(View v) {
setSwipingInProgress(false);
}
+ @Override
+ public float getFalsingThresholdFactor() {
+ return mPhoneStatusBar.isScreenOnComingFromTouch() ? 1.5f : 1.0f;
+ }
+
public View getChildAtPosition(MotionEvent ev) {
return getChildAtPosition(ev.getX(), ev.getY());
}
@@ -645,6 +713,14 @@ public class NotificationStackScrollLayout extends ViewGroup
int right = getWidth();
if (touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) {
+ if (slidingChild instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild;
+ if (!mIsExpanded && row.isHeadsUp() && row.isPinned()
+ && mHeadsUpManager.getTopEntry().entry.row != row) {
+ continue;
+ }
+ return row.getViewAtPosition(touchY - childTop);
+ }
return slidingChild;
}
}
@@ -653,7 +729,8 @@ public class NotificationStackScrollLayout extends ViewGroup
public boolean canChildBeExpanded(View v) {
return v instanceof ExpandableNotificationRow
- && ((ExpandableNotificationRow) v).isExpandable();
+ && ((ExpandableNotificationRow) v).isExpandable()
+ && !((ExpandableNotificationRow) v).isHeadsUp();
}
public void setUserExpandedChild(View v, boolean userExpanded) {
@@ -723,7 +800,6 @@ public class NotificationStackScrollLayout extends ViewGroup
}
public void dismissViewAnimated(View child, Runnable endRunnable, int delay, long duration) {
- child.setClipBounds(null);
mSwipeHelper.dismissChild(child, 0, endRunnable, delay, true, duration);
}
@@ -740,7 +816,7 @@ public class NotificationStackScrollLayout extends ViewGroup
}
handleEmptySpaceClick(ev);
boolean expandWantsIt = false;
- if (!mSwipingInProgress && !mOnlyScrollingInThisMotion && isScrollingEnabled()) {
+ if (mIsExpanded && !mSwipingInProgress && !mOnlyScrollingInThisMotion) {
if (isCancelOrUp) {
mExpandHelper.onlyObserveMovements(false);
}
@@ -752,7 +828,8 @@ public class NotificationStackScrollLayout extends ViewGroup
}
}
boolean scrollerWantsIt = false;
- if (!mSwipingInProgress && !mExpandingNotification && !mDisallowScrollingInThisMotion) {
+ if (mIsExpanded && !mSwipingInProgress && !mExpandingNotification
+ && !mDisallowScrollingInThisMotion) {
scrollerWantsIt = onScrollTouch(ev);
}
boolean horizontalSwipeWantsIt = false;
@@ -1295,17 +1372,11 @@ public class NotificationStackScrollLayout extends ViewGroup
int childCount = getChildCount();
int count = 0;
for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- if (child.getVisibility() != View.GONE) {
+ ExpandableView child = (ExpandableView) getChildAt(i);
+ if (child.getVisibility() != View.GONE && !child.willBeGone()) {
count++;
}
}
- if (mDismissView.willBeGone()) {
- count--;
- }
- if (mEmptyShadeView.willBeGone()) {
- count--;
- }
return count;
}
@@ -1330,12 +1401,9 @@ public class NotificationStackScrollLayout extends ViewGroup
// add the padding before this element
height += mPaddingBetweenElements;
}
- if (child instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
- height += row.getIntrinsicHeight();
- } else if (child instanceof ExpandableView) {
+ if (child instanceof ExpandableView) {
ExpandableView expandableView = (ExpandableView) child;
- height += expandableView.getActualHeight();
+ height += expandableView.getIntrinsicHeight();
}
}
}
@@ -1446,7 +1514,7 @@ public class NotificationStackScrollLayout extends ViewGroup
}
if (mExpandedInThisMotion) {
return RUBBER_BAND_FACTOR_AFTER_EXPAND;
- } else if (mIsExpansionChanging) {
+ } else if (mIsExpansionChanging || mPanelTracking) {
return RUBBER_BAND_FACTOR_ON_PANEL_EXPAND;
} else if (mScrolledToTopOnFirstDown) {
return 1.0f;
@@ -1460,7 +1528,7 @@ public class NotificationStackScrollLayout extends ViewGroup
* overscroll view (e.g. expand QS).
*/
private boolean isRubberbanded(boolean onTop) {
- return !onTop || mExpandedInThisMotion || mIsExpansionChanging
+ return !onTop || mExpandedInThisMotion || mIsExpansionChanging || mPanelTracking
|| !mScrolledToTopOnFirstDown;
}
@@ -1496,7 +1564,7 @@ public class NotificationStackScrollLayout extends ViewGroup
initDownStates(ev);
handleEmptySpaceClick(ev);
boolean expandWantsIt = false;
- if (!mSwipingInProgress && !mOnlyScrollingInThisMotion && isScrollingEnabled()) {
+ if (!mSwipingInProgress && !mOnlyScrollingInThisMotion) {
expandWantsIt = mExpandHelper.onInterceptTouchEvent(ev);
}
boolean scrollWantsIt = false;
@@ -1542,8 +1610,16 @@ public class NotificationStackScrollLayout extends ViewGroup
}
@Override
- protected void onViewRemoved(View child) {
+ public void onViewRemoved(View child) {
super.onViewRemoved(child);
+ // we only call our internal methods if this is actually a removal and not just a
+ // notification which becomes a child notification
+ if (!isChildInGroup(child)) {
+ onViewRemovedInternal(child);
+ }
+ }
+
+ private void onViewRemovedInternal(View child) {
mStackScrollAlgorithm.notifyChildrenChanged(this);
if (mChangePositionInProgress) {
// This is only a position change, don't do anything special
@@ -1561,7 +1637,13 @@ public class NotificationStackScrollLayout extends ViewGroup
updateAnimationState(false, child);
// Make sure the clipRect we might have set is removed
- child.setClipBounds(null);
+ ((ExpandableView) child).setClipTopOptimization(0);
+ }
+
+ private boolean isChildInGroup(View child) {
+ return child instanceof ExpandableNotificationRow
+ && mGroupManager.isChildInGroupWithSummary(
+ ((ExpandableNotificationRow) child).getStatusBarNotification());
}
/**
@@ -1571,7 +1653,11 @@ public class NotificationStackScrollLayout extends ViewGroup
* @return Whether an animation was generated.
*/
private boolean generateRemoveAnimation(View child) {
- if (mIsExpanded && mAnimationsEnabled) {
+ if (removeRemovedChildFromHeadsUpChangeAnimations(child)) {
+ mAddedHeadsUpChildren.remove(child);
+ return false;
+ }
+ if (mIsExpanded && mAnimationsEnabled && !isChildInInvisibleGroup(child)) {
if (!mChildrenToAddAnimated.contains(child)) {
// Generate Animations
mChildrenToRemoveAnimated.add(child);
@@ -1587,6 +1673,46 @@ public class NotificationStackScrollLayout extends ViewGroup
}
/**
+ * Remove a removed child view from the heads up animations if it was just added there
+ *
+ * @return whether any child was removed from the list to animate
+ */
+ private boolean removeRemovedChildFromHeadsUpChangeAnimations(View child) {
+ boolean hasAddEvent = false;
+ for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
+ ExpandableNotificationRow row = eventPair.first;
+ boolean isHeadsUp = eventPair.second;
+ if (child == row) {
+ mTmpList.add(eventPair);
+ hasAddEvent |= isHeadsUp;
+ }
+ }
+ if (hasAddEvent) {
+ // This child was just added lets remove all events.
+ mHeadsUpChangeAnimations.removeAll(mTmpList);
+ }
+ mTmpList.clear();
+ return hasAddEvent;
+ }
+
+ /**
+ * @param child the child to query
+ * @return whether a view is not a top level child but a child notification and that group is
+ * not expanded
+ */
+ private boolean isChildInInvisibleGroup(View child) {
+ if (child instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ ExpandableNotificationRow groupSummary =
+ mGroupManager.getGroupSummary(row.getStatusBarNotification());
+ if (groupSummary != null && groupSummary != row) {
+ return !groupSummary.areChildrenExpanded();
+ }
+ }
+ return false;
+ }
+
+ /**
* Updates the scroll position when a child was removed
*
* @param removedChild the removed child
@@ -1632,8 +1758,12 @@ public class NotificationStackScrollLayout extends ViewGroup
}
@Override
- protected void onViewAdded(View child) {
+ public void onViewAdded(View child) {
super.onViewAdded(child);
+ onViewAddedInternal(child);
+ }
+
+ private void onViewAddedInternal(View child) {
mStackScrollAlgorithm.notifyChildrenChanged(this);
((ExpandableView) child).setOnHeightChangedListener(this);
generateAddAnimation(child, false /* fromMoreCard */);
@@ -1646,22 +1776,31 @@ public class NotificationStackScrollLayout extends ViewGroup
}
}
+ public void notifyGroupChildRemoved(View row) {
+ onViewRemovedInternal(row);
+ }
+
+ public void notifyGroupChildAdded(View row) {
+ onViewAddedInternal(row);
+ }
+
public void setAnimationsEnabled(boolean animationsEnabled) {
mAnimationsEnabled = animationsEnabled;
updateNotificationAnimationStates();
}
private void updateNotificationAnimationStates() {
- boolean running = mIsExpanded && mAnimationsEnabled;
+ boolean running = mAnimationsEnabled;
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
+ running &= mIsExpanded || isPinnedHeadsUp(child);
updateAnimationState(running, child);
}
}
private void updateAnimationState(View child) {
- updateAnimationState(mAnimationsEnabled && mIsExpanded, child);
+ updateAnimationState((mAnimationsEnabled || isPinnedHeadsUp(child)) && mIsExpanded, child);
}
@@ -1691,6 +1830,10 @@ public class NotificationStackScrollLayout extends ViewGroup
}
mNeedsAnimation = true;
}
+ if (isHeadsUp(child)) {
+ mAddedHeadsUpChildren.add(child);
+ mChildrenToAddAnimated.remove(child);
+ }
}
/**
@@ -1729,6 +1872,7 @@ public class NotificationStackScrollLayout extends ViewGroup
}
private void generateChildHierarchyEvents() {
+ generateHeadsUpAnimationEvents();
generateChildRemovalEvents();
generateChildAdditionEvents();
generatePositionChangeEvents();
@@ -1741,10 +1885,62 @@ public class NotificationStackScrollLayout extends ViewGroup
generateDarkEvent();
generateGoToFullShadeEvent();
generateViewResizeEvent();
+ generateGroupExpansionEvent();
generateAnimateEverythingEvent();
mNeedsAnimation = false;
}
+ private void generateHeadsUpAnimationEvents() {
+ for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
+ ExpandableNotificationRow row = eventPair.first;
+ boolean isHeadsUp = eventPair.second;
+ int type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_OTHER;
+ boolean onBottom = false;
+ boolean pinnedAndClosed = row.isPinned() && !mIsExpanded;
+ if (!mIsExpanded && !isHeadsUp) {
+ type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR;
+ } else {
+ StackViewState viewState = mCurrentStackScrollState.getViewStateForView(row);
+ if (viewState == null) {
+ // A view state was never generated for this view, so we don't need to animate
+ // this. This may happen with notification children.
+ continue;
+ }
+ if (isHeadsUp && (mAddedHeadsUpChildren.contains(row) || pinnedAndClosed)) {
+ if (pinnedAndClosed || shouldHunAppearFromBottom(viewState)) {
+ // Our custom add animation
+ type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR;
+ } else {
+ // Normal add animation
+ type = AnimationEvent.ANIMATION_TYPE_ADD;
+ }
+ onBottom = !pinnedAndClosed;
+ }
+ }
+ AnimationEvent event = new AnimationEvent(row, type);
+ event.headsUpFromBottom = onBottom;
+ mAnimationEvents.add(event);
+ }
+ mHeadsUpChangeAnimations.clear();
+ mAddedHeadsUpChildren.clear();
+ }
+
+ private boolean shouldHunAppearFromBottom(StackViewState viewState) {
+ if (viewState.yTranslation + viewState.height < mAmbientState.getMaxHeadsUpTranslation()) {
+ return false;
+ }
+ return true;
+ }
+
+ private void generateGroupExpansionEvent() {
+ // Generate a group expansion/collapsing event if there is such a group at all
+ if (mExpandedGroupView != null) {
+ mAnimationEvents.add(new AnimationEvent(mExpandedGroupView,
+ AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED));
+ mExpandedGroupView = null;
+ }
+ }
+
private void generateViewResizeEvent() {
if (mNeedViewResizeAnimation) {
mAnimationEvents.add(
@@ -1791,6 +1987,11 @@ public class NotificationStackScrollLayout extends ViewGroup
AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
}
mChildrenChangingPositions.clear();
+ if (mGenerateChildOrderChangedEvent) {
+ mAnimationEvents.add(new AnimationEvent(null,
+ AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
+ mGenerateChildOrderChangedEvent = false;
+ }
}
private void generateChildAdditionEvents() {
@@ -2049,6 +2250,18 @@ public class NotificationStackScrollLayout extends ViewGroup
}
}
+ public void onPanelTrackingStarted() {
+ mPanelTracking = true;
+ }
+ public void onPanelTrackingStopped() {
+ mPanelTracking = false;
+ }
+
+ public void resetScrollPosition() {
+ mScroller.abortAnimation();
+ mOwnScrollY = 0;
+ }
+
private void setIsExpanded(boolean isExpanded) {
boolean changed = isExpanded != mIsExpanded;
mIsExpanded = isExpanded;
@@ -2059,11 +2272,14 @@ public class NotificationStackScrollLayout extends ViewGroup
}
@Override
- public void onHeightChanged(ExpandableView view) {
+ public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
updateContentHeight();
updateScrollPositionOnExpandInBottom(view);
clampScrollPosition();
notifyHeightChangeListener(view);
+ if (needsAnimation) {
+ requestAnimationOnViewResize();
+ }
requestChildrenUpdate();
}
@@ -2079,11 +2295,11 @@ public class NotificationStackScrollLayout extends ViewGroup
private void updateScrollPositionOnExpandInBottom(ExpandableView view) {
if (view instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) view;
- if (row.isUserLocked()) {
+ if (row.isUserLocked() && row != getFirstChildNotGone()) {
// We are actually expanding this view
float endPosition = row.getTranslationY() + row.getActualHeight();
int stackEnd = mMaxLayoutHeight - mBottomStackPeekSize -
- mBottomStackSlowDownHeight;
+ mBottomStackSlowDownHeight + (int) mStackTranslation;
if (endPosition > stackEnd) {
mOwnScrollY += endPosition - stackEnd;
mDisallowScrollingInThisMotion = true;
@@ -2103,6 +2319,14 @@ public class NotificationStackScrollLayout extends ViewGroup
public void onChildAnimationFinished() {
requestChildrenUpdate();
+ runAnimationFinishedRunnables();
+ }
+
+ private void runAnimationFinishedRunnables() {
+ for (Runnable runnable : mAnimationFinishedRunnables) {
+ runnable.run();
+ }
+ mAnimationFinishedRunnables.clear();
}
/**
@@ -2156,6 +2380,7 @@ public class NotificationStackScrollLayout extends ViewGroup
if (mListener != null) {
mListener.onChildLocationsChanged(this);
}
+ runAnimationFinishedRunnables();
}
public void setSpeedBumpView(SpeedBumpView speedBumpView) {
@@ -2204,7 +2429,7 @@ public class NotificationStackScrollLayout extends ViewGroup
* @return the y position of the first notification
*/
public float getNotificationsTopY() {
- return mTopPadding + getTranslationY();
+ return mTopPadding + getStackTranslation();
}
@Override
@@ -2278,7 +2503,7 @@ public class NotificationStackScrollLayout extends ViewGroup
mEmptyShadeView.setVisibility(newVisibility);
mEmptyShadeView.setWillBeGone(false);
updateContentHeight();
- notifyHeightChangeListener(mDismissView);
+ notifyHeightChangeListener(mEmptyShadeView);
} else {
Runnable onFinishedRunnable = new Runnable() {
@Override
@@ -2286,7 +2511,7 @@ public class NotificationStackScrollLayout extends ViewGroup
mEmptyShadeView.setVisibility(GONE);
mEmptyShadeView.setWillBeGone(false);
updateContentHeight();
- notifyHeightChangeListener(mDismissView);
+ notifyHeightChangeListener(mEmptyShadeView);
}
};
if (mAnimationsEnabled) {
@@ -2300,6 +2525,45 @@ public class NotificationStackScrollLayout extends ViewGroup
}
}
+ public void setOverflowContainer(NotificationOverflowContainer overFlowContainer) {
+ mOverflowContainer = overFlowContainer;
+ addView(mOverflowContainer);
+ }
+
+ public void updateOverflowContainerVisibility(boolean visible) {
+ int oldVisibility = mOverflowContainer.willBeGone() ? GONE
+ : mOverflowContainer.getVisibility();
+ final int newVisibility = visible ? VISIBLE : GONE;
+ if (oldVisibility != newVisibility) {
+ Runnable onFinishedRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mOverflowContainer.setVisibility(newVisibility);
+ mOverflowContainer.setWillBeGone(false);
+ updateContentHeight();
+ notifyHeightChangeListener(mOverflowContainer);
+ }
+ };
+ if (!mAnimationsEnabled || !mIsExpanded) {
+ mOverflowContainer.cancelAppearDrawing();
+ onFinishedRunnable.run();
+ } else if (newVisibility != GONE) {
+ mOverflowContainer.performAddAnimation(0,
+ StackStateAnimator.ANIMATION_DURATION_STANDARD);
+ mOverflowContainer.setVisibility(newVisibility);
+ mOverflowContainer.setWillBeGone(false);
+ updateContentHeight();
+ notifyHeightChangeListener(mOverflowContainer);
+ } else {
+ mOverflowContainer.performRemoveAnimation(
+ StackStateAnimator.ANIMATION_DURATION_STANDARD,
+ 0.0f,
+ onFinishedRunnable);
+ mOverflowContainer.setWillBeGone(true);
+ }
+ }
+ }
+
public void updateDismissView(boolean visible) {
int oldVisibility = mDismissView.willBeGone() ? GONE : mDismissView.getVisibility();
int newVisibility = visible ? VISIBLE : GONE;
@@ -2338,6 +2602,20 @@ public class NotificationStackScrollLayout extends ViewGroup
public void setDismissAllInProgress(boolean dismissAllInProgress) {
mDismissAllInProgress = dismissAllInProgress;
mDismissView.setDismissAllInProgress(dismissAllInProgress);
+ if (dismissAllInProgress) {
+ disableClipOptimization();
+ }
+ }
+
+ private void disableClipOptimization() {
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ ExpandableView child = (ExpandableView) getChildAt(i);
+ if (child.getVisibility() == GONE) {
+ continue;
+ }
+ child.setClipTopOptimization(0);
+ }
}
public boolean isDismissViewNotGone() {
@@ -2377,7 +2655,7 @@ public class NotificationStackScrollLayout extends ViewGroup
max = bottom;
}
}
- return max + getTranslationY();
+ return max + getStackTranslation();
}
/**
@@ -2392,29 +2670,147 @@ public class NotificationStackScrollLayout extends ViewGroup
this.mPhoneStatusBar = phoneStatusBar;
}
+ public void setGroupManager(NotificationGroupManager groupManager) {
+ this.mGroupManager = groupManager;
+ }
+
public void onGoToKeyguard() {
+ requestAnimateEverything();
+ }
+
+ private void requestAnimateEverything() {
if (mIsExpanded && mAnimationsEnabled) {
mEverythingNeedsAnimation = true;
+ mNeedsAnimation = true;
requestChildrenUpdate();
}
}
- private boolean isBelowLastNotification(float touchX, float touchY) {
- ExpandableView lastChildNotGone = (ExpandableView) getLastChildNotGone();
- if (lastChildNotGone == null) {
- return touchY > mIntrinsicPadding;
+ public boolean isBelowLastNotification(float touchX, float touchY) {
+ int childCount = getChildCount();
+ for (int i = childCount - 1; i >= 0; i--) {
+ ExpandableView child = (ExpandableView) getChildAt(i);
+ if (child.getVisibility() != View.GONE) {
+ float childTop = child.getY();
+ if (childTop > touchY) {
+ // we are above a notification entirely let's abort
+ return false;
+ }
+ boolean belowChild = touchY > childTop + child.getActualHeight();
+ if (child == mDismissView) {
+ if(!belowChild && !mDismissView.isOnEmptySpace(touchX - mDismissView.getX(),
+ touchY - childTop)) {
+ // We clicked on the dismiss button
+ return false;
+ }
+ } else if (child == mEmptyShadeView) {
+ // We arrived at the empty shade view, for which we accept all clicks
+ return true;
+ } else if (!belowChild){
+ // We are on a child
+ return false;
+ }
+ }
+ }
+ return touchY > mTopPadding + mStackTranslation;
+ }
+
+ private void updateExpandButtons() {
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ if (child instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ row.updateExpandButton();
+ }
}
- if (lastChildNotGone != mDismissView && lastChildNotGone != mEmptyShadeView) {
- return touchY > lastChildNotGone.getY() + lastChildNotGone.getActualHeight();
- } else if (lastChildNotGone == mEmptyShadeView) {
- return touchY > mEmptyShadeView.getY();
- } else {
- float dismissY = mDismissView.getY();
- boolean belowDismissView = touchY > dismissY + mDismissView.getActualHeight();
- return belowDismissView || (touchY > dismissY
- && mDismissView.isOnEmptySpace(touchX - mDismissView.getX(),
- touchY - dismissY));
+ }
+
+ @Override
+ public void onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded) {
+ boolean animated = mAnimationsEnabled && mIsExpanded;
+ if (animated) {
+ mExpandedGroupView = changedRow;
+ mNeedsAnimation = true;
}
+ changedRow.setChildrenExpanded(expanded, animated);
+ onHeightChanged(changedRow, false /* needsAnimation */);
+ }
+
+ @Override
+ public void onGroupsProhibitedChanged() {
+ updateExpandButtons();
+ }
+
+ @Override
+ public void onGroupCreatedFromChildren(NotificationGroupManager.NotificationGroup group) {
+ for (NotificationData.Entry entry : group.children) {
+ ExpandableNotificationRow row = entry.row;
+ if (indexOfChild(row) != -1) {
+ removeView(row);
+ group.summary.row.addChildNotification(row);
+ }
+ }
+ }
+
+ public void generateChildOrderChangedEvent() {
+ if (mIsExpanded && mAnimationsEnabled) {
+ mGenerateChildOrderChangedEvent = true;
+ mNeedsAnimation = true;
+ requestChildrenUpdate();
+ }
+ }
+
+ public void runAfterAnimationFinished(Runnable runnable) {
+ mAnimationFinishedRunnables.add(runnable);
+ }
+
+ public void setHeadsUpManager(HeadsUpManager headsUpManager) {
+ mHeadsUpManager = headsUpManager;
+ mAmbientState.setHeadsUpManager(headsUpManager);
+ mStackScrollAlgorithm.setHeadsUpManager(headsUpManager);
+ }
+
+ public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) {
+ if (mAnimationsEnabled) {
+ mHeadsUpChangeAnimations.add(new Pair<>(row, isHeadsUp));
+ mNeedsAnimation = true;
+ requestChildrenUpdate();
+ }
+ }
+
+ public void setShadeExpanded(boolean shadeExpanded) {
+ mAmbientState.setShadeExpanded(shadeExpanded);
+ mStateAnimator.setShadeExpanded(shadeExpanded);
+ }
+
+ /**
+ * Set the boundary for the bottom heads up position. The heads up will always be above this
+ * position.
+ *
+ * @param height the height of the screen
+ * @param bottomBarHeight the height of the bar on the bottom
+ */
+ public void setHeadsUpBoundaries(int height, int bottomBarHeight) {
+ mAmbientState.setMaxHeadsUpTranslation(height - bottomBarHeight);
+ mStateAnimator.setHeadsUpAppearHeightBottom(height);
+ requestChildrenUpdate();
+ }
+
+ public void setTrackingHeadsUp(boolean trackingHeadsUp) {
+ mTrackingHeadsUp = trackingHeadsUp;
+ }
+
+ public void setScrimController(ScrimController scrimController) {
+ mScrimController = scrimController;
+ }
+
+ public void forceNoOverlappingRendering(boolean force) {
+ mForceNoOverlappingRendering = force;
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return !mForceNoOverlappingRendering && super.hasOverlappingRendering();
}
/**
@@ -2553,6 +2949,38 @@ public class NotificationStackScrollLayout extends ViewGroup
.animateY()
.animateZ(),
+ // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED
+ new AnimationFilter()
+ .animateAlpha()
+ .animateHeight()
+ .animateTopInset()
+ .animateY()
+ .animateZ(),
+
+ // ANIMATION_TYPE_HEADS_UP_APPEAR
+ new AnimationFilter()
+ .animateAlpha()
+ .animateHeight()
+ .animateTopInset()
+ .animateY()
+ .animateZ(),
+
+ // ANIMATION_TYPE_HEADS_UP_DISAPPEAR
+ new AnimationFilter()
+ .animateAlpha()
+ .animateHeight()
+ .animateTopInset()
+ .animateY()
+ .animateZ(),
+
+ // ANIMATION_TYPE_HEADS_UP_OTHER
+ new AnimationFilter()
+ .animateAlpha()
+ .animateHeight()
+ .animateTopInset()
+ .animateY()
+ .animateZ(),
+
// ANIMATION_TYPE_EVERYTHING
new AnimationFilter()
.animateAlpha()
@@ -2607,6 +3035,18 @@ public class NotificationStackScrollLayout extends ViewGroup
// ANIMATION_TYPE_VIEW_RESIZE
StackStateAnimator.ANIMATION_DURATION_STANDARD,
+ // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED
+ StackStateAnimator.ANIMATION_DURATION_EXPAND_CLICKED,
+
+ // ANIMATION_TYPE_HEADS_UP_APPEAR
+ StackStateAnimator.ANIMATION_DURATION_HEADS_UP_APPEAR,
+
+ // ANIMATION_TYPE_HEADS_UP_DISAPPEAR
+ StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
+
+ // ANIMATION_TYPE_HEADS_UP_OTHER
+ StackStateAnimator.ANIMATION_DURATION_STANDARD,
+
// ANIMATION_TYPE_EVERYTHING
StackStateAnimator.ANIMATION_DURATION_STANDARD,
};
@@ -2624,7 +3064,11 @@ public class NotificationStackScrollLayout extends ViewGroup
static final int ANIMATION_TYPE_GO_TO_FULL_SHADE = 10;
static final int ANIMATION_TYPE_HIDE_SENSITIVE = 11;
static final int ANIMATION_TYPE_VIEW_RESIZE = 12;
- static final int ANIMATION_TYPE_EVERYTHING = 13;
+ static final int ANIMATION_TYPE_GROUP_EXPANSION_CHANGED = 13;
+ static final int ANIMATION_TYPE_HEADS_UP_APPEAR = 14;
+ static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR = 15;
+ static final int ANIMATION_TYPE_HEADS_UP_OTHER = 16;
+ static final int ANIMATION_TYPE_EVERYTHING = 17;
static final int DARK_ANIMATION_ORIGIN_INDEX_ABOVE = -1;
static final int DARK_ANIMATION_ORIGIN_INDEX_BELOW = -2;
@@ -2636,6 +3080,7 @@ public class NotificationStackScrollLayout extends ViewGroup
final long length;
View viewAfterChangingView;
int darkAnimationOriginIndex;
+ boolean headsUpFromBottom;
AnimationEvent(View view, int type) {
this(view, type, LENGTHS[type]);
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 ddc4251..5c604b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -25,8 +25,10 @@ import android.view.ViewGroup;
import com.android.systemui.R;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.ExpandableView;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
import java.util.ArrayList;
+import java.util.List;
/**
* The Algorithm of the {@link com.android.systemui.statusbar.stack
@@ -53,11 +55,6 @@ public class StackScrollAlgorithm {
private StackIndentationFunctor mTopStackIndentationFunctor;
private StackIndentationFunctor mBottomStackIndentationFunctor;
- private int mLayoutHeight;
-
- /** mLayoutHeight - mTopPadding */
- private int mInnerHeight;
- private int mTopPadding;
private StackScrollAlgorithmState mTempAlgorithmState = new StackScrollAlgorithmState();
private boolean mIsExpansionChanging;
private int mFirstChildMaxHeight;
@@ -67,12 +64,14 @@ public class StackScrollAlgorithm {
private int mTopStackTotalSize;
private int mPaddingBetweenElementsDimmed;
private int mPaddingBetweenElementsNormal;
+ private int mNotificationsTopPadding;
private int mBottomStackSlowDownLength;
private int mTopStackSlowDownLength;
private int mCollapseSecondCardPadding;
private boolean mIsSmallScreen;
private int mMaxNotificationHeight;
private boolean mScaleDimmed;
+ private HeadsUpManager mHeadsUpManager;
public StackScrollAlgorithm(Context context) {
initConstants(context);
@@ -106,6 +105,8 @@ public class StackScrollAlgorithm {
.getDimensionPixelSize(R.dimen.notification_padding_dimmed);
mPaddingBetweenElementsNormal = context.getResources()
.getDimensionPixelSize(R.dimen.notification_padding);
+ mNotificationsTopPadding = context.getResources()
+ .getDimensionPixelSize(R.dimen.notifications_top_padding);
mCollapsedSize = context.getResources()
.getDimensionPixelSize(R.dimen.notification_min_height);
mMaxNotificationHeight = context.getResources()
@@ -159,18 +160,31 @@ public class StackScrollAlgorithm {
updateVisibleChildren(resultState, algorithmState);
// Phase 1:
- findNumberOfItemsInTopStackAndUpdateState(resultState, algorithmState);
+ findNumberOfItemsInTopStackAndUpdateState(resultState, algorithmState, ambientState);
// Phase 2:
- updatePositionsForState(resultState, algorithmState);
+ updatePositionsForState(resultState, algorithmState, ambientState);
// Phase 3:
updateZValuesForState(resultState, algorithmState);
handleDraggedViews(ambientState, resultState, algorithmState);
updateDimmedActivatedHideSensitive(ambientState, resultState, algorithmState);
- updateClipping(resultState, algorithmState);
+ updateClipping(resultState, algorithmState, ambientState);
updateSpeedBumpState(resultState, algorithmState, ambientState.getSpeedBumpIndex());
+ getNotificationChildrenStates(resultState, algorithmState);
+ }
+
+ private void getNotificationChildrenStates(StackScrollState resultState,
+ StackScrollAlgorithmState algorithmState) {
+ int childCount = algorithmState.visibleChildren.size();
+ for (int i = 0; i < childCount; i++) {
+ ExpandableView v = algorithmState.visibleChildren.get(i);
+ if (v instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+ row.getChildrenStates(resultState);
+ }
+ }
}
private void updateSpeedBumpState(StackScrollState resultState,
@@ -178,7 +192,7 @@ public class StackScrollAlgorithm {
int childCount = algorithmState.visibleChildren.size();
for (int i = 0; i < childCount; i++) {
View child = algorithmState.visibleChildren.get(i);
- StackScrollState.ViewState childViewState = resultState.getViewStateForView(child);
+ StackViewState childViewState = resultState.getViewStateForView(child);
// The speed bump can also be gone, so equality needs to be taken when comparing
// indices.
@@ -187,14 +201,14 @@ public class StackScrollAlgorithm {
}
private void updateClipping(StackScrollState resultState,
- StackScrollAlgorithmState algorithmState) {
+ StackScrollAlgorithmState algorithmState, AmbientState ambientState) {
float previousNotificationEnd = 0;
float previousNotificationStart = 0;
boolean previousNotificationIsSwiped = false;
int childCount = algorithmState.visibleChildren.size();
for (int i = 0; i < childCount; i++) {
ExpandableView child = algorithmState.visibleChildren.get(i);
- StackScrollState.ViewState state = resultState.getViewStateForView(child);
+ StackViewState state = resultState.getViewStateForView(child);
float newYTranslation = state.yTranslation + state.height * (1f - state.scale) / 2f;
float newHeight = state.height * state.scale;
// apply clipping and shadow
@@ -228,7 +242,7 @@ public class StackScrollAlgorithm {
// otherwise we would clip to a transparent view.
previousNotificationStart = newYTranslation + state.clipTopAmount * state.scale;
previousNotificationEnd = newNotificationEnd;
- previousNotificationIsSwiped = child.getTranslationX() != 0;
+ previousNotificationIsSwiped = ambientState.getDraggedViews().contains(child);
}
}
}
@@ -242,8 +256,8 @@ public class StackScrollAlgorithm {
* @param backgroundHeight the desired background height. The shadows of the view will be
* based on this height and the content will be clipped from the top
*/
- private void updateChildClippingAndBackground(StackScrollState.ViewState state,
- float realHeight, float clipHeight, float backgroundHeight) {
+ private void updateChildClippingAndBackground(StackViewState state, float realHeight,
+ float clipHeight, float backgroundHeight) {
if (realHeight > clipHeight) {
// Rather overlap than create a hole.
state.topOverLap = (int) Math.floor((realHeight - clipHeight) / state.scale);
@@ -270,7 +284,7 @@ public class StackScrollAlgorithm {
int childCount = algorithmState.visibleChildren.size();
for (int i = 0; i < childCount; i++) {
View child = algorithmState.visibleChildren.get(i);
- StackScrollState.ViewState childViewState = resultState.getViewStateForView(child);
+ StackViewState childViewState = resultState.getViewStateForView(child);
childViewState.dimmed = dimmed;
childViewState.dark = dark;
childViewState.hideSensitive = hideSensitive;
@@ -297,14 +311,16 @@ public class StackScrollAlgorithm {
if (!draggedViews.contains(nextChild)) {
// only if the view is not dragged itself we modify its state to be fully
// visible
- StackScrollState.ViewState viewState = resultState.getViewStateForView(
+ StackViewState viewState = resultState.getViewStateForView(
nextChild);
// The child below the dragged one must be fully visible
- viewState.alpha = 1;
+ if (ambientState.isShadeExpanded()) {
+ viewState.alpha = 1;
+ }
}
// Lets set the alpha to the one it currently has, as its currently being dragged
- StackScrollState.ViewState viewState = resultState.getViewStateForView(draggedView);
+ StackViewState viewState = resultState.getViewStateForView(draggedView);
// The dragged child should keep the set alpha
viewState.alpha = draggedView.getAlpha();
}
@@ -320,27 +336,54 @@ public class StackScrollAlgorithm {
int childCount = hostView.getChildCount();
state.visibleChildren.clear();
state.visibleChildren.ensureCapacity(childCount);
+ int notGoneIndex = 0;
for (int i = 0; i < childCount; i++) {
ExpandableView v = (ExpandableView) hostView.getChildAt(i);
if (v.getVisibility() != View.GONE) {
- StackScrollState.ViewState viewState = resultState.getViewStateForView(v);
- viewState.notGoneIndex = state.visibleChildren.size();
- state.visibleChildren.add(v);
+ notGoneIndex = updateNotGoneIndex(resultState, state, notGoneIndex, v);
+ if (v instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+
+ // handle the notgoneIndex for the children as well
+ List<ExpandableNotificationRow> children =
+ row.getNotificationChildren();
+ if (row.areChildrenExpanded() && children != null) {
+ for (ExpandableNotificationRow childRow : children) {
+ if (childRow.getVisibility() != View.GONE) {
+ StackViewState childState
+ = resultState.getViewStateForView(childRow);
+ childState.notGoneIndex = notGoneIndex;
+ notGoneIndex++;
+ }
+ }
+ }
+ }
}
}
}
+ private int updateNotGoneIndex(StackScrollState resultState,
+ StackScrollAlgorithmState state, int notGoneIndex,
+ ExpandableView v) {
+ StackViewState viewState = resultState.getViewStateForView(v);
+ viewState.notGoneIndex = notGoneIndex;
+ state.visibleChildren.add(v);
+ notGoneIndex++;
+ return notGoneIndex;
+ }
+
/**
* Determine the positions for the views. This is the main part of the algorithm.
*
* @param resultState The result state to update if a change to the properties of a child occurs
* @param algorithmState The state in which the current pass of the algorithm is currently in
+ * @param ambientState The current ambient state
*/
private void updatePositionsForState(StackScrollState resultState,
- StackScrollAlgorithmState algorithmState) {
+ StackScrollAlgorithmState algorithmState, AmbientState ambientState) {
// The starting position of the bottom stack peek
- float bottomPeekStart = mInnerHeight - mBottomStackPeekSize;
+ float bottomPeekStart = ambientState.getInnerHeight() - mBottomStackPeekSize;
// The position where the bottom stack starts.
float bottomStackStart = bottomPeekStart - mBottomStackSlowDownLength;
@@ -351,13 +394,17 @@ public class StackScrollAlgorithm {
// How far in is the element currently transitioning into the bottom stack.
float yPositionInScrollView = 0.0f;
+ // If we have a heads-up higher than the collapsed height we need to add the difference to
+ // the padding of all other elements, i.e push in the top stack slightly.
+ ExpandableNotificationRow topHeadsUpEntry = ambientState.getTopHeadsUpEntry();
+
int childCount = algorithmState.visibleChildren.size();
int numberOfElementsCompletelyIn = (int) algorithmState.itemsInTopStack;
for (int i = 0; i < childCount; i++) {
ExpandableView child = algorithmState.visibleChildren.get(i);
- StackScrollState.ViewState childViewState = resultState.getViewStateForView(child);
- childViewState.location = StackScrollState.ViewState.LOCATION_UNKNOWN;
- int childHeight = getMaxAllowedChildHeight(child);
+ StackViewState childViewState = resultState.getViewStateForView(child);
+ childViewState.location = StackViewState.LOCATION_UNKNOWN;
+ int childHeight = getMaxAllowedChildHeight(child, ambientState);
float yPositionInScrollViewAfterElement = yPositionInScrollView
+ childHeight
+ mPaddingBetweenElements;
@@ -394,7 +441,8 @@ public class StackScrollAlgorithm {
bottomPeekStart, childViewState.yTranslation, childViewState,
childHeight);
}
- clampPositionToBottomStackStart(childViewState, childViewState.height);
+ clampPositionToBottomStackStart(childViewState, childViewState.height,
+ ambientState);
} else if (nextYPosition >= bottomStackStart) {
// Case 2:
// We are in the bottom stack.
@@ -402,7 +450,7 @@ public class StackScrollAlgorithm {
// According to the regular scroll view we are fully translated out of the
// bottom of the screen so we are fully in the bottom stack
updateStateForChildFullyInBottomStack(algorithmState,
- bottomStackStart, childViewState, childHeight);
+ bottomStackStart, childViewState, childHeight, ambientState);
} else {
// According to the regular scroll view we are currently translating out of /
// into the bottom of the screen
@@ -413,8 +461,8 @@ public class StackScrollAlgorithm {
} else {
// Case 3:
// We are in the regular scroll area.
- childViewState.location = StackScrollState.ViewState.LOCATION_MAIN_AREA;
- clampYTranslation(childViewState, childHeight);
+ childViewState.location = StackViewState.LOCATION_MAIN_AREA;
+ clampYTranslation(childViewState, childHeight, ambientState);
}
// The first card is always rendered.
@@ -427,15 +475,64 @@ public class StackScrollAlgorithm {
bottomPeekStart - mCollapseSecondCardPadding
- childViewState.yTranslation, mCollapsedSize);
}
- childViewState.location = StackScrollState.ViewState.LOCATION_FIRST_CARD;
+ childViewState.location = StackViewState.LOCATION_FIRST_CARD;
}
- if (childViewState.location == StackScrollState.ViewState.LOCATION_UNKNOWN) {
+ if (childViewState.location == StackViewState.LOCATION_UNKNOWN) {
Log.wtf(LOG_TAG, "Failed to assign location for child " + i);
}
currentYPosition = childViewState.yTranslation + childHeight + mPaddingBetweenElements;
yPositionInScrollView = yPositionInScrollViewAfterElement;
- childViewState.yTranslation += mTopPadding;
+ if (ambientState.isShadeExpanded() && topHeadsUpEntry != null
+ && child != topHeadsUpEntry) {
+ childViewState.yTranslation += topHeadsUpEntry.getHeadsUpHeight() - mCollapsedSize;
+ }
+ childViewState.yTranslation += ambientState.getTopPadding()
+ + ambientState.getStackTranslation();
+ }
+ updateHeadsUpStates(resultState, algorithmState, ambientState);
+ }
+
+ private void updateHeadsUpStates(StackScrollState resultState,
+ StackScrollAlgorithmState algorithmState, AmbientState ambientState) {
+ int childCount = algorithmState.visibleChildren.size();
+ ExpandableNotificationRow topHeadsUpEntry = null;
+ for (int i = 0; i < childCount; i++) {
+ View child = algorithmState.visibleChildren.get(i);
+ if (!(child instanceof ExpandableNotificationRow)) {
+ break;
+ }
+ ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ if (!row.isHeadsUp()) {
+ break;
+ } else if (topHeadsUpEntry == null) {
+ topHeadsUpEntry = row;
+ }
+ StackViewState childState = resultState.getViewStateForView(row);
+ boolean isTopEntry = topHeadsUpEntry == row;
+ if (mIsExpanded) {
+ if (isTopEntry) {
+ childState.height += row.getHeadsUpHeight() - mCollapsedSize;
+ }
+ childState.height = Math.max(childState.height, row.getHeadsUpHeight());
+ // Ensure that the heads up is always visible even when scrolled off from the bottom
+ float bottomPosition = ambientState.getMaxHeadsUpTranslation() - childState.height;
+ childState.yTranslation = Math.min(childState.yTranslation,
+ bottomPosition);
+ }
+ if (row.isPinned()) {
+ childState.yTranslation = Math.max(childState.yTranslation,
+ mNotificationsTopPadding);
+ childState.height = row.getHeadsUpHeight();
+ if (!isTopEntry) {
+ // Ensure that a headsUp doesn't vertically extend further than the heads-up at
+ // the top most z-position
+ StackViewState topState = resultState.getViewStateForView(topHeadsUpEntry);
+ childState.height = row.getHeadsUpHeight();
+ childState.yTranslation = topState.yTranslation + topState.height
+ - childState.height;
+ }
+ }
}
}
@@ -445,8 +542,9 @@ public class StackScrollAlgorithm {
* @param childViewState the view state of the child
* @param childHeight the height of this child
*/
- private void clampYTranslation(StackScrollState.ViewState childViewState, int childHeight) {
- clampPositionToBottomStackStart(childViewState, childHeight);
+ private void clampYTranslation(StackViewState childViewState, int childHeight,
+ AmbientState ambientState) {
+ clampPositionToBottomStackStart(childViewState, childHeight, ambientState);
clampPositionToTopStackEnd(childViewState, childHeight);
}
@@ -457,39 +555,45 @@ public class StackScrollAlgorithm {
* @param childViewState the view state of the child
* @param childHeight the height of this child
*/
- private void clampPositionToBottomStackStart(StackScrollState.ViewState childViewState,
- int childHeight) {
+ private void clampPositionToBottomStackStart(StackViewState childViewState,
+ int childHeight, AmbientState ambientState) {
childViewState.yTranslation = Math.min(childViewState.yTranslation,
- mInnerHeight - mBottomStackPeekSize - mCollapseSecondCardPadding - childHeight);
+ ambientState.getInnerHeight() - mBottomStackPeekSize - mCollapseSecondCardPadding
+ - childHeight);
}
/**
* Clamp the yTranslation of the child up such that its end is at lest on the end of the top
- * stack.get
+ * stack.
*
* @param childViewState the view state of the child
* @param childHeight the height of this child
*/
- private void clampPositionToTopStackEnd(StackScrollState.ViewState childViewState,
+ private void clampPositionToTopStackEnd(StackViewState childViewState,
int childHeight) {
childViewState.yTranslation = Math.max(childViewState.yTranslation,
mCollapsedSize - childHeight);
}
- private int getMaxAllowedChildHeight(View child) {
+ private int getMaxAllowedChildHeight(View child, AmbientState ambientState) {
if (child instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ if (ambientState == null && row.isHeadsUp()
+ || ambientState != null && ambientState.getTopHeadsUpEntry() == child) {
+ int extraSize = row.getIntrinsicHeight() - row.getHeadsUpHeight();
+ return mCollapsedSize + extraSize;
+ }
return row.getIntrinsicHeight();
} else if (child instanceof ExpandableView) {
ExpandableView expandableView = (ExpandableView) child;
- return expandableView.getActualHeight();
+ return expandableView.getIntrinsicHeight();
}
return child == null? mCollapsedSize : child.getHeight();
}
private void updateStateForChildTransitioningInBottom(StackScrollAlgorithmState algorithmState,
float transitioningPositionStart, float bottomPeakStart, float currentYPosition,
- StackScrollState.ViewState childViewState, int childHeight) {
+ StackViewState childViewState, int childHeight) {
// This is the transitioning element on top of bottom stack, calculate how far we are in.
algorithmState.partialInBottom = 1.0f - (
@@ -510,13 +614,12 @@ public class StackScrollAlgorithm {
// We want at least to be at the end of the top stack when collapsing
clampPositionToTopStackEnd(childViewState, newHeight);
- childViewState.location = StackScrollState.ViewState.LOCATION_MAIN_AREA;
+ childViewState.location = StackViewState.LOCATION_MAIN_AREA;
}
private void updateStateForChildFullyInBottomStack(StackScrollAlgorithmState algorithmState,
- float transitioningPositionStart, StackScrollState.ViewState childViewState,
- int childHeight) {
-
+ float transitioningPositionStart, StackViewState childViewState,
+ int childHeight, AmbientState ambientState) {
float currentYPosition;
algorithmState.itemsInBottomStack += 1.0f;
if (algorithmState.itemsInBottomStack < MAX_ITEMS_IN_BOTTOM_STACK) {
@@ -524,7 +627,7 @@ public class StackScrollAlgorithm {
currentYPosition = transitioningPositionStart
+ mBottomStackIndentationFunctor.getValue(algorithmState.itemsInBottomStack)
- mPaddingBetweenElements;
- childViewState.location = StackScrollState.ViewState.LOCATION_BOTTOM_STACK_PEEKING;
+ childViewState.location = StackViewState.LOCATION_BOTTOM_STACK_PEEKING;
} else {
// we are fully inside the stack
if (algorithmState.itemsInBottomStack > MAX_ITEMS_IN_BOTTOM_STACK + 2) {
@@ -533,8 +636,8 @@ public class StackScrollAlgorithm {
> MAX_ITEMS_IN_BOTTOM_STACK + 1) {
childViewState.alpha = 1.0f - algorithmState.partialInBottom;
}
- childViewState.location = StackScrollState.ViewState.LOCATION_BOTTOM_STACK_HIDDEN;
- currentYPosition = mInnerHeight;
+ childViewState.location = StackViewState.LOCATION_BOTTOM_STACK_HIDDEN;
+ currentYPosition = ambientState.getInnerHeight();
}
childViewState.yTranslation = currentYPosition - childHeight;
clampPositionToTopStackEnd(childViewState, childHeight);
@@ -542,7 +645,7 @@ public class StackScrollAlgorithm {
private void updateStateForTopStackChild(StackScrollAlgorithmState algorithmState,
int numberOfElementsCompletelyIn, int i, int childHeight,
- StackScrollState.ViewState childViewState, float scrollOffset) {
+ StackViewState childViewState, float scrollOffset) {
// First we calculate the index relative to the current stack window of size at most
@@ -574,7 +677,7 @@ public class StackScrollAlgorithm {
- mTopStackIndentationFunctor.getValue(numItemsBefore);
childViewState.yTranslation = currentChildEndY - childHeight;
}
- childViewState.location = StackScrollState.ViewState.LOCATION_TOP_STACK_PEEKING;
+ childViewState.location = StackViewState.LOCATION_TOP_STACK_PEEKING;
} else {
if (paddedIndex == -1) {
childViewState.alpha = 1.0f - algorithmState.partialInTop;
@@ -583,7 +686,7 @@ public class StackScrollAlgorithm {
childViewState.alpha = 0.0f;
}
childViewState.yTranslation = mCollapsedSize - childHeight;
- childViewState.location = StackScrollState.ViewState.LOCATION_TOP_STACK_HIDDEN;
+ childViewState.location = StackViewState.LOCATION_TOP_STACK_HIDDEN;
}
@@ -596,7 +699,7 @@ public class StackScrollAlgorithm {
* @param algorithmState The state in which the current pass of the algorithm is currently in
*/
private void findNumberOfItemsInTopStackAndUpdateState(StackScrollState resultState,
- StackScrollAlgorithmState algorithmState) {
+ StackScrollAlgorithmState algorithmState, AmbientState ambientState) {
// The y Position if the element would be in a regular scrollView
float yPositionInScrollView = 0.0f;
@@ -605,8 +708,8 @@ public class StackScrollAlgorithm {
// find the number of elements in the top stack.
for (int i = 0; i < childCount; i++) {
ExpandableView child = algorithmState.visibleChildren.get(i);
- StackScrollState.ViewState childViewState = resultState.getViewStateForView(child);
- int childHeight = getMaxAllowedChildHeight(child);
+ StackViewState childViewState = resultState.getViewStateForView(child);
+ int childHeight = getMaxAllowedChildHeight(child, ambientState);
float yPositionInScrollViewAfterElement = yPositionInScrollView
+ childHeight
+ mPaddingBetweenElements;
@@ -614,7 +717,7 @@ public class StackScrollAlgorithm {
if (i == 0 && algorithmState.scrollY <= mCollapsedSize) {
// The starting position of the bottom stack peek
- int bottomPeekStart = mInnerHeight - mBottomStackPeekSize -
+ int bottomPeekStart = ambientState.getInnerHeight() - mBottomStackPeekSize -
mCollapseSecondCardPadding;
// Collapse and expand the first child while the shade is being expanded
float maxHeight = mIsExpansionChanging && child == mFirstChildWhileExpanding
@@ -676,7 +779,7 @@ public class StackScrollAlgorithm {
int childCount = algorithmState.visibleChildren.size();
for (int i = 0; i < childCount; i++) {
View child = algorithmState.visibleChildren.get(i);
- StackScrollState.ViewState childViewState = resultState.getViewStateForView(child);
+ StackViewState childViewState = resultState.getViewStateForView(child);
if (i < algorithmState.itemsInTopStack) {
float stackIndex = algorithmState.itemsInTopStack - i;
@@ -711,21 +814,6 @@ public class StackScrollAlgorithm {
}
}
- public void setLayoutHeight(int layoutHeight) {
- this.mLayoutHeight = layoutHeight;
- updateInnerHeight();
- }
-
- public void setTopPadding(int topPadding) {
- mTopPadding = topPadding;
- updateInnerHeight();
- }
-
- private void updateInnerHeight() {
- mInnerHeight = mLayoutHeight - mTopPadding;
- }
-
-
/**
* Update whether the device is very small, i.e. Notifications can be in both the top and the
* bottom stack at the same time
@@ -755,6 +843,13 @@ public class StackScrollAlgorithm {
// current height or the end value of the animation.
mFirstChildMaxHeight = StackStateAnimator.getFinalActualHeight(
mFirstChildWhileExpanding);
+ if (mFirstChildWhileExpanding instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row =
+ (ExpandableNotificationRow) mFirstChildWhileExpanding;
+ if (row.isHeadsUp()) {
+ mFirstChildMaxHeight += mCollapsedSize - row.getHeadsUpHeight();
+ }
+ }
} else {
updateFirstChildMaxSizeToMaxHeight();
}
@@ -776,7 +871,7 @@ public class StackScrollAlgorithm {
int oldBottom) {
if (mFirstChildWhileExpanding != null) {
mFirstChildMaxHeight = getMaxAllowedChildHeight(
- mFirstChildWhileExpanding);
+ mFirstChildWhileExpanding, null);
} else {
mFirstChildMaxHeight = 0;
}
@@ -784,7 +879,7 @@ public class StackScrollAlgorithm {
}
});
} else {
- mFirstChildMaxHeight = getMaxAllowedChildHeight(mFirstChildWhileExpanding);
+ mFirstChildMaxHeight = getMaxAllowedChildHeight(mFirstChildWhileExpanding, null);
}
}
@@ -837,6 +932,10 @@ public class StackScrollAlgorithm {
}
}
+ public void setHeadsUpManager(HeadsUpManager headsUpManager) {
+ mHeadsUpManager = headsUpManager;
+ }
+
class StackScrollAlgorithmState {
/**
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 0b1ce8f..3768ca4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.stack;
-import android.graphics.Rect;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@@ -24,10 +23,12 @@ import android.view.ViewGroup;
import com.android.systemui.R;
import com.android.systemui.statusbar.DismissView;
import com.android.systemui.statusbar.EmptyShadeView;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.ExpandableView;
import com.android.systemui.statusbar.SpeedBumpView;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
/**
@@ -39,13 +40,12 @@ public class StackScrollState {
private static final String CHILD_NOT_FOUND_TAG = "StackScrollStateNoSuchChild";
private final ViewGroup mHostView;
- private Map<ExpandableView, ViewState> mStateMap;
- private final Rect mClipRect = new Rect();
+ private Map<ExpandableView, StackViewState> mStateMap;
private final int mClearAllTopPadding;
public StackScrollState(ViewGroup hostView) {
mHostView = hostView;
- mStateMap = new HashMap<ExpandableView, ViewState>();
+ mStateMap = new HashMap<ExpandableView, StackViewState>();
mClearAllTopPadding = hostView.getContext().getResources().getDimensionPixelSize(
R.dimen.clear_all_padding_top);
}
@@ -58,20 +58,36 @@ public class StackScrollState {
int numChildren = mHostView.getChildCount();
for (int i = 0; i < numChildren; i++) {
ExpandableView child = (ExpandableView) mHostView.getChildAt(i);
- ViewState viewState = mStateMap.get(child);
- if (viewState == null) {
- viewState = new ViewState();
- mStateMap.put(child, viewState);
+ resetViewState(child);
+
+ // handling reset for child notifications
+ if (child instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ List<ExpandableNotificationRow> children =
+ row.getNotificationChildren();
+ if (row.areChildrenExpanded() && children != null) {
+ for (ExpandableNotificationRow childRow : children) {
+ resetViewState(childRow);
+ }
+ }
}
- // initialize with the default values of the view
- viewState.height = child.getIntrinsicHeight();
- viewState.gone = child.getVisibility() == View.GONE;
- viewState.alpha = 1;
- viewState.notGoneIndex = -1;
}
}
- public ViewState getViewStateForView(View requestedView) {
+ private void resetViewState(ExpandableView view) {
+ StackViewState viewState = mStateMap.get(view);
+ if (viewState == null) {
+ viewState = new StackViewState();
+ mStateMap.put(view, viewState);
+ }
+ // initialize with the default values of the view
+ viewState.height = view.getIntrinsicHeight();
+ viewState.gone = view.getVisibility() == View.GONE;
+ viewState.alpha = 1;
+ viewState.notGoneIndex = -1;
+ }
+
+ public StackViewState getViewStateForView(View requestedView) {
return mStateMap.get(requestedView);
}
@@ -87,126 +103,142 @@ public class StackScrollState {
int numChildren = mHostView.getChildCount();
for (int i = 0; i < numChildren; i++) {
ExpandableView child = (ExpandableView) mHostView.getChildAt(i);
- ViewState state = mStateMap.get(child);
- if (state == null) {
- Log.wtf(CHILD_NOT_FOUND_TAG, "No child state was found when applying this state " +
- "to the hostView");
+ StackViewState state = mStateMap.get(child);
+ if (!applyState(child, state)) {
continue;
}
- if (!state.gone) {
- float alpha = child.getAlpha();
- float yTranslation = child.getTranslationY();
- float xTranslation = child.getTranslationX();
- float zTranslation = child.getTranslationZ();
- float scale = child.getScaleX();
- int height = child.getActualHeight();
- float newAlpha = state.alpha;
- float newYTranslation = state.yTranslation;
- float newZTranslation = state.zTranslation;
- float newScale = state.scale;
- int newHeight = state.height;
- boolean becomesInvisible = newAlpha == 0.0f;
- if (alpha != newAlpha && xTranslation == 0) {
- // apply layer type
- boolean becomesFullyVisible = newAlpha == 1.0f;
- boolean newLayerTypeIsHardware = !becomesInvisible && !becomesFullyVisible;
- int layerType = child.getLayerType();
- int newLayerType = newLayerTypeIsHardware
- ? View.LAYER_TYPE_HARDWARE
- : View.LAYER_TYPE_NONE;
- if (layerType != newLayerType) {
- child.setLayerType(newLayerType, null);
- }
+ if(child instanceof SpeedBumpView) {
+ performSpeedBumpAnimation(i, (SpeedBumpView) child, state, 0);
+ } else if (child instanceof DismissView) {
+ DismissView dismissView = (DismissView) child;
+ boolean visible = state.topOverLap < mClearAllTopPadding;
+ dismissView.performVisibilityAnimation(visible && !dismissView.willBeGone());
+ } else if (child instanceof EmptyShadeView) {
+ EmptyShadeView emptyShadeView = (EmptyShadeView) child;
+ boolean visible = state.topOverLap <= 0;
+ emptyShadeView.performVisibilityAnimation(
+ visible && !emptyShadeView.willBeGone());
+ }
+ }
+ }
- // apply alpha
- child.setAlpha(newAlpha);
- }
+ /**
+ * Applies a {@link StackViewState} to an {@link ExpandableView}.
+ *
+ * @return whether the state was applied correctly
+ */
+ public boolean applyState(ExpandableView view, StackViewState state) {
+ if (state == null) {
+ Log.wtf(CHILD_NOT_FOUND_TAG, "No child state was found when applying this state " +
+ "to the hostView");
+ return false;
+ }
+ if (state.gone) {
+ return false;
+ }
+ applyViewState(view, state);
- // apply visibility
- int oldVisibility = child.getVisibility();
- int newVisibility = becomesInvisible ? View.INVISIBLE : View.VISIBLE;
- if (newVisibility != oldVisibility) {
- child.setVisibility(newVisibility);
- }
+ int height = view.getActualHeight();
+ int newHeight = state.height;
- // apply yTranslation
- if (yTranslation != newYTranslation) {
- child.setTranslationY(newYTranslation);
- }
+ // apply height
+ if (height != newHeight) {
+ view.setActualHeight(newHeight, false /* notifyListeners */);
+ }
- // apply zTranslation
- if (zTranslation != newZTranslation) {
- child.setTranslationZ(newZTranslation);
- }
+ // apply dimming
+ view.setDimmed(state.dimmed, false /* animate */);
- // apply scale
- if (scale != newScale) {
- child.setScaleX(newScale);
- child.setScaleY(newScale);
- }
+ // apply hiding sensitive
+ view.setHideSensitive(
+ state.hideSensitive, false /* animated */, 0 /* delay */, 0 /* duration */);
- // apply height
- if (height != newHeight) {
- child.setActualHeight(newHeight, false /* notifyListeners */);
- }
+ // apply speed bump state
+ view.setBelowSpeedBump(state.belowSpeedBump);
- // apply dimming
- child.setDimmed(state.dimmed, false /* animate */);
+ // apply dark
+ view.setDark(state.dark, false /* animate */, 0 /* delay */);
- // apply dark
- child.setDark(state.dark, false /* animate */, 0 /* delay */);
+ // apply clipping
+ float oldClipTopAmount = view.getClipTopAmount();
+ if (oldClipTopAmount != state.clipTopAmount) {
+ view.setClipTopAmount(state.clipTopAmount);
+ }
+ float oldClipTopOptimization = view.getClipTopOptimization();
+ if (oldClipTopOptimization != state.topOverLap) {
+ view.setClipTopOptimization(state.topOverLap);
+ }
+ if (view instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+ row.applyChildrenState(this);
+ }
+ return true;
+ }
- // apply hiding sensitive
- child.setHideSensitive(
- state.hideSensitive, false /* animated */, 0 /* delay */, 0 /* duration */);
+ /**
+ * Applies a {@link ViewState} to a normal view.
+ */
+ public void applyViewState(View view, ViewState state) {
+ float alpha = view.getAlpha();
+ float yTranslation = view.getTranslationY();
+ float xTranslation = view.getTranslationX();
+ float zTranslation = view.getTranslationZ();
+ float scale = view.getScaleX();
+ float newAlpha = state.alpha;
+ float newYTranslation = state.yTranslation;
+ float newZTranslation = state.zTranslation;
+ float newScale = state.scale;
+ boolean becomesInvisible = newAlpha == 0.0f;
+ if (alpha != newAlpha && xTranslation == 0) {
+ // apply layer type
+ boolean becomesFullyVisible = newAlpha == 1.0f;
+ boolean newLayerTypeIsHardware = !becomesInvisible && !becomesFullyVisible
+ && view.hasOverlappingRendering();
+ int layerType = view.getLayerType();
+ int newLayerType = newLayerTypeIsHardware
+ ? View.LAYER_TYPE_HARDWARE
+ : View.LAYER_TYPE_NONE;
+ if (layerType != newLayerType) {
+ view.setLayerType(newLayerType, null);
+ }
- // apply speed bump state
- child.setBelowSpeedBump(state.belowSpeedBump);
+ // apply alpha
+ view.setAlpha(newAlpha);
+ }
- // apply clipping
- float oldClipTopAmount = child.getClipTopAmount();
- if (oldClipTopAmount != state.clipTopAmount) {
- child.setClipTopAmount(state.clipTopAmount);
- }
- updateChildClip(child, newHeight, state.topOverLap);
-
- if(child instanceof SpeedBumpView) {
- performSpeedBumpAnimation(i, (SpeedBumpView) child, state, 0);
- } else if (child instanceof DismissView) {
- DismissView dismissView = (DismissView) child;
- boolean visible = state.topOverLap < mClearAllTopPadding;
- dismissView.performVisibilityAnimation(visible && !dismissView.willBeGone());
- } else if (child instanceof EmptyShadeView) {
- EmptyShadeView emptyShadeView = (EmptyShadeView) child;
- boolean visible = state.topOverLap <= 0;
- emptyShadeView.performVisibilityAnimation(
- visible && !emptyShadeView.willBeGone());
- }
+ // apply visibility
+ int oldVisibility = view.getVisibility();
+ int newVisibility = becomesInvisible ? View.INVISIBLE : View.VISIBLE;
+ if (newVisibility != oldVisibility) {
+ if (!(view instanceof ExpandableView) || !((ExpandableView) view).willBeGone()) {
+ // We don't want views to change visibility when they are animating to GONE
+ view.setVisibility(newVisibility);
}
}
- }
- /**
- * Updates the clipping of a view
- *
- * @param child the view to update
- * @param height the currently applied height of the view
- * @param clipInset how much should this view be clipped from the top
- */
- private void updateChildClip(View child, int height, int clipInset) {
- mClipRect.set(0,
- clipInset,
- child.getWidth(),
- height);
- child.setClipBounds(mClipRect);
+ // apply yTranslation
+ if (yTranslation != newYTranslation) {
+ view.setTranslationY(newYTranslation);
+ }
+
+ // apply zTranslation
+ if (zTranslation != newZTranslation) {
+ view.setTranslationZ(newZTranslation);
+ }
+
+ // apply scale
+ if (scale != newScale) {
+ view.setScaleX(newScale);
+ view.setScaleY(newScale);
+ }
}
- public void performSpeedBumpAnimation(int i, SpeedBumpView speedBump, ViewState state,
+ public void performSpeedBumpAnimation(int i, SpeedBumpView speedBump, StackViewState state,
long delay) {
View nextChild = getNextChildNotGone(i);
if (nextChild != null) {
float lineEnd = state.yTranslation + state.height / 2;
- ViewState nextState = getViewStateForView(nextChild);
+ StackViewState nextState = getViewStateForView(nextChild);
boolean startIsAboveNext = nextState.yTranslation > lineEnd;
speedBump.animateDivider(startIsAboveNext, delay, null /* onFinishedRunnable */);
}
@@ -223,53 +255,4 @@ public class StackScrollState {
return null;
}
- public static class ViewState {
-
- // These are flags such that we can create masks for filtering.
-
- public static final int LOCATION_UNKNOWN = 0x00;
- public static final int LOCATION_FIRST_CARD = 0x01;
- public static final int LOCATION_TOP_STACK_HIDDEN = 0x02;
- public static final int LOCATION_TOP_STACK_PEEKING = 0x04;
- public static final int LOCATION_MAIN_AREA = 0x08;
- public static final int LOCATION_BOTTOM_STACK_PEEKING = 0x10;
- public static final int LOCATION_BOTTOM_STACK_HIDDEN = 0x20;
- /** The view isn't layouted at all. */
- public static final int LOCATION_GONE = 0x40;
-
- float alpha;
- float yTranslation;
- float zTranslation;
- int height;
- boolean gone;
- float scale;
- boolean dimmed;
- boolean dark;
- boolean hideSensitive;
- boolean belowSpeedBump;
-
- /**
- * The amount which the view should be clipped from the top. This is calculated to
- * perceive consistent shadows.
- */
- int clipTopAmount;
-
- /**
- * How much does the child overlap with the previous view on the top? Can be used for
- * a clipping optimization
- */
- int topOverLap;
-
- /**
- * The index of the view, only accounting for views not equal to GONE
- */
- int notGoneIndex;
-
- /**
- * The location this view is currently rendered at.
- *
- * <p>See <code>LOCATION_</code> flags.</p>
- */
- int location;
- }
}
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 b027787..5b8fe89 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
@@ -26,12 +26,12 @@ import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import com.android.systemui.R;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.ExpandableView;
import com.android.systemui.statusbar.SpeedBumpView;
import java.util.ArrayList;
import java.util.HashSet;
-import java.util.Set;
import java.util.Stack;
/**
@@ -42,12 +42,17 @@ public class StackStateAnimator {
public static final int ANIMATION_DURATION_STANDARD = 360;
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_EXPAND_CLICKED = 360;
public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220;
+ public static final int ANIMATION_DURATION_HEADS_UP_APPEAR = 650;
+ public static final int ANIMATION_DURATION_HEADS_UP_DISAPPEAR = 230;
public static final int ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING = 80;
+ public static final int ANIMATION_DELAY_PER_ELEMENT_EXPAND_CHILDREN = 54;
public static final int ANIMATION_DELAY_PER_ELEMENT_MANUAL = 32;
public static final int ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE = 48;
public static final int ANIMATION_DELAY_PER_ELEMENT_DARK = 24;
- private static final int DELAY_EFFECT_MAX_INDEX_DIFFERENCE = 2;
+ public static final int DELAY_EFFECT_MAX_INDEX_DIFFERENCE = 2;
+ public static final int DELAY_EFFECT_MAX_INDEX_DIFFERENCE_CHILDREN = 3;
private static final int TAG_ANIMATOR_TRANSLATION_Y = R.id.translation_y_animator_tag;
private static final int TAG_ANIMATOR_TRANSLATION_Z = R.id.translation_z_animator_tag;
@@ -69,12 +74,16 @@ public class StackStateAnimator {
private static final int TAG_START_TOP_INSET = R.id.top_inset_animator_start_value_tag;
private final Interpolator mFastOutSlowInInterpolator;
+ private final Interpolator mHeadsUpAppearInterpolator;
private final int mGoToFullShadeAppearingTranslation;
+ private final StackViewState mTmpState = new StackViewState();
public NotificationStackScrollLayout mHostLayout;
private ArrayList<NotificationStackScrollLayout.AnimationEvent> mNewEvents =
new ArrayList<>();
private ArrayList<View> mNewAddChildren = new ArrayList<>();
- private Set<Animator> mAnimatorSet = new HashSet<>();
+ private HashSet<View> mHeadsUpAppearChildren = new HashSet<>();
+ private HashSet<View> mHeadsUpDisappearChildren = new HashSet<>();
+ private HashSet<Animator> mAnimatorSet = new HashSet<>();
private Stack<AnimatorListenerAdapter> mAnimationListenerPool = new Stack<>();
private AnimationFilter mAnimationFilter = new AnimationFilter();
private long mCurrentLength;
@@ -82,9 +91,12 @@ public class StackStateAnimator {
/** The current index for the last child which was not added in this event set. */
private int mCurrentLastNotAddedIndex;
-
private ValueAnimator mTopOverScrollAnimator;
private ValueAnimator mBottomOverScrollAnimator;
+ private ExpandableNotificationRow mChildExpandingView;
+ private int mHeadsUpAppearHeightBottom;
+ private boolean mShadeExpanded;
+ private ArrayList<View> mChildrenToClearFromOverlay = new ArrayList<>();
public StackStateAnimator(NotificationStackScrollLayout hostLayout) {
mHostLayout = hostLayout;
@@ -93,6 +105,7 @@ public class StackStateAnimator {
mGoToFullShadeAppearingTranslation =
hostLayout.getContext().getResources().getDimensionPixelSize(
R.dimen.go_to_full_shade_appearing_translation);
+ mHeadsUpAppearInterpolator = new HeadsUpAppearInterpolator();
}
public boolean isRunning() {
@@ -113,20 +126,50 @@ public class StackStateAnimator {
for (int i = 0; i < childCount; i++) {
final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i);
- StackScrollState.ViewState viewState = finalState.getViewStateForView(child);
- if (viewState == null || child.getVisibility() == View.GONE) {
+ StackViewState viewState = finalState.getViewStateForView(child);
+ if (viewState == null || child.getVisibility() == View.GONE
+ || applyWithoutAnimation(child, viewState, finalState)) {
continue;
}
- child.setClipBounds(null);
- startAnimations(child, viewState, finalState, i);
+ child.setClipTopOptimization(0);
+ startStackAnimations(child, viewState, finalState, i, -1 /* fixedDelay */);
}
if (!isRunning()) {
// no child has preformed any animation, lets finish
onAnimationFinished();
}
+ mHeadsUpAppearChildren.clear();
+ mHeadsUpDisappearChildren.clear();
mNewEvents.clear();
mNewAddChildren.clear();
+ mChildExpandingView = null;
+ }
+
+ /**
+ * Determines if a view should not perform an animation and applies it directly.
+ *
+ * @return true if no animation should be performed
+ */
+ private boolean applyWithoutAnimation(ExpandableView child, StackViewState viewState,
+ StackScrollState finalState) {
+ if (mShadeExpanded) {
+ return false;
+ }
+ if (getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y) != null) {
+ // A Y translation animation is running
+ return false;
+ }
+ if (mHeadsUpDisappearChildren.contains(child) || mHeadsUpAppearChildren.contains(child)) {
+ // This is a heads up animation
+ return false;
+ }
+ if (NotificationStackScrollLayout.isPinnedHeadsUp(child)) {
+ // This is another headsUp which might move. Let's animate!
+ return false;
+ }
+ finalState.applyState(child, viewState);
+ return true;
}
private int findLastNotAddedIndex(StackScrollState finalState) {
@@ -134,7 +177,7 @@ public class StackStateAnimator {
for (int i = childCount - 1; i >= 0; i--) {
final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i);
- StackScrollState.ViewState viewState = finalState.getViewStateForView(child);
+ StackViewState viewState = finalState.getViewStateForView(child);
if (viewState == null || child.getVisibility() == View.GONE) {
continue;
}
@@ -145,18 +188,29 @@ public class StackStateAnimator {
return -1;
}
+
/**
- * Start an animation to the given viewState
+ * Start an animation to the given {@link StackViewState}.
+ *
+ * @param child the child to start the animation on
+ * @param viewState the {@link StackViewState} of the view to animate to
+ * @param finalState the final state after the animation
+ * @param i the index of the view; only relevant if the view is the speed bump and is
+ * ignored otherwise
+ * @param fixedDelay a fixed delay if desired or -1 if the delay should be calculated
*/
- private void startAnimations(final ExpandableView child, StackScrollState.ViewState viewState,
- StackScrollState finalState, int i) {
- int childVisibility = child.getVisibility();
- boolean wasVisible = childVisibility == View.VISIBLE;
+ public void startStackAnimations(final ExpandableView child, StackViewState viewState,
+ StackScrollState finalState, int i, long fixedDelay) {
final float alpha = viewState.alpha;
- if (!wasVisible && alpha != 0 && !viewState.gone) {
- child.setVisibility(View.VISIBLE);
+ boolean wasAdded = mNewAddChildren.contains(child);
+ long duration = mCurrentLength;
+ if (wasAdded && mAnimationFilter.hasGoToFullShadeEvent) {
+ child.setTranslationY(child.getTranslationY() + mGoToFullShadeAppearingTranslation);
+ float longerDurationFactor = viewState.notGoneIndex - mCurrentLastNotAddedIndex;
+ longerDurationFactor = (float) Math.pow(longerDurationFactor, 0.7f);
+ duration = ANIMATION_DURATION_APPEAR_DISAPPEAR + 50 +
+ (long) (100 * longerDurationFactor);
}
-
boolean yTranslationChanging = child.getTranslationY() != viewState.yTranslation;
boolean zTranslationChanging = child.getTranslationZ() != viewState.zTranslation;
boolean scaleChanging = child.getScaleX() != viewState.scale;
@@ -164,94 +218,40 @@ public class StackStateAnimator {
boolean heightChanging = viewState.height != child.getActualHeight();
boolean darkChanging = viewState.dark != child.isDark();
boolean topInsetChanging = viewState.clipTopAmount != child.getClipTopAmount();
- boolean wasAdded = mNewAddChildren.contains(child);
boolean hasDelays = mAnimationFilter.hasDelays;
boolean isDelayRelevant = yTranslationChanging || zTranslationChanging || scaleChanging ||
alphaChanging || heightChanging || topInsetChanging || darkChanging;
- boolean noAnimation = wasAdded;
long delay = 0;
- long duration = mCurrentLength;
- if (hasDelays && isDelayRelevant || wasAdded) {
+ if (fixedDelay != -1) {
+ delay = fixedDelay;
+ } else if (hasDelays && isDelayRelevant || wasAdded) {
delay = mCurrentAdditionalDelay + calculateChildAnimationDelay(viewState, finalState);
}
- if (wasAdded && mAnimationFilter.hasGoToFullShadeEvent) {
- child.setTranslationY(child.getTranslationY() + mGoToFullShadeAppearingTranslation);
- yTranslationChanging = true;
- float longerDurationFactor = viewState.notGoneIndex - mCurrentLastNotAddedIndex;
- longerDurationFactor = (float) Math.pow(longerDurationFactor, 0.7f);
- duration = ANIMATION_DURATION_APPEAR_DISAPPEAR + 50 +
- (long) (100 * longerDurationFactor);
- }
-
- // start translationY animation
- if (yTranslationChanging) {
- if (noAnimation && !mAnimationFilter.hasGoToFullShadeEvent) {
- child.setTranslationY(viewState.yTranslation);
- } else {
- startYTranslationAnimation(child, viewState, duration, delay);
- }
- }
-
- // start translationZ animation
- if (zTranslationChanging) {
- if (noAnimation) {
- child.setTranslationZ(viewState.zTranslation);
- } else {
- startZTranslationAnimation(child, viewState, duration, delay);
- }
- }
-
- // start scale animation
- if (scaleChanging) {
- if (noAnimation) {
- child.setScaleX(viewState.scale);
- child.setScaleY(viewState.scale);
- } else {
- startScaleAnimation(child, viewState, duration);
- }
- }
-
- // start alpha animation
- if (alphaChanging && child.getTranslationX() == 0) {
- if (noAnimation) {
- child.setAlpha(viewState.alpha);
- } else {
- startAlphaAnimation(child, viewState, duration, delay);
- }
- }
+ startViewAnimations(child, viewState, delay, duration);
// start height animation
if (heightChanging && child.getActualHeight() != 0) {
- if (noAnimation) {
- child.setActualHeight(viewState.height, false);
- } else {
- startHeightAnimation(child, viewState, duration, delay);
- }
+ startHeightAnimation(child, viewState, duration, delay);
}
// start top inset animation
if (topInsetChanging) {
- if (noAnimation) {
- child.setClipTopAmount(viewState.clipTopAmount);
- } else {
- startInsetAnimation(child, viewState, duration, delay);
- }
+ startInsetAnimation(child, viewState, duration, delay);
}
// start dimmed animation
- child.setDimmed(viewState.dimmed, mAnimationFilter.animateDimmed && !wasAdded
- && !noAnimation);
-
- // start dark animation
- child.setDark(viewState.dark, mAnimationFilter.animateDark && !noAnimation, delay);
+ child.setDimmed(viewState.dimmed, mAnimationFilter.animateDimmed);
// apply speed bump state
child.setBelowSpeedBump(viewState.belowSpeedBump);
// start hiding sensitive animation
- child.setHideSensitive(viewState.hideSensitive, mAnimationFilter.animateHideSensitive &&
- !wasAdded && !noAnimation, delay, duration);
+ child.setHideSensitive(viewState.hideSensitive, mAnimationFilter.animateHideSensitive,
+ delay, duration);
+
+ // start dark animation
+ child.setDark(viewState.dark, mAnimationFilter.animateDark, delay);
if (wasAdded) {
child.performAddAnimation(delay, mCurrentLength);
@@ -259,10 +259,59 @@ public class StackStateAnimator {
if (child instanceof SpeedBumpView) {
finalState.performSpeedBumpAnimation(i, (SpeedBumpView) child, viewState,
delay + duration);
+ } else if (child instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ row.startChildAnimation(finalState, this, child == mChildExpandingView, delay,
+ duration);
}
}
- private long calculateChildAnimationDelay(StackScrollState.ViewState viewState,
+ /**
+ * Start an animation to a new {@link ViewState}.
+ *
+ * @param child the child to start the animation on
+ * @param viewState the {@link StackViewState} of the view to animate to
+ * @param delay a fixed delay
+ * @param duration the duration of the animation
+ */
+ public void startViewAnimations(View child, ViewState viewState, long delay, long duration) {
+ boolean wasVisible = child.getVisibility() == View.VISIBLE;
+ final float alpha = viewState.alpha;
+ if (!wasVisible && alpha != 0 && !viewState.gone) {
+ child.setVisibility(View.VISIBLE);
+ }
+ boolean yTranslationChanging = child.getTranslationY() != viewState.yTranslation;
+ boolean zTranslationChanging = child.getTranslationZ() != viewState.zTranslation;
+ boolean scaleChanging = child.getScaleX() != viewState.scale;
+ float childAlpha = child.getVisibility() == View.INVISIBLE ? 0.0f : child.getAlpha();
+ boolean alphaChanging = viewState.alpha != childAlpha;
+ if (child instanceof ExpandableView) {
+ // We don't want views to change visibility when they are animating to GONE
+ alphaChanging &= !((ExpandableView) child).willBeGone();
+ }
+
+ // start translationY animation
+ if (yTranslationChanging) {
+ startYTranslationAnimation(child, viewState, duration, delay);
+ }
+
+ // start translationZ animation
+ if (zTranslationChanging) {
+ startZTranslationAnimation(child, viewState, duration, delay);
+ }
+
+ // start scale animation
+ if (scaleChanging) {
+ startScaleAnimation(child, viewState, duration);
+ }
+
+ // start alpha animation
+ if (alphaChanging && child.getTranslationX() == 0) {
+ startAlphaAnimation(child, viewState, duration, delay);
+ }
+ }
+
+ private long calculateChildAnimationDelay(StackViewState viewState,
StackScrollState finalState) {
if (mAnimationFilter.hasDarkEvent) {
return calculateDelayDark(viewState);
@@ -314,7 +363,7 @@ public class StackStateAnimator {
return minDelay;
}
- private long calculateDelayDark(StackScrollState.ViewState viewState) {
+ private long calculateDelayDark(StackViewState viewState) {
int referenceIndex;
if (mAnimationFilter.darkAnimationOriginIndex ==
NotificationStackScrollLayout.AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE) {
@@ -328,14 +377,14 @@ public class StackStateAnimator {
return Math.abs(referenceIndex - viewState.notGoneIndex) * ANIMATION_DELAY_PER_ELEMENT_DARK;
}
- private long calculateDelayGoToFullShade(StackScrollState.ViewState viewState) {
+ private long calculateDelayGoToFullShade(StackViewState viewState) {
float index = viewState.notGoneIndex;
index = (float) Math.pow(index, 0.7f);
return (long) (index * ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE);
}
private void startHeightAnimation(final ExpandableView child,
- StackScrollState.ViewState viewState, long duration, long delay) {
+ StackViewState viewState, long duration, long delay) {
Integer previousStartValue = getChildTag(child, TAG_START_HEIGHT);
Integer previousEndValue = getChildTag(child, TAG_END_HEIGHT);
int newEndValue = viewState.height;
@@ -394,7 +443,7 @@ public class StackStateAnimator {
}
private void startInsetAnimation(final ExpandableView child,
- StackScrollState.ViewState viewState, long duration, long delay) {
+ StackViewState viewState, long duration, long delay) {
Integer previousStartValue = getChildTag(child, TAG_START_TOP_INSET);
Integer previousEndValue = getChildTag(child, TAG_END_TOP_INSET);
int newEndValue = viewState.clipTopAmount;
@@ -451,8 +500,8 @@ public class StackStateAnimator {
child.setTag(TAG_END_TOP_INSET, newEndValue);
}
- private void startAlphaAnimation(final ExpandableView child,
- final StackScrollState.ViewState viewState, long duration, long delay) {
+ private void startAlphaAnimation(final View child,
+ final ViewState viewState, long duration, long delay) {
Float previousStartValue = getChildTag(child,TAG_START_ALPHA);
Float previousEndValue = getChildTag(child,TAG_END_ALPHA);
final float newEndValue = viewState.alpha;
@@ -525,8 +574,8 @@ public class StackStateAnimator {
child.setTag(TAG_END_ALPHA, newEndValue);
}
- private void startZTranslationAnimation(final ExpandableView child,
- final StackScrollState.ViewState viewState, long duration, long delay) {
+ private void startZTranslationAnimation(final View child,
+ final ViewState viewState, long duration, long delay) {
Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Z);
Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Z);
float newEndValue = viewState.zTranslation;
@@ -577,8 +626,8 @@ public class StackStateAnimator {
child.setTag(TAG_END_TRANSLATION_Z, newEndValue);
}
- private void startYTranslationAnimation(final ExpandableView child,
- StackScrollState.ViewState viewState, long duration, long delay) {
+ private void startYTranslationAnimation(final View child,
+ ViewState viewState, long duration, long delay) {
Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Y);
Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Y);
float newEndValue = viewState.yTranslation;
@@ -608,7 +657,9 @@ public class StackStateAnimator {
ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Y,
child.getTranslationY(), newEndValue);
- animator.setInterpolator(mFastOutSlowInInterpolator);
+ Interpolator interpolator = mHeadsUpAppearChildren.contains(child) ?
+ mHeadsUpAppearInterpolator :mFastOutSlowInInterpolator;
+ animator.setInterpolator(interpolator);
long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
animator.setDuration(newDuration);
if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
@@ -630,8 +681,8 @@ public class StackStateAnimator {
child.setTag(TAG_END_TRANSLATION_Y, newEndValue);
}
- private void startScaleAnimation(final ExpandableView child,
- StackScrollState.ViewState viewState, long duration) {
+ private void startScaleAnimation(final View child,
+ ViewState viewState, long duration) {
Float previousStartValue = getChildTag(child, TAG_START_SCALE);
Float previousEndValue = getChildTag(child, TAG_END_SCALE);
float newEndValue = viewState.scale;
@@ -723,7 +774,7 @@ public class StackStateAnimator {
};
}
- private static <T> T getChildTag(View child, int tag) {
+ public static <T> T getChildTag(View child, int tag) {
return (T) child.getTag(tag);
}
@@ -748,6 +799,10 @@ public class StackStateAnimator {
private void onAnimationFinished() {
mHostLayout.onChildAnimationFinished();
+ for (View v : mChildrenToClearFromOverlay) {
+ mHostLayout.getOverlay().remove(v);
+ }
+ mChildrenToClearFromOverlay.clear();
}
/**
@@ -765,7 +820,7 @@ public class StackStateAnimator {
NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD) {
// This item is added, initialize it's properties.
- StackScrollState.ViewState viewState = finalState
+ StackViewState viewState = finalState
.getViewStateForView(changingView);
if (viewState == null) {
// The position for this child was never generated, let's continue.
@@ -776,10 +831,7 @@ public class StackStateAnimator {
finalState.removeViewStateForView(changingView);
continue;
}
- changingView.setAlpha(viewState.alpha);
- changingView.setTranslationY(viewState.yTranslation);
- changingView.setTranslationZ(viewState.zTranslation);
- changingView.setActualHeight(viewState.height, false);
+ finalState.applyState(changingView, viewState);
mNewAddChildren.add(changingView);
} else if (event.animationType ==
@@ -791,7 +843,7 @@ public class StackStateAnimator {
// Find the amount to translate up. This is needed in order to understand the
// direction of the remove animation (either downwards or upwards)
- StackScrollState.ViewState viewState = finalState
+ StackViewState viewState = finalState
.getViewStateForView(event.viewAfterChangingView);
int actualHeight = changingView.getActualHeight();
// upwards by default
@@ -813,11 +865,43 @@ public class StackStateAnimator {
mHostLayout.getOverlay().remove(changingView);
}
});
- } else if (event.animationType ==
+ } else if (event.animationType ==
NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) {
// A race condition can trigger the view to be added to the overlay even though
// it is swiped out. So let's remove it
mHostLayout.getOverlay().remove(changingView);
+ } else if (event.animationType == NotificationStackScrollLayout
+ .AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) event.changingView;
+ row.prepareExpansionChanged(finalState);
+ mChildExpandingView = row;
+ } else if (event.animationType == NotificationStackScrollLayout
+ .AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR) {
+ // This item is added, initialize it's properties.
+ StackViewState viewState = finalState.getViewStateForView(changingView);
+ mTmpState.copyFrom(viewState);
+ if (event.headsUpFromBottom) {
+ mTmpState.yTranslation = mHeadsUpAppearHeightBottom;
+ } else {
+ mTmpState.yTranslation = -mTmpState.height;
+ }
+ mHeadsUpAppearChildren.add(changingView);
+ finalState.applyState(changingView, mTmpState);
+ } else if (event.animationType == NotificationStackScrollLayout
+ .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR) {
+ mHeadsUpDisappearChildren.add(changingView);
+ if (mHostLayout.indexOfChild(changingView) == -1) {
+ // This notification was actually removed, so we need to add it to the overlay
+ mHostLayout.getOverlay().add(changingView);
+ mTmpState.initFrom(changingView);
+ mTmpState.yTranslation = -changingView.getActualHeight();
+ // We temporarily enable Y animations, the real filter will be combined
+ // afterwards anyway
+ mAnimationFilter.animateY = true;
+ startViewAnimations(changingView, mTmpState, 0,
+ ANIMATION_DURATION_HEADS_UP_DISAPPEAR);
+ mChildrenToClearFromOverlay.add(changingView);
+ }
}
mNewEvents.add(event);
}
@@ -883,4 +967,12 @@ public class StackStateAnimator {
return getChildTag(view, TAG_END_HEIGHT);
}
}
+
+ public void setHeadsUpAppearHeightBottom(int headsUpAppearHeightBottom) {
+ mHeadsUpAppearHeightBottom = headsUpAppearHeightBottom;
+ }
+
+ public void setShadeExpanded(boolean shadeExpanded) {
+ mShadeExpanded = shadeExpanded;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackViewState.java
new file mode 100644
index 0000000..55ef440
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackViewState.java
@@ -0,0 +1,86 @@
+/*
+ * 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.statusbar.stack;
+
+import android.view.View;
+
+import com.android.systemui.statusbar.ExpandableView;
+
+/**
+* A state of an expandable view
+*/
+public class StackViewState extends ViewState {
+
+ // These are flags such that we can create masks for filtering.
+
+ public static final int LOCATION_UNKNOWN = 0x00;
+ public static final int LOCATION_FIRST_CARD = 0x01;
+ public static final int LOCATION_TOP_STACK_HIDDEN = 0x02;
+ public static final int LOCATION_TOP_STACK_PEEKING = 0x04;
+ public static final int LOCATION_MAIN_AREA = 0x08;
+ public static final int LOCATION_BOTTOM_STACK_PEEKING = 0x10;
+ public static final int LOCATION_BOTTOM_STACK_HIDDEN = 0x20;
+ /** The view isn't layouted at all. */
+ public static final int LOCATION_GONE = 0x40;
+
+ public int height;
+ public boolean dimmed;
+ public boolean dark;
+ public boolean hideSensitive;
+ public boolean belowSpeedBump;
+
+ /**
+ * The amount which the view should be clipped from the top. This is calculated to
+ * perceive consistent shadows.
+ */
+ public int clipTopAmount;
+
+ /**
+ * How much does the child overlap with the previous view on the top? Can be used for
+ * a clipping optimization
+ */
+ public int topOverLap;
+
+ /**
+ * The index of the view, only accounting for views not equal to GONE
+ */
+ public int notGoneIndex;
+
+ /**
+ * The location this view is currently rendered at.
+ *
+ * <p>See <code>LOCATION_</code> flags.</p>
+ */
+ public int location;
+
+ @Override
+ public void copyFrom(ViewState viewState) {
+ super.copyFrom(viewState);
+ if (viewState instanceof StackViewState) {
+ StackViewState svs = (StackViewState) viewState;
+ height = svs.height;
+ dimmed = svs.dimmed;
+ dark = svs.dark;
+ hideSensitive = svs.hideSensitive;
+ belowSpeedBump = svs.belowSpeedBump;
+ clipTopAmount = svs.clipTopAmount;
+ topOverLap = svs.topOverLap;
+ notGoneIndex = svs.notGoneIndex;
+ location = svs.location;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
new file mode 100644
index 0000000..3e538df
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
@@ -0,0 +1,49 @@
+/*
+ * 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.statusbar.stack;
+
+import android.view.View;
+
+/**
+ * A state of a view. This can be used to apply a set of view properties to a view with
+ * {@link com.android.systemui.statusbar.stack.StackScrollState} or start animations with
+ * {@link com.android.systemui.statusbar.stack.StackStateAnimator}.
+*/
+public class ViewState {
+
+ public float alpha;
+ public float yTranslation;
+ public float zTranslation;
+ public boolean gone;
+ public float scale;
+
+ public void copyFrom(ViewState viewState) {
+ alpha = viewState.alpha;
+ yTranslation = viewState.yTranslation;
+ zTranslation = viewState.zTranslation;
+ gone = viewState.gone;
+ scale = viewState.scale;
+ }
+
+ public void initFrom(View view) {
+ alpha = view.getVisibility() == View.INVISIBLE ? 0.0f : view.getAlpha();
+ yTranslation = view.getTranslationY();
+ zTranslation = view.getTranslationZ();
+ gone = view.getVisibility() == View.GONE;
+ scale = view.getScaleX();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
index 08732e5..920b682 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
@@ -20,12 +20,11 @@ import android.os.IBinder;
import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.StatusBarNotification;
import android.view.View;
-import android.view.ViewGroup.LayoutParams;
-import android.view.WindowManager;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.systemui.statusbar.ActivatableNotificationView;
import com.android.systemui.statusbar.BaseStatusBar;
+import com.android.systemui.statusbar.NotificationData;
/*
* Status bar implementation for "large screen" products that mostly present no on-screen nav
@@ -47,7 +46,8 @@ public class TvStatusBar extends BaseStatusBar {
}
@Override
- public void addNotification(StatusBarNotification notification, RankingMap ranking) {
+ public void addNotification(StatusBarNotification notification, RankingMap ranking,
+ NotificationData.Entry entry) {
}
@Override
@@ -59,7 +59,7 @@ public class TvStatusBar extends BaseStatusBar {
}
@Override
- public void disable(int state, boolean animate) {
+ public void disable(int state1, int state2, boolean animate) {
}
@Override
@@ -104,82 +104,82 @@ public class TvStatusBar extends BaseStatusBar {
}
@Override
- protected WindowManager.LayoutParams getSearchLayoutParams(
- LayoutParams layoutParams) {
- return null;
+ protected void setAreThereNotifications() {
}
@Override
- protected void haltTicker() {
+ protected void updateNotifications() {
}
@Override
- protected void setAreThereNotifications() {
+ public boolean shouldDisableNavbarGestures() {
+ return true;
}
- @Override
- protected void updateNotifications() {
+ public View getStatusBarView() {
+ return null;
}
@Override
- protected void tick(StatusBarNotification n, boolean firstTime) {
+ public void maybeEscalateHeadsUp() {
}
@Override
- protected void updateExpandedViewPos(int expandedPosition) {
+ protected boolean isPanelFullyCollapsed() {
+ return false;
}
@Override
- protected boolean shouldDisableNavbarGestures() {
- return true;
+ protected int getMaxKeyguardNotifications() {
+ return 0;
}
- public View getStatusBarView() {
- return null;
+ @Override
+ public void animateExpandSettingsPanel() {
}
@Override
- public void resetHeadsUpDecayTimer() {
+ protected void createAndAddWindows() {
}
@Override
- public void scheduleHeadsUpOpen() {
+ protected void refreshLayout(int layoutDirection) {
}
@Override
- public void scheduleHeadsUpEscalation() {
+ public void onActivated(ActivatableNotificationView view) {
}
@Override
- public void scheduleHeadsUpClose() {
+ public void onActivationReset(ActivatableNotificationView view) {
}
@Override
- protected int getMaxKeyguardNotifications() {
- return 0;
+ public void showScreenPinningRequest() {
}
@Override
- public void animateExpandSettingsPanel() {
+ public void appTransitionPending() {
}
@Override
- protected void createAndAddWindows() {
+ public void appTransitionCancelled() {
}
@Override
- protected void refreshLayout(int layoutDirection) {
+ public void appTransitionStarting(long startTime, long duration) {
}
@Override
- public void onActivated(ActivatableNotificationView view) {
+ protected void updateHeadsUp(String key, NotificationData.Entry entry, boolean shouldInterrupt,
+ boolean alertAgain) {
}
@Override
- public void onActivationReset(ActivatableNotificationView view) {
+ protected void setHeadsUpUser(int newUserId) {
}
- @Override
- public void showScreenPinningRequest() {
+ protected boolean isSnoozedPackage(StatusBarNotification sbn) {
+ return false;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/AutoScrollView.java b/packages/SystemUI/src/com/android/systemui/tuner/AutoScrollView.java
new file mode 100644
index 0000000..6aea2cb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tuner/AutoScrollView.java
@@ -0,0 +1,46 @@
+/*
+ * 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.tuner;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.DragEvent;
+import android.widget.ScrollView;
+
+public class AutoScrollView extends ScrollView {
+
+ private static final float SCROLL_PERCENT = .10f;
+
+ public AutoScrollView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public boolean onDragEvent(DragEvent event) {
+ switch (event.getAction()) {
+ case DragEvent.ACTION_DRAG_LOCATION:
+ int y = (int) event.getY();
+ int height = getHeight();
+ int scrollPadding = (int) (height * SCROLL_PERCENT);
+ if (y < scrollPadding) {
+ scrollBy(0, y - scrollPadding);
+ } else if (y > height - scrollPadding) {
+ scrollBy(0, y - height + scrollPadding);
+ }
+ break;
+ }
+ return false;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
new file mode 100644
index 0000000..ca6aaeb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
@@ -0,0 +1,178 @@
+/*
+ * 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.tuner;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.database.ContentObserver;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceChangeListener;
+import android.preference.PreferenceFragment;
+import android.preference.PreferenceScreen;
+import android.preference.SwitchPreference;
+import android.provider.Settings;
+
+import com.android.systemui.DemoMode;
+import com.android.systemui.R;
+
+public class DemoModeFragment extends PreferenceFragment implements OnPreferenceChangeListener {
+
+ private static final String DEMO_MODE_ALLOWED = "sysui_demo_allowed";
+ private static final String DEMO_MODE_ON = "sysui_tuner_demo_on";
+
+ private static final String[] STATUS_ICONS = {
+ "volume",
+ "bluetooth",
+ "location",
+ "alarm",
+ "zen",
+ "sync",
+ "tty",
+ "eri",
+ "mute",
+ "speakerphone",
+ "managed_profile",
+ };
+
+ private SwitchPreference mEnabledSwitch;
+ private SwitchPreference mOnSwitch;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Context context = getContext();
+ mEnabledSwitch = new SwitchPreference(context);
+ mEnabledSwitch.setTitle(R.string.enable_demo_mode);
+ mEnabledSwitch.setOnPreferenceChangeListener(this);
+ mOnSwitch = new SwitchPreference(context);
+ mOnSwitch.setTitle(R.string.show_demo_mode);
+ mOnSwitch.setEnabled(false);
+ mOnSwitch.setOnPreferenceChangeListener(this);
+
+ PreferenceScreen screen = getPreferenceManager().createPreferenceScreen(context);
+ screen.addPreference(mEnabledSwitch);
+ screen.addPreference(mOnSwitch);
+ setPreferenceScreen(screen);
+
+ updateDemoModeEnabled();
+ updateDemoModeOn();
+ ContentResolver contentResolver = getContext().getContentResolver();
+ contentResolver.registerContentObserver(Settings.Global.getUriFor(DEMO_MODE_ALLOWED), false,
+ mDemoModeObserver);
+ contentResolver.registerContentObserver(Settings.Global.getUriFor(DEMO_MODE_ON), false,
+ mDemoModeObserver);
+ }
+
+ @Override
+ public void onDestroy() {
+ getContext().getContentResolver().unregisterContentObserver(mDemoModeObserver);
+ super.onDestroy();
+ }
+
+ private void updateDemoModeEnabled() {
+ boolean enabled = Settings.Global.getInt(getContext().getContentResolver(),
+ DEMO_MODE_ALLOWED, 0) != 0;
+ mEnabledSwitch.setChecked(enabled);
+ mOnSwitch.setEnabled(enabled);
+ }
+
+ private void updateDemoModeOn() {
+ boolean enabled = Settings.Global.getInt(getContext().getContentResolver(),
+ DEMO_MODE_ON, 0) != 0;
+ mOnSwitch.setChecked(enabled);
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ if (preference == mEnabledSwitch) {
+ setGlobal(DEMO_MODE_ALLOWED, newValue == Boolean.TRUE ? 1 : 0);
+ } else if (preference == mOnSwitch) {
+ if (newValue == Boolean.TRUE) {
+ startDemoMode();
+ } else {
+ stopDemoMode();
+ }
+ } else {
+ return false;
+ }
+ return true;
+ }
+
+ private void startDemoMode() {
+ Intent intent = new Intent(DemoMode.ACTION_DEMO);
+
+ intent.putExtra(DemoMode.EXTRA_COMMAND, DemoMode.COMMAND_ENTER);
+ getContext().sendBroadcast(intent);
+
+ intent.putExtra(DemoMode.EXTRA_COMMAND, DemoMode.COMMAND_CLOCK);
+ intent.putExtra("hhmm", "0520");
+ getContext().sendBroadcast(intent);
+
+ intent.putExtra(DemoMode.EXTRA_COMMAND, DemoMode.COMMAND_NETWORK);
+ intent.putExtra("wifi", "show");
+ intent.putExtra("mobile", "show");
+ intent.putExtra("sims", "1");
+ intent.putExtra("nosim", "false");
+ intent.putExtra("level", "4");
+ intent.putExtra("datatypel", "");
+ getContext().sendBroadcast(intent);
+
+ // Need to send this after so that the sim controller already exists.
+ intent.putExtra("fully", "true");
+ getContext().sendBroadcast(intent);
+
+ intent.putExtra(DemoMode.EXTRA_COMMAND, DemoMode.COMMAND_BATTERY);
+ intent.putExtra("level", "100");
+ intent.putExtra("plugged", "false");
+ getContext().sendBroadcast(intent);
+
+ intent.putExtra(DemoMode.EXTRA_COMMAND, DemoMode.COMMAND_STATUS);
+ for (String icon : STATUS_ICONS) {
+ intent.putExtra(icon, "hide");
+ }
+ getContext().sendBroadcast(intent);
+
+ intent.putExtra(DemoMode.EXTRA_COMMAND, DemoMode.COMMAND_NOTIFICATIONS);
+ intent.putExtra("visible", "false");
+ getContext().sendBroadcast(intent);
+
+ setGlobal(DEMO_MODE_ON, 1);
+ }
+
+ private void stopDemoMode() {
+ Intent intent = new Intent(DemoMode.ACTION_DEMO);
+ intent.putExtra(DemoMode.EXTRA_COMMAND, DemoMode.COMMAND_EXIT);
+ getContext().sendBroadcast(intent);
+ setGlobal(DEMO_MODE_ON, 0);
+ }
+
+ private void setGlobal(String key, int value) {
+ Settings.Global.putInt(getContext().getContentResolver(), key, value);
+ }
+
+ private final ContentObserver mDemoModeObserver =
+ new ContentObserver(new Handler(Looper.getMainLooper())) {
+ public void onChange(boolean selfChange) {
+ updateDemoModeEnabled();
+ updateDemoModeOn();
+ };
+ };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java b/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java
new file mode 100644
index 0000000..a5b244e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java
@@ -0,0 +1,522 @@
+/*
+ * 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.tuner;
+
+import android.app.ActivityManager;
+import android.app.AlertDialog;
+import android.app.Fragment;
+import android.content.ClipData;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.provider.Settings.Secure;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.DragEvent;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnDragListener;
+import android.view.View.OnTouchListener;
+import android.view.ViewGroup;
+import android.widget.EditText;
+import android.widget.FrameLayout;
+import android.widget.ScrollView;
+
+import com.android.systemui.R;
+import com.android.systemui.qs.QSPanel;
+import com.android.systemui.qs.QSTile;
+import com.android.systemui.qs.QSTile.Host.Callback;
+import com.android.systemui.qs.QSTile.ResourceIcon;
+import com.android.systemui.qs.QSTileView;
+import com.android.systemui.qs.tiles.IntentTile;
+import com.android.systemui.statusbar.phone.QSTileHost;
+import com.android.systemui.statusbar.policy.SecurityController;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class QsTuner extends Fragment implements Callback {
+
+ private static final String TAG = "QsTuner";
+
+ private static final int MENU_RESET = Menu.FIRST;
+
+ private DraggableQsPanel mQsPanel;
+ private CustomHost mTileHost;
+
+ private FrameLayout mDropTarget;
+
+ private ScrollView mScrollRoot;
+
+ private FrameLayout mAddTarget;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setHasOptionsMenu(true);
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ menu.add(0, MENU_RESET, 0, com.android.internal.R.string.reset);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case MENU_RESET:
+ mTileHost.reset();
+ break;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ mScrollRoot = (ScrollView) inflater.inflate(R.layout.tuner_qs, container, false);
+
+ mQsPanel = new DraggableQsPanel(getContext());
+ mTileHost = new CustomHost(getContext());
+ mTileHost.setCallback(this);
+ mQsPanel.setTiles(mTileHost.getTiles());
+ mQsPanel.setHost(mTileHost);
+ mQsPanel.refreshAllTiles();
+ ((ViewGroup) mScrollRoot.findViewById(R.id.all_details)).addView(mQsPanel, 0);
+
+ mDropTarget = (FrameLayout) mScrollRoot.findViewById(R.id.remove_target);
+ setupDropTarget();
+ mAddTarget = (FrameLayout) mScrollRoot.findViewById(R.id.add_target);
+ setupAddTarget();
+ return mScrollRoot;
+ }
+
+ @Override
+ public void onDestroyView() {
+ mTileHost.destroy();
+ super.onDestroyView();
+ }
+
+ private void setupDropTarget() {
+ QSTileView tileView = new QSTileView(getContext());
+ QSTile.State state = new QSTile.State();
+ state.visible = true;
+ state.icon = ResourceIcon.get(R.drawable.ic_delete);
+ state.label = getString(com.android.internal.R.string.delete);
+ tileView.onStateChanged(state);
+ mDropTarget.addView(tileView);
+ mDropTarget.setVisibility(View.GONE);
+ new DragHelper(tileView, new DropListener() {
+ @Override
+ public void onDrop(String sourceText) {
+ mTileHost.remove(sourceText);
+ }
+ });
+ }
+
+ private void setupAddTarget() {
+ QSTileView tileView = new QSTileView(getContext());
+ QSTile.State state = new QSTile.State();
+ state.visible = true;
+ state.icon = ResourceIcon.get(R.drawable.ic_add_circle_qs);
+ state.label = getString(R.string.add_tile);
+ tileView.onStateChanged(state);
+ mAddTarget.addView(tileView);
+ tileView.setClickable(true);
+ tileView.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mTileHost.showAddDialog();
+ }
+ });
+ }
+
+ public void onStartDrag() {
+ mDropTarget.post(new Runnable() {
+ @Override
+ public void run() {
+ mDropTarget.setVisibility(View.VISIBLE);
+ mAddTarget.setVisibility(View.GONE);
+ }
+ });
+ }
+
+ public void stopDrag() {
+ mDropTarget.post(new Runnable() {
+ @Override
+ public void run() {
+ mDropTarget.setVisibility(View.GONE);
+ mAddTarget.setVisibility(View.VISIBLE);
+ }
+ });
+ }
+
+ @Override
+ public void onTilesChanged() {
+ mQsPanel.setTiles(mTileHost.getTiles());
+ }
+
+ private static int getLabelResource(String spec) {
+ if (spec.equals("wifi")) return R.string.quick_settings_wifi_label;
+ else if (spec.equals("bt")) return R.string.quick_settings_bluetooth_label;
+ else if (spec.equals("inversion")) return R.string.quick_settings_inversion_label;
+ else if (spec.equals("cell")) return R.string.quick_settings_cellular_detail_title;
+ else if (spec.equals("airplane")) return R.string.airplane_mode;
+ else if (spec.equals("dnd")) return R.string.quick_settings_dnd_label;
+ else if (spec.equals("rotation")) return R.string.quick_settings_rotation_locked_label;
+ else if (spec.equals("flashlight")) return R.string.quick_settings_flashlight_label;
+ else if (spec.equals("location")) return R.string.quick_settings_location_label;
+ else if (spec.equals("cast")) return R.string.quick_settings_cast_title;
+ else if (spec.equals("hotspot")) return R.string.quick_settings_hotspot_label;
+ return 0;
+ }
+
+ private static class CustomHost extends QSTileHost {
+
+ public CustomHost(Context context) {
+ super(context, null, null, null, null, null, null, null, null, null,
+ null, null, new BlankSecurityController());
+ }
+
+ @Override
+ protected QSTile<?> createTile(String tileSpec) {
+ return new DraggableTile(this, tileSpec);
+ }
+
+ public void replace(String oldTile, String newTile) {
+ if (oldTile.equals(newTile)) {
+ return;
+ }
+ List<String> order = new ArrayList<>(mTileSpecs);
+ int index = order.indexOf(oldTile);
+ if (index < 0) {
+ Log.e(TAG, "Can't find " + oldTile);
+ return;
+ }
+ order.remove(newTile);
+ order.add(index, newTile);
+ setTiles(order);
+ }
+
+ public void remove(String tile) {
+ List<String> tiles = new ArrayList<>(mTileSpecs);
+ tiles.remove(tile);
+ setTiles(tiles);
+ }
+
+ public void add(String tile) {
+ List<String> tiles = new ArrayList<>(mTileSpecs);
+ tiles.add(tile);
+ setTiles(tiles);
+ }
+
+ public void reset() {
+ Secure.putStringForUser(getContext().getContentResolver(),
+ TILES_SETTING, "default", ActivityManager.getCurrentUser());
+ }
+
+ private void setTiles(List<String> tiles) {
+ Secure.putStringForUser(getContext().getContentResolver(), TILES_SETTING,
+ TextUtils.join(",", tiles), ActivityManager.getCurrentUser());
+ }
+
+ public void showAddDialog() {
+ List<String> tiles = mTileSpecs;
+ int numBroadcast = 0;
+ for (int i = 0; i < tiles.size(); i++) {
+ if (tiles.get(i).startsWith(IntentTile.PREFIX)) {
+ numBroadcast++;
+ }
+ }
+ String[] defaults =
+ getContext().getString(R.string.quick_settings_tiles_default).split(",");
+ final String[] available = new String[defaults.length + 1
+ - (tiles.size() - numBroadcast)];
+ final String[] availableTiles = new String[available.length];
+ int index = 0;
+ for (int i = 0; i < defaults.length; i++) {
+ if (tiles.contains(defaults[i])) {
+ continue;
+ }
+ int resource = getLabelResource(defaults[i]);
+ if (resource != 0) {
+ availableTiles[index] = defaults[i];
+ available[index++] = getContext().getString(resource);
+ } else {
+ availableTiles[index] = defaults[i];
+ available[index++] = defaults[i];
+ }
+ }
+ available[index++] = getContext().getString(R.string.broadcast_tile);
+ new AlertDialog.Builder(getContext())
+ .setTitle(R.string.add_tile)
+ .setItems(available, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ if (which < available.length - 1) {
+ add(availableTiles[which]);
+ } else {
+ showBroadcastTileDialog();
+ }
+ }
+ }).show();
+ }
+
+ public void showBroadcastTileDialog() {
+ final EditText editText = new EditText(getContext());
+ new AlertDialog.Builder(getContext())
+ .setTitle(R.string.broadcast_tile)
+ .setView(editText)
+ .setNegativeButton(android.R.string.cancel, null)
+ .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ String action = editText.getText().toString();
+ if (isValid(action)) {
+ add(IntentTile.PREFIX + action + ')');
+ }
+ }
+ }).show();
+ }
+
+ private boolean isValid(String action) {
+ for (int i = 0; i < action.length(); i++) {
+ char c = action.charAt(i);
+ if (!Character.isAlphabetic(c) && !Character.isDigit(c) && c != '.') {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static class BlankSecurityController implements SecurityController {
+ @Override
+ public boolean hasDeviceOwner() {
+ return false;
+ }
+
+ @Override
+ public boolean hasProfileOwner() {
+ return false;
+ }
+
+ @Override
+ public String getDeviceOwnerName() {
+ return null;
+ }
+
+ @Override
+ public String getProfileOwnerName() {
+ return null;
+ }
+
+ @Override
+ public boolean isVpnEnabled() {
+ return false;
+ }
+
+ @Override
+ public String getPrimaryVpnName() {
+ return null;
+ }
+
+ @Override
+ public String getProfileVpnName() {
+ return null;
+ }
+
+ @Override
+ public void onUserSwitched(int newUserId) {
+ }
+
+ @Override
+ public void addCallback(SecurityControllerCallback callback) {
+ }
+
+ @Override
+ public void removeCallback(SecurityControllerCallback callback) {
+ }
+ }
+ }
+
+ private static class DraggableTile extends QSTile<QSTile.State>
+ implements DropListener {
+ private String mSpec;
+ private QSTileView mView;
+
+ protected DraggableTile(QSTile.Host host, String tileSpec) {
+ super(host);
+ Log.d(TAG, "Creating tile " + tileSpec);
+ mSpec = tileSpec;
+ }
+
+ @Override
+ public QSTileView createTileView(Context context) {
+ mView = super.createTileView(context);
+ return mView;
+ }
+
+ @Override
+ public boolean supportsDualTargets() {
+ return "wifi".equals(mSpec) || "bt".equals(mSpec);
+ }
+
+ @Override
+ public void setListening(boolean listening) {
+ }
+
+ @Override
+ protected QSTile.State newTileState() {
+ return new QSTile.State();
+ }
+
+ @Override
+ protected void handleClick() {
+ }
+
+ @Override
+ protected void handleUpdateState(QSTile.State state, Object arg) {
+ state.visible = true;
+ state.icon = ResourceIcon.get(getIcon());
+ state.label = getLabel();
+ }
+
+ private String getLabel() {
+ int resource = getLabelResource(mSpec);
+ if (resource != 0) {
+ return mContext.getString(resource);
+ }
+ if (mSpec.startsWith(IntentTile.PREFIX)) {
+ int lastDot = mSpec.lastIndexOf('.');
+ if (lastDot >= 0) {
+ return mSpec.substring(lastDot + 1, mSpec.length() - 1);
+ } else {
+ return mSpec.substring(IntentTile.PREFIX.length(), mSpec.length() - 1);
+ }
+ }
+ return mSpec;
+ }
+
+ private int getIcon() {
+ if (mSpec.equals("wifi")) return R.drawable.ic_qs_wifi_full_3;
+ else if (mSpec.equals("bt")) return R.drawable.ic_qs_bluetooth_connected;
+ else if (mSpec.equals("inversion")) return R.drawable.ic_invert_colors_enable;
+ else if (mSpec.equals("cell")) return R.drawable.ic_qs_signal_full_3;
+ else if (mSpec.equals("airplane")) return R.drawable.ic_signal_airplane_enable;
+ else if (mSpec.equals("dnd")) return R.drawable.ic_qs_dnd_on;
+ else if (mSpec.equals("rotation")) return R.drawable.ic_portrait_from_auto_rotate;
+ else if (mSpec.equals("flashlight")) return R.drawable.ic_signal_flashlight_enable;
+ else if (mSpec.equals("location")) return R.drawable.ic_signal_location_enable;
+ else if (mSpec.equals("cast")) return R.drawable.ic_qs_cast_on;
+ else if (mSpec.equals("hotspot")) return R.drawable.ic_hotspot_enable;
+ return R.drawable.android;
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return 20000;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof DraggableTile) {
+ return mSpec.equals(((DraggableTile) o).mSpec);
+ }
+ return false;
+ }
+
+ @Override
+ public void onDrop(String sourceText) {
+ ((CustomHost) mHost).replace(mSpec, sourceText);
+ }
+
+ }
+
+ private class DragHelper implements OnDragListener {
+
+ private final View mView;
+ private final DropListener mListener;
+
+ public DragHelper(View view, DropListener dropListener) {
+ mView = view;
+ mListener = dropListener;
+ mView.setOnDragListener(this);
+ }
+
+ @Override
+ public boolean onDrag(View v, DragEvent event) {
+ switch (event.getAction()) {
+ case DragEvent.ACTION_DRAG_ENTERED:
+ mView.setBackgroundColor(0x77ffffff);
+ break;
+ case DragEvent.ACTION_DRAG_ENDED:
+ stopDrag();
+ case DragEvent.ACTION_DRAG_EXITED:
+ mView.setBackgroundColor(0x0);
+ break;
+ case DragEvent.ACTION_DROP:
+ stopDrag();
+ String text = event.getClipData().getItemAt(0).getText().toString();
+ mListener.onDrop(text);
+ break;
+ }
+ return true;
+ }
+
+ }
+
+ public interface DropListener {
+ void onDrop(String sourceText);
+ }
+
+ private class DraggableQsPanel extends QSPanel implements OnTouchListener {
+ public DraggableQsPanel(Context context) {
+ super(context);
+ mBrightnessView.setVisibility(View.GONE);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ for (TileRecord r : mRecords) {
+ new DragHelper(r.tileView, (DraggableTile) r.tile);
+ r.tileView.setTag(r.tile);
+ r.tileView.setOnTouchListener(this);
+
+ for (int i = 0; i < r.tileView.getChildCount(); i++) {
+ r.tileView.getChildAt(i).setClickable(false);
+ }
+ }
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ String tileSpec = (String) ((DraggableTile) v.getTag()).mSpec;
+ ClipData data = ClipData.newPlainText(tileSpec, tileSpec);
+ v.startDrag(data, new View.DragShadowBuilder(v), null, 0);
+ onStartDrag();
+ return true;
+ }
+ return false;
+ }
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/StatusBarSwitch.java b/packages/SystemUI/src/com/android/systemui/tuner/StatusBarSwitch.java
new file mode 100644
index 0000000..d4cc56d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tuner/StatusBarSwitch.java
@@ -0,0 +1,69 @@
+/*
+ * 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.tuner;
+
+import android.app.ActivityManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.preference.SwitchPreference;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+
+import com.android.systemui.statusbar.phone.StatusBarIconController;
+import com.android.systemui.tuner.TunerService.Tunable;
+
+import java.util.Set;
+
+public class StatusBarSwitch extends SwitchPreference implements Tunable {
+
+ private Set<String> mBlacklist;
+
+ public StatusBarSwitch(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public void onTuningChanged(String key, String newValue) {
+ if (!StatusBarIconController.ICON_BLACKLIST.equals(key)) {
+ return;
+ }
+ mBlacklist = StatusBarIconController.getIconBlacklist(newValue);
+ setChecked(!mBlacklist.contains(getKey()));
+ }
+
+ @Override
+ protected boolean persistBoolean(boolean value) {
+ if (!value) {
+ // If not enabled add to blacklist.
+ if (!mBlacklist.contains(getKey())) {
+ mBlacklist.add(getKey());
+ setList(mBlacklist);
+ }
+ } else {
+ if (mBlacklist.remove(getKey())) {
+ setList(mBlacklist);
+ }
+ }
+ return true;
+ }
+
+ private void setList(Set<String> blacklist) {
+ ContentResolver contentResolver = getContext().getContentResolver();
+ Settings.Secure.putStringForUser(contentResolver, StatusBarIconController.ICON_BLACKLIST,
+ TextUtils.join(",", blacklist), ActivityManager.getCurrentUser());
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsCallback.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
index deb5670..c84f618 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2011 The Android Open Source Project
+ * 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.
@@ -13,19 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.systemui.tuner;
-package com.android.systemui.recent;
+import android.app.Activity;
+import android.os.Bundle;
-import android.view.View;
+public class TunerActivity extends Activity {
-public interface RecentsCallback {
- static final int SWIPE_LEFT = 0;
- static final int SWIPE_RIGHT = 1;
- static final int SWIPE_UP = 2;
- static final int SWIPE_DOWN = 3;
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ getFragmentManager().beginTransaction().replace(android.R.id.content, new TunerFragment())
+ .commit();
+ }
- void handleOnClick(View selectedView);
- void handleSwipe(View selectedView);
- void handleLongPress(View selectedView, View anchorView, View thumbnailView);
- void dismiss();
}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
new file mode 100644
index 0000000..4a8c2e4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
@@ -0,0 +1,159 @@
+/*
+ * 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.tuner;
+
+import static com.android.systemui.BatteryMeterView.SHOW_PERCENT_SETTING;
+
+import android.app.FragmentTransaction;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceChangeListener;
+import android.preference.Preference.OnPreferenceClickListener;
+import android.preference.PreferenceFragment;
+import android.preference.PreferenceGroup;
+import android.preference.SwitchPreference;
+import android.provider.Settings.System;
+import android.view.MenuItem;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.StatusBarIconController;
+import com.android.systemui.tuner.TunerService.Tunable;
+
+public class TunerFragment extends PreferenceFragment {
+
+ private static final String KEY_QS_TUNER = "qs_tuner";
+ private static final String KEY_DEMO_MODE = "demo_mode";
+ private static final String KEY_BATTERY_PCT = "battery_pct";
+
+ private final SettingObserver mSettingObserver = new SettingObserver();
+
+ private SwitchPreference mBatteryPct;
+
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ addPreferencesFromResource(R.xml.tuner_prefs);
+ getActivity().getActionBar().setDisplayHomeAsUpEnabled(true);
+ setHasOptionsMenu(true);
+
+ findPreference(KEY_QS_TUNER).setOnPreferenceClickListener(new OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ FragmentTransaction ft = getFragmentManager().beginTransaction();
+ ft.replace(android.R.id.content, new QsTuner(), "QsTuner");
+ ft.addToBackStack(null);
+ ft.commit();
+ return true;
+ }
+ });
+ findPreference(KEY_DEMO_MODE).setOnPreferenceClickListener(new OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ FragmentTransaction ft = getFragmentManager().beginTransaction();
+ ft.replace(android.R.id.content, new DemoModeFragment(), "DemoMode");
+ ft.addToBackStack(null);
+ ft.commit();
+ return true;
+ }
+ });
+ mBatteryPct = (SwitchPreference) findPreference(KEY_BATTERY_PCT);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ updateBatteryPct();
+ getContext().getContentResolver().registerContentObserver(
+ System.getUriFor(SHOW_PERCENT_SETTING), false, mSettingObserver);
+
+ registerPrefs(getPreferenceScreen());
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ getContext().getContentResolver().unregisterContentObserver(mSettingObserver);
+
+ unregisterPrefs(getPreferenceScreen());
+ }
+
+ private void registerPrefs(PreferenceGroup group) {
+ TunerService tunerService = TunerService.get(getContext());
+ final int N = group.getPreferenceCount();
+ for (int i = 0; i < N; i++) {
+ Preference pref = group.getPreference(i);
+ if (pref instanceof StatusBarSwitch) {
+ tunerService.addTunable((Tunable) pref, StatusBarIconController.ICON_BLACKLIST);
+ } else if (pref instanceof PreferenceGroup) {
+ registerPrefs((PreferenceGroup) pref);
+ }
+ }
+ }
+
+ private void unregisterPrefs(PreferenceGroup group) {
+ TunerService tunerService = TunerService.get(getContext());
+ final int N = group.getPreferenceCount();
+ for (int i = 0; i < N; i++) {
+ Preference pref = group.getPreference(i);
+ if (pref instanceof Tunable) {
+ tunerService.removeTunable((Tunable) pref);
+ } else if (pref instanceof PreferenceGroup) {
+ registerPrefs((PreferenceGroup) pref);
+ }
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ getActivity().finish();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private void updateBatteryPct() {
+ mBatteryPct.setOnPreferenceChangeListener(null);
+ mBatteryPct.setChecked(System.getInt(getContext().getContentResolver(),
+ SHOW_PERCENT_SETTING, 0) != 0);
+ mBatteryPct.setOnPreferenceChangeListener(mBatteryPctChange);
+ }
+
+ private final class SettingObserver extends ContentObserver {
+ public SettingObserver() {
+ super(new Handler());
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri, int userId) {
+ super.onChange(selfChange, uri, userId);
+ updateBatteryPct();
+ }
+ }
+
+ private final OnPreferenceChangeListener mBatteryPctChange = new OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ final boolean v = (Boolean) newValue;
+ System.putInt(getContext().getContentResolver(), SHOW_PERCENT_SETTING, v ? 1 : 0);
+ return true;
+ }
+ };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
new file mode 100644
index 0000000..de5aaf6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
@@ -0,0 +1,160 @@
+/*
+ * 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.tuner;
+
+import android.app.ActivityManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+import android.util.ArrayMap;
+
+import com.android.systemui.SystemUI;
+import com.android.systemui.SystemUIApplication;
+import com.android.systemui.settings.CurrentUserTracker;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+
+public class TunerService extends SystemUI {
+
+ private final Observer mObserver = new Observer();
+ // Map of Uris we listen on to their settings keys.
+ private final ArrayMap<Uri, String> mListeningUris = new ArrayMap<>();
+ // Map of settings keys to the listener.
+ private final HashMap<String, List<Tunable>> mTunableLookup = new HashMap<>();
+
+ private ContentResolver mContentResolver;
+ private int mCurrentUser;
+ private CurrentUserTracker mUserTracker;
+
+ @Override
+ public void start() {
+ mContentResolver = mContext.getContentResolver();
+ putComponent(TunerService.class, this);
+
+ mCurrentUser = ActivityManager.getCurrentUser();
+ mUserTracker = new CurrentUserTracker(mContext) {
+ @Override
+ public void onUserSwitched(int newUserId) {
+ mCurrentUser = newUserId;
+ reloadAll();
+ reregisterAll();
+ }
+ };
+ mUserTracker.startTracking();
+ }
+
+ public void addTunable(Tunable tunable, String... keys) {
+ for (String key : keys) {
+ addTunable(tunable, key);
+ }
+ }
+
+ private void addTunable(Tunable tunable, String key) {
+ if (!mTunableLookup.containsKey(key)) {
+ mTunableLookup.put(key, new ArrayList<Tunable>());
+ }
+ mTunableLookup.get(key).add(tunable);
+ Uri uri = Settings.Secure.getUriFor(key);
+ if (!mListeningUris.containsKey(uri)) {
+ mListeningUris.put(uri, key);
+ mContentResolver.registerContentObserver(uri, false, mObserver, mCurrentUser);
+ }
+ // Send the first state.
+ String value = Settings.Secure.getStringForUser(mContentResolver, key, mCurrentUser);
+ tunable.onTuningChanged(key, value);
+ }
+
+ public void removeTunable(Tunable tunable) {
+ for (List<Tunable> list : mTunableLookup.values()) {
+ list.remove(tunable);
+ }
+ }
+
+ protected void reregisterAll() {
+ if (mListeningUris.size() == 0) {
+ return;
+ }
+ mContentResolver.unregisterContentObserver(mObserver);
+ for (Uri uri : mListeningUris.keySet()) {
+ mContentResolver.registerContentObserver(uri, false, mObserver, mCurrentUser);
+ }
+ }
+
+ public void reloadSetting(Uri uri) {
+ String key = mListeningUris.get(uri);
+ String value = Settings.Secure.getStringForUser(mContentResolver, key, mCurrentUser);
+ for (Tunable tunable : mTunableLookup.get(key)) {
+ tunable.onTuningChanged(key, value);
+ }
+ }
+
+ private void reloadAll() {
+ for (String key : mTunableLookup.keySet()) {
+ String value = Settings.Secure.getStringForUser(mContentResolver, key,
+ mCurrentUser);
+ for (Tunable tunable : mTunableLookup.get(key)) {
+ tunable.onTuningChanged(key, value);
+ }
+ }
+ }
+
+ // Only used in other processes, such as the tuner.
+ private static TunerService sInstance;
+
+ public static TunerService get(Context context) {
+ SystemUIApplication sysUi = (SystemUIApplication) context.getApplicationContext();
+ TunerService service = sysUi.getComponent(TunerService.class);
+ if (service == null) {
+ // Can't get it as a component, must in the tuner, lets just create one for now.
+ return getStaticService(context);
+ }
+ return service;
+ }
+
+ private static TunerService getStaticService(Context context) {
+ if (sInstance == null) {
+ sInstance = new TunerService();
+ sInstance.mContext = context.getApplicationContext();
+ sInstance.mComponents = new HashMap<>();
+ sInstance.start();
+ }
+ return sInstance;
+ }
+
+ private class Observer extends ContentObserver {
+ public Observer() {
+ super(new Handler(Looper.getMainLooper()));
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri, int userId) {
+ if (userId == ActivityManager.getCurrentUser()) {
+ reloadSetting(uri);
+ }
+ }
+ }
+
+ public interface Tunable {
+ void onTuningChanged(String key, String newValue);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
index f804736..180d918 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010 Google Inc.
+ * 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.
@@ -17,424 +17,652 @@
package com.android.systemui.usb;
import android.app.Notification;
+import android.app.Notification.Action;
import android.app.NotificationManager;
import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import android.content.res.Resources;
-import android.os.Environment;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.MoveCallback;
+import android.os.Bundle;
import android.os.Handler;
-import android.os.HandlerThread;
import android.os.UserHandle;
+import android.os.storage.DiskInfo;
import android.os.storage.StorageEventListener;
import android.os.storage.StorageManager;
-import android.os.storage.StorageVolume;
-import android.provider.Settings;
+import android.os.storage.VolumeInfo;
+import android.os.storage.VolumeRecord;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
import android.util.Log;
+import android.util.SparseArray;
+import com.android.internal.R;
import com.android.systemui.SystemUI;
+import java.util.List;
+
public class StorageNotification extends SystemUI {
private static final String TAG = "StorageNotification";
- private static final boolean DEBUG = false;
-
- private static final boolean POP_UMS_ACTIVITY_ON_CONNECT = true;
-
- /**
- * The notification that is shown when a USB mass storage host
- * is connected.
- * <p>
- * This is lazily created, so use {@link #setUsbStorageNotification()}.
- */
- private Notification mUsbStorageNotification;
-
- /**
- * The notification that is shown when the following media events occur:
- * - Media is being checked
- * - Media is blank (or unknown filesystem)
- * - Media is corrupt
- * - Media is safe to unmount
- * - Media is missing
- * <p>
- * This is lazily created, so use {@link #setMediaStorageNotification()}.
- */
- private Notification mMediaStorageNotification;
- private boolean mUmsAvailable;
+
+ private static final int PUBLIC_ID = 0x53505542; // SPUB
+ private static final int PRIVATE_ID = 0x53505256; // SPRV
+ private static final int DISK_ID = 0x5344534b; // SDSK
+ private static final int MOVE_ID = 0x534d4f56; // SMOV
+
+ private static final String ACTION_SNOOZE_VOLUME = "com.android.systemui.action.SNOOZE_VOLUME";
+ private static final String ACTION_FINISH_WIZARD = "com.android.systemui.action.FINISH_WIZARD";
+
+ // TODO: delay some notifications to avoid bumpy fast operations
+
+ private NotificationManager mNotificationManager;
private StorageManager mStorageManager;
- private Handler mAsyncEventHandler;
+ private static class MoveInfo {
+ public int moveId;
+ public Bundle extras;
+ public String packageName;
+ public String label;
+ public String volumeUuid;
+ }
+
+ private final SparseArray<MoveInfo> mMoves = new SparseArray<>();
- private class StorageNotificationEventListener extends StorageEventListener {
- public void onUsbMassStorageConnectionChanged(final boolean connected) {
- mAsyncEventHandler.post(new Runnable() {
- @Override
- public void run() {
- onUsbMassStorageConnectionChangedAsync(connected);
- }
- });
+ private final StorageEventListener mListener = new StorageEventListener() {
+ @Override
+ public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
+ onVolumeStateChangedInternal(vol);
}
- public void onStorageStateChanged(final String path,
- final String oldState, final String newState) {
- mAsyncEventHandler.post(new Runnable() {
- @Override
- public void run() {
- onStorageStateChangedAsync(path, oldState, newState);
- }
- });
+
+ @Override
+ public void onVolumeRecordChanged(VolumeRecord rec) {
+ // Avoid kicking notifications when getting early metadata before
+ // mounted. If already mounted, we're being kicked because of a
+ // nickname or init'ed change.
+ final VolumeInfo vol = mStorageManager.findVolumeByUuid(rec.getFsUuid());
+ if (vol != null && vol.isMountedReadable()) {
+ onVolumeStateChangedInternal(vol);
+ }
}
- }
+
+ @Override
+ public void onVolumeForgotten(String fsUuid) {
+ // Stop annoying the user
+ mNotificationManager.cancelAsUser(fsUuid, PRIVATE_ID, UserHandle.ALL);
+ }
+
+ @Override
+ public void onDiskScanned(DiskInfo disk, int volumeCount) {
+ onDiskScannedInternal(disk, volumeCount);
+ }
+ };
+
+ private final BroadcastReceiver mSnoozeReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // TODO: kick this onto background thread
+ final String fsUuid = intent.getStringExtra(VolumeRecord.EXTRA_FS_UUID);
+ mStorageManager.setVolumeSnoozed(fsUuid, true);
+ }
+ };
+
+ private final BroadcastReceiver mFinishReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // When finishing the adoption wizard, clean up any notifications
+ // for moving primary storage
+ mNotificationManager.cancelAsUser(null, MOVE_ID, UserHandle.ALL);
+ }
+ };
+
+ private final MoveCallback mMoveCallback = new MoveCallback() {
+ @Override
+ public void onCreated(int moveId, Bundle extras) {
+ final MoveInfo move = new MoveInfo();
+ move.moveId = moveId;
+ move.extras = extras;
+ if (extras != null) {
+ move.packageName = extras.getString(Intent.EXTRA_PACKAGE_NAME);
+ move.label = extras.getString(Intent.EXTRA_TITLE);
+ move.volumeUuid = extras.getString(VolumeRecord.EXTRA_FS_UUID);
+ }
+ mMoves.put(moveId, move);
+ }
+
+ @Override
+ public void onStatusChanged(int moveId, int status, long estMillis) {
+ final MoveInfo move = mMoves.get(moveId);
+ if (move == null) {
+ Log.w(TAG, "Ignoring unknown move " + moveId);
+ return;
+ }
+
+ if (PackageManager.isMoveStatusFinished(status)) {
+ onMoveFinished(move, status);
+ } else {
+ onMoveProgress(move, status, estMillis);
+ }
+ }
+ };
@Override
public void start() {
- mStorageManager = (StorageManager) mContext.getSystemService(Context.STORAGE_SERVICE);
- final boolean connected = mStorageManager.isUsbMassStorageConnected();
- if (DEBUG) Log.d(TAG, String.format( "Startup with UMS connection %s (media state %s)",
- mUmsAvailable, Environment.getExternalStorageState()));
-
- HandlerThread thr = new HandlerThread("SystemUI StorageNotification");
- thr.start();
- mAsyncEventHandler = new Handler(thr.getLooper());
-
- StorageNotificationEventListener listener = new StorageNotificationEventListener();
- listener.onUsbMassStorageConnectionChanged(connected);
- mStorageManager.registerListener(listener);
- }
+ mNotificationManager = mContext.getSystemService(NotificationManager.class);
+
+ mStorageManager = mContext.getSystemService(StorageManager.class);
+ mStorageManager.registerListener(mListener);
+
+ mContext.registerReceiver(mSnoozeReceiver, new IntentFilter(ACTION_SNOOZE_VOLUME),
+ android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null);
+ mContext.registerReceiver(mFinishReceiver, new IntentFilter(ACTION_FINISH_WIZARD),
+ android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null);
- private void onUsbMassStorageConnectionChangedAsync(boolean connected) {
- mUmsAvailable = connected;
- /*
- * Even though we may have a UMS host connected, we the SD card
- * may not be in a state for export.
- */
- String st = Environment.getExternalStorageState();
-
- if (DEBUG) Log.i(TAG, String.format("UMS connection changed to %s (media state %s)",
- connected, st));
-
- if (connected && (st.equals(
- Environment.MEDIA_REMOVED) || st.equals(Environment.MEDIA_CHECKING))) {
- /*
- * No card or card being checked = don't display
- */
- connected = false;
+ // Kick current state into place
+ final List<DiskInfo> disks = mStorageManager.getDisks();
+ for (DiskInfo disk : disks) {
+ onDiskScannedInternal(disk, disk.volumeCount);
}
- updateUsbMassStorageNotification(connected);
+
+ final List<VolumeInfo> vols = mStorageManager.getVolumes();
+ for (VolumeInfo vol : vols) {
+ onVolumeStateChangedInternal(vol);
+ }
+
+ mContext.getPackageManager().registerMoveCallback(mMoveCallback, new Handler());
+
+ updateMissingPrivateVolumes();
}
- private void onStorageStateChangedAsync(String path, String oldState, String newState) {
- if (DEBUG) Log.i(TAG, String.format(
- "Media {%s} state changed from {%s} -> {%s}", path, oldState, newState));
- if (newState.equals(Environment.MEDIA_SHARED)) {
- /*
- * Storage is now shared. Modify the UMS notification
- * for stopping UMS.
- */
- Intent intent = new Intent();
- intent.setClass(mContext, com.android.systemui.usb.UsbStorageActivity.class);
- PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
- setUsbStorageNotification(
- com.android.internal.R.string.usb_storage_stop_notification_title,
- com.android.internal.R.string.usb_storage_stop_notification_message,
- com.android.internal.R.drawable.stat_sys_warning, false, true, pi);
- } else if (newState.equals(Environment.MEDIA_CHECKING)) {
- /*
- * Storage is now checking. Update media notification and disable
- * UMS notification.
- */
- setMediaStorageNotification(
- com.android.internal.R.string.ext_media_checking_notification_title,
- com.android.internal.R.string.ext_media_checking_notification_message,
- com.android.internal.R.drawable.stat_notify_sdcard_prepare, true, false, null);
- updateUsbMassStorageNotification(false);
- } else if (newState.equals(Environment.MEDIA_MOUNTED)) {
- /*
- * Storage is now mounted. Dismiss any media notifications,
- * and enable UMS notification if connected.
- */
- setMediaStorageNotification(0, 0, 0, false, false, null);
- updateUsbMassStorageNotification(mUmsAvailable);
- } else if (newState.equals(Environment.MEDIA_UNMOUNTED)) {
- /*
- * Storage is now unmounted. We may have been unmounted
- * because the user is enabling/disabling UMS, in which case we don't
- * want to display the 'safe to unmount' notification.
- */
- if (!mStorageManager.isUsbMassStorageEnabled()) {
- if (oldState.equals(Environment.MEDIA_SHARED)) {
- /*
- * The unmount was due to UMS being enabled. Dismiss any
- * media notifications, and enable UMS notification if connected
- */
- setMediaStorageNotification(0, 0, 0, false, false, null);
- updateUsbMassStorageNotification(mUmsAvailable);
- } else {
- /*
- * Show safe to unmount media notification, and enable UMS
- * notification if connected.
- */
- if (Environment.isExternalStorageRemovable()) {
- setMediaStorageNotification(
- com.android.internal.R.string.ext_media_safe_unmount_notification_title,
- com.android.internal.R.string.ext_media_safe_unmount_notification_message,
- com.android.internal.R.drawable.stat_notify_sdcard, true, true, null);
- } else {
- // This device does not have removable storage, so
- // don't tell the user they can remove it.
- setMediaStorageNotification(0, 0, 0, false, false, null);
- }
- updateUsbMassStorageNotification(mUmsAvailable);
- }
+ private void updateMissingPrivateVolumes() {
+ final List<VolumeRecord> recs = mStorageManager.getVolumeRecords();
+ for (VolumeRecord rec : recs) {
+ if (rec.getType() != VolumeInfo.TYPE_PRIVATE) continue;
+
+ final String fsUuid = rec.getFsUuid();
+ final VolumeInfo info = mStorageManager.findVolumeByUuid(fsUuid);
+ if ((info != null && info.isMountedWritable()) || rec.isSnoozed()) {
+ // Yay, private volume is here, or user snoozed
+ mNotificationManager.cancelAsUser(fsUuid, PRIVATE_ID, UserHandle.ALL);
+
} else {
- /*
- * The unmount was due to UMS being enabled. Dismiss any
- * media notifications, and disable the UMS notification
- */
- setMediaStorageNotification(0, 0, 0, false, false, null);
- updateUsbMassStorageNotification(false);
+ // Boo, annoy the user to reinsert the private volume
+ final CharSequence title = mContext.getString(R.string.ext_media_missing_title,
+ rec.getNickname());
+ final CharSequence text = mContext.getString(R.string.ext_media_missing_message);
+
+ final Notification notif = new Notification.Builder(mContext)
+ .setSmallIcon(R.drawable.ic_sd_card_48dp)
+ .setColor(mContext.getColor(R.color.system_notification_accent_color))
+ .setContentTitle(title)
+ .setContentText(text)
+ .setContentIntent(buildForgetPendingIntent(rec))
+ .setStyle(new Notification.BigTextStyle().bigText(text))
+ .setVisibility(Notification.VISIBILITY_PUBLIC)
+ .setLocalOnly(true)
+ .setCategory(Notification.CATEGORY_SYSTEM)
+ .setDeleteIntent(buildSnoozeIntent(fsUuid))
+ .build();
+
+ mNotificationManager.notifyAsUser(fsUuid, PRIVATE_ID, notif, UserHandle.ALL);
}
- } else if (newState.equals(Environment.MEDIA_NOFS)) {
- /*
- * Storage has no filesystem. Show blank media notification,
- * and enable UMS notification if connected.
- */
- Intent intent = new Intent();
- intent.setClass(mContext, com.android.internal.app.ExternalMediaFormatActivity.class);
- intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME,
- getVolumeByPath(mStorageManager.getVolumeList(), path));
- PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
-
- setMediaStorageNotification(
- com.android.internal.R.string.ext_media_nofs_notification_title,
- com.android.internal.R.string.ext_media_nofs_notification_message,
- com.android.internal.R.drawable.stat_notify_sdcard_usb, true, false, pi);
- updateUsbMassStorageNotification(mUmsAvailable);
- } else if (newState.equals(Environment.MEDIA_UNMOUNTABLE)) {
- /*
- * Storage is corrupt. Show corrupt media notification,
- * and enable UMS notification if connected.
- */
- Intent intent = new Intent();
- intent.setClass(mContext, com.android.internal.app.ExternalMediaFormatActivity.class);
- intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME,
- getVolumeByPath(mStorageManager.getVolumeList(), path));
- PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
-
- setMediaStorageNotification(
- com.android.internal.R.string.ext_media_unmountable_notification_title,
- com.android.internal.R.string.ext_media_unmountable_notification_message,
- com.android.internal.R.drawable.stat_notify_sdcard_usb, true, false, pi);
- updateUsbMassStorageNotification(mUmsAvailable);
- } else if (newState.equals(Environment.MEDIA_REMOVED)) {
- /*
- * Storage has been removed. Show nomedia media notification,
- * and disable UMS notification regardless of connection state.
- */
- setMediaStorageNotification(
- com.android.internal.R.string.ext_media_nomedia_notification_title,
- com.android.internal.R.string.ext_media_nomedia_notification_message,
- com.android.internal.R.drawable.stat_notify_sdcard_usb,
- true, false, null);
- updateUsbMassStorageNotification(false);
- } else if (newState.equals(Environment.MEDIA_BAD_REMOVAL)) {
- /*
- * Storage has been removed unsafely. Show bad removal media notification,
- * and disable UMS notification regardless of connection state.
- */
- setMediaStorageNotification(
- com.android.internal.R.string.ext_media_badremoval_notification_title,
- com.android.internal.R.string.ext_media_badremoval_notification_message,
- com.android.internal.R.drawable.stat_sys_warning,
- true, true, null);
- updateUsbMassStorageNotification(false);
+ }
+ }
+
+ private void onDiskScannedInternal(DiskInfo disk, int volumeCount) {
+ if (volumeCount == 0 && disk.size > 0) {
+ // No supported volumes found, give user option to format
+ final CharSequence title = mContext.getString(
+ R.string.ext_media_unsupported_notification_title, disk.getDescription());
+ final CharSequence text = mContext.getString(
+ R.string.ext_media_unsupported_notification_message, disk.getDescription());
+
+ final Notification notif = new Notification.Builder(mContext)
+ .setSmallIcon(getSmallIcon(disk, VolumeInfo.STATE_UNMOUNTABLE))
+ .setColor(mContext.getColor(R.color.system_notification_accent_color))
+ .setContentTitle(title)
+ .setContentText(text)
+ .setContentIntent(buildInitPendingIntent(disk))
+ .setStyle(new Notification.BigTextStyle().bigText(text))
+ .setVisibility(Notification.VISIBILITY_PUBLIC)
+ .setLocalOnly(true)
+ .setCategory(Notification.CATEGORY_ERROR)
+ .build();
+
+ mNotificationManager.notifyAsUser(disk.getId(), DISK_ID, notif, UserHandle.ALL);
+
} else {
- Log.w(TAG, String.format("Ignoring unknown state {%s}", newState));
+ // Yay, we have volumes!
+ mNotificationManager.cancelAsUser(disk.getId(), DISK_ID, UserHandle.ALL);
}
}
- /**
- * Get the corresponding StorageVolume object for a specific path.
- */
- private final StorageVolume getVolumeByPath(StorageVolume[] volumes, String path) {
- for (StorageVolume volume : volumes) {
- if (volume.getPath().equals(path)) {
- return volume;
- }
+ private void onVolumeStateChangedInternal(VolumeInfo vol) {
+ switch (vol.getType()) {
+ case VolumeInfo.TYPE_PRIVATE:
+ onPrivateVolumeStateChangedInternal(vol);
+ break;
+ case VolumeInfo.TYPE_PUBLIC:
+ onPublicVolumeStateChangedInternal(vol);
+ break;
}
- Log.w(TAG, "No storage found");
- return null;
}
- /**
- * Update the state of the USB mass storage notification
- */
- void updateUsbMassStorageNotification(boolean available) {
-
- if (available) {
- Intent intent = new Intent();
- intent.setClass(mContext, com.android.systemui.usb.UsbStorageActivity.class);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
- PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
- setUsbStorageNotification(
- com.android.internal.R.string.usb_storage_notification_title,
- com.android.internal.R.string.usb_storage_notification_message,
- com.android.internal.R.drawable.stat_sys_data_usb,
- false, true, pi);
+ private void onPrivateVolumeStateChangedInternal(VolumeInfo vol) {
+ Log.d(TAG, "Notifying about private volume: " + vol.toString());
+
+ updateMissingPrivateVolumes();
+ }
+
+ private void onPublicVolumeStateChangedInternal(VolumeInfo vol) {
+ Log.d(TAG, "Notifying about public volume: " + vol.toString());
+
+ final Notification notif;
+ switch (vol.getState()) {
+ case VolumeInfo.STATE_UNMOUNTED:
+ notif = onVolumeUnmounted(vol);
+ break;
+ case VolumeInfo.STATE_CHECKING:
+ notif = onVolumeChecking(vol);
+ break;
+ case VolumeInfo.STATE_MOUNTED:
+ case VolumeInfo.STATE_MOUNTED_READ_ONLY:
+ notif = onVolumeMounted(vol);
+ break;
+ case VolumeInfo.STATE_FORMATTING:
+ notif = onVolumeFormatting(vol);
+ break;
+ case VolumeInfo.STATE_EJECTING:
+ notif = onVolumeEjecting(vol);
+ break;
+ case VolumeInfo.STATE_UNMOUNTABLE:
+ notif = onVolumeUnmountable(vol);
+ break;
+ case VolumeInfo.STATE_REMOVED:
+ notif = onVolumeRemoved(vol);
+ break;
+ case VolumeInfo.STATE_BAD_REMOVAL:
+ notif = onVolumeBadRemoval(vol);
+ break;
+ default:
+ notif = null;
+ break;
+ }
+
+ if (notif != null) {
+ mNotificationManager.notifyAsUser(vol.getId(), PUBLIC_ID, notif, UserHandle.ALL);
} else {
- setUsbStorageNotification(0, 0, 0, false, false, null);
+ mNotificationManager.cancelAsUser(vol.getId(), PUBLIC_ID, UserHandle.ALL);
}
}
- /**
- * Sets the USB storage notification.
- */
- private synchronized void setUsbStorageNotification(int titleId, int messageId, int icon,
- boolean sound, boolean visible, PendingIntent pi) {
+ private Notification onVolumeUnmounted(VolumeInfo vol) {
+ // Ignored
+ return null;
+ }
- if (!visible && mUsbStorageNotification == null) {
- return;
- }
+ private Notification onVolumeChecking(VolumeInfo vol) {
+ final DiskInfo disk = vol.getDisk();
+ final CharSequence title = mContext.getString(
+ R.string.ext_media_checking_notification_title, disk.getDescription());
+ final CharSequence text = mContext.getString(
+ R.string.ext_media_checking_notification_message, disk.getDescription());
+
+ return buildNotificationBuilder(vol, title, text)
+ .setCategory(Notification.CATEGORY_PROGRESS)
+ .setPriority(Notification.PRIORITY_LOW)
+ .setOngoing(true)
+ .build();
+ }
- NotificationManager notificationManager = (NotificationManager) mContext
- .getSystemService(Context.NOTIFICATION_SERVICE);
+ private Notification onVolumeMounted(VolumeInfo vol) {
+ final VolumeRecord rec = mStorageManager.findRecordByUuid(vol.getFsUuid());
+ final DiskInfo disk = vol.getDisk();
- if (notificationManager == null) {
- return;
+ // Don't annoy when user dismissed in past. (But make sure the disk is adoptable; we
+ // used to allow snoozing non-adoptable disks too.)
+ if (rec.isSnoozed() && disk.isAdoptable()) {
+ return null;
}
- if (visible) {
- Resources r = Resources.getSystem();
- CharSequence title = r.getText(titleId);
- CharSequence message = r.getText(messageId);
+ if (disk.isAdoptable() && !rec.isInited()) {
+ final CharSequence title = disk.getDescription();
+ final CharSequence text = mContext.getString(
+ R.string.ext_media_new_notification_message, disk.getDescription());
+
+ final PendingIntent initIntent = buildInitPendingIntent(vol);
+ return buildNotificationBuilder(vol, title, text)
+ .addAction(new Action(R.drawable.ic_settings_24dp,
+ mContext.getString(R.string.ext_media_init_action), initIntent))
+ .addAction(new Action(R.drawable.ic_eject_24dp,
+ mContext.getString(R.string.ext_media_unmount_action),
+ buildUnmountPendingIntent(vol)))
+ .setContentIntent(initIntent)
+ .setDeleteIntent(buildSnoozeIntent(vol.getFsUuid()))
+ .setCategory(Notification.CATEGORY_SYSTEM)
+ .build();
- if (mUsbStorageNotification == null) {
- mUsbStorageNotification = new Notification();
- mUsbStorageNotification.icon = icon;
- mUsbStorageNotification.when = 0;
+ } else {
+ final CharSequence title = disk.getDescription();
+ final CharSequence text = mContext.getString(
+ R.string.ext_media_ready_notification_message, disk.getDescription());
+
+ final PendingIntent browseIntent = buildBrowsePendingIntent(vol);
+ final Notification.Builder builder = buildNotificationBuilder(vol, title, text)
+ .addAction(new Action(R.drawable.ic_folder_24dp,
+ mContext.getString(R.string.ext_media_browse_action),
+ browseIntent))
+ .addAction(new Action(R.drawable.ic_eject_24dp,
+ mContext.getString(R.string.ext_media_unmount_action),
+ buildUnmountPendingIntent(vol)))
+ .setContentIntent(browseIntent)
+ .setCategory(Notification.CATEGORY_SYSTEM)
+ .setPriority(Notification.PRIORITY_LOW);
+ // Non-adoptable disks can't be snoozed.
+ if (disk.isAdoptable()) {
+ builder.setDeleteIntent(buildSnoozeIntent(vol.getFsUuid()));
}
- if (sound) {
- mUsbStorageNotification.defaults |= Notification.DEFAULT_SOUND;
- } else {
- mUsbStorageNotification.defaults &= ~Notification.DEFAULT_SOUND;
- }
+ return builder.build();
+ }
+ }
- mUsbStorageNotification.flags = Notification.FLAG_ONGOING_EVENT;
+ private Notification onVolumeFormatting(VolumeInfo vol) {
+ // Ignored
+ return null;
+ }
- mUsbStorageNotification.tickerText = title;
- if (pi == null) {
- Intent intent = new Intent();
- pi = PendingIntent.getBroadcastAsUser(mContext, 0, intent, 0,
- UserHandle.CURRENT);
- }
- mUsbStorageNotification.color = mContext.getResources().getColor(
- com.android.internal.R.color.system_notification_accent_color);
- mUsbStorageNotification.setLatestEventInfo(mContext, title, message, pi);
- mUsbStorageNotification.visibility = Notification.VISIBILITY_PUBLIC;
- mUsbStorageNotification.category = Notification.CATEGORY_SYSTEM;
-
- final boolean adbOn = 1 == Settings.Global.getInt(
- mContext.getContentResolver(),
- Settings.Global.ADB_ENABLED,
- 0);
-
- if (POP_UMS_ACTIVITY_ON_CONNECT && !adbOn) {
- // Pop up a full-screen alert to coach the user through enabling UMS. The average
- // user has attached the device to USB either to charge the phone (in which case
- // this is harmless) or transfer files, and in the latter case this alert saves
- // several steps (as well as subtly indicates that you shouldn't mix UMS with other
- // activities on the device).
- //
- // If ADB is enabled, however, we suppress this dialog (under the assumption that a
- // developer (a) knows how to enable UMS, and (b) is probably using USB to install
- // builds or use adb commands.
- mUsbStorageNotification.fullScreenIntent = pi;
- }
- }
+ private Notification onVolumeEjecting(VolumeInfo vol) {
+ final DiskInfo disk = vol.getDisk();
+ final CharSequence title = mContext.getString(
+ R.string.ext_media_unmounting_notification_title, disk.getDescription());
+ final CharSequence text = mContext.getString(
+ R.string.ext_media_unmounting_notification_message, disk.getDescription());
+
+ return buildNotificationBuilder(vol, title, text)
+ .setCategory(Notification.CATEGORY_PROGRESS)
+ .setPriority(Notification.PRIORITY_LOW)
+ .setOngoing(true)
+ .build();
+ }
- final int notificationId = mUsbStorageNotification.icon;
- if (visible) {
- notificationManager.notifyAsUser(null, notificationId, mUsbStorageNotification,
- UserHandle.ALL);
- } else {
- notificationManager.cancelAsUser(null, notificationId, UserHandle.ALL);
+ private Notification onVolumeUnmountable(VolumeInfo vol) {
+ final DiskInfo disk = vol.getDisk();
+ final CharSequence title = mContext.getString(
+ R.string.ext_media_unmountable_notification_title, disk.getDescription());
+ final CharSequence text = mContext.getString(
+ R.string.ext_media_unmountable_notification_message, disk.getDescription());
+
+ return buildNotificationBuilder(vol, title, text)
+ .setContentIntent(buildInitPendingIntent(vol))
+ .setCategory(Notification.CATEGORY_ERROR)
+ .build();
+ }
+
+ private Notification onVolumeRemoved(VolumeInfo vol) {
+ if (!vol.isPrimary()) {
+ // Ignore non-primary media
+ return null;
}
+
+ final DiskInfo disk = vol.getDisk();
+ final CharSequence title = mContext.getString(
+ R.string.ext_media_nomedia_notification_title, disk.getDescription());
+ final CharSequence text = mContext.getString(
+ R.string.ext_media_nomedia_notification_message, disk.getDescription());
+
+ return buildNotificationBuilder(vol, title, text)
+ .setCategory(Notification.CATEGORY_ERROR)
+ .build();
}
- private synchronized boolean getMediaStorageNotificationDismissable() {
- if ((mMediaStorageNotification != null) &&
- ((mMediaStorageNotification.flags & Notification.FLAG_AUTO_CANCEL) ==
- Notification.FLAG_AUTO_CANCEL))
- return true;
+ private Notification onVolumeBadRemoval(VolumeInfo vol) {
+ if (!vol.isPrimary()) {
+ // Ignore non-primary media
+ return null;
+ }
- return false;
+ final DiskInfo disk = vol.getDisk();
+ final CharSequence title = mContext.getString(
+ R.string.ext_media_badremoval_notification_title, disk.getDescription());
+ final CharSequence text = mContext.getString(
+ R.string.ext_media_badremoval_notification_message, disk.getDescription());
+
+ return buildNotificationBuilder(vol, title, text)
+ .setCategory(Notification.CATEGORY_ERROR)
+ .build();
}
- /**
- * Sets the media storage notification.
- */
- private synchronized void setMediaStorageNotification(int titleId, int messageId, int icon, boolean visible,
- boolean dismissable, PendingIntent pi) {
+ private void onMoveProgress(MoveInfo move, int status, long estMillis) {
+ final CharSequence title;
+ if (!TextUtils.isEmpty(move.label)) {
+ title = mContext.getString(R.string.ext_media_move_specific_title, move.label);
+ } else {
+ title = mContext.getString(R.string.ext_media_move_title);
+ }
- if (!visible && mMediaStorageNotification == null) {
- return;
+ final CharSequence text;
+ if (estMillis < 0) {
+ text = null;
+ } else {
+ text = DateUtils.formatDuration(estMillis);
}
- NotificationManager notificationManager = (NotificationManager) mContext
- .getSystemService(Context.NOTIFICATION_SERVICE);
+ final PendingIntent intent;
+ if (move.packageName != null) {
+ intent = buildWizardMovePendingIntent(move);
+ } else {
+ intent = buildWizardMigratePendingIntent(move);
+ }
- if (notificationManager == null) {
+ final Notification notif = new Notification.Builder(mContext)
+ .setSmallIcon(R.drawable.ic_sd_card_48dp)
+ .setColor(mContext.getColor(R.color.system_notification_accent_color))
+ .setContentTitle(title)
+ .setContentText(text)
+ .setContentIntent(intent)
+ .setStyle(new Notification.BigTextStyle().bigText(text))
+ .setVisibility(Notification.VISIBILITY_PUBLIC)
+ .setLocalOnly(true)
+ .setCategory(Notification.CATEGORY_PROGRESS)
+ .setPriority(Notification.PRIORITY_LOW)
+ .setProgress(100, status, false)
+ .setOngoing(true)
+ .build();
+
+ mNotificationManager.notifyAsUser(move.packageName, MOVE_ID, notif, UserHandle.ALL);
+ }
+
+ private void onMoveFinished(MoveInfo move, int status) {
+ if (move.packageName != null) {
+ // We currently ignore finished app moves; just clear the last
+ // published progress
+ mNotificationManager.cancelAsUser(move.packageName, MOVE_ID, UserHandle.ALL);
return;
}
- if (mMediaStorageNotification != null && visible) {
- /*
- * Dismiss the previous notification - we're about to
- * re-use it.
- */
- final int notificationId = mMediaStorageNotification.icon;
- notificationManager.cancel(notificationId);
+ final VolumeInfo privateVol = mContext.getPackageManager().getPrimaryStorageCurrentVolume();
+ final String descrip = mStorageManager.getBestVolumeDescription(privateVol);
+
+ final CharSequence title;
+ final CharSequence text;
+ if (status == PackageManager.MOVE_SUCCEEDED) {
+ title = mContext.getString(R.string.ext_media_move_success_title);
+ text = mContext.getString(R.string.ext_media_move_success_message, descrip);
+ } else {
+ title = mContext.getString(R.string.ext_media_move_failure_title);
+ text = mContext.getString(R.string.ext_media_move_failure_message);
+ }
+
+ // Jump back into the wizard flow if we moved to a real disk
+ final PendingIntent intent;
+ if (privateVol != null && privateVol.getDisk() != null) {
+ intent = buildWizardReadyPendingIntent(privateVol.getDisk());
+ } else if (privateVol != null) {
+ intent = buildVolumeSettingsPendingIntent(privateVol);
+ } else {
+ intent = null;
}
- if (visible) {
- Resources r = Resources.getSystem();
- CharSequence title = r.getText(titleId);
- CharSequence message = r.getText(messageId);
+ final Notification notif = new Notification.Builder(mContext)
+ .setSmallIcon(R.drawable.ic_sd_card_48dp)
+ .setColor(mContext.getColor(R.color.system_notification_accent_color))
+ .setContentTitle(title)
+ .setContentText(text)
+ .setContentIntent(intent)
+ .setStyle(new Notification.BigTextStyle().bigText(text))
+ .setVisibility(Notification.VISIBILITY_PUBLIC)
+ .setLocalOnly(true)
+ .setCategory(Notification.CATEGORY_SYSTEM)
+ .setPriority(Notification.PRIORITY_LOW)
+ .setAutoCancel(true)
+ .build();
+
+ mNotificationManager.notifyAsUser(move.packageName, MOVE_ID, notif, UserHandle.ALL);
+ }
- if (mMediaStorageNotification == null) {
- mMediaStorageNotification = new Notification();
- mMediaStorageNotification.when = 0;
+ private int getSmallIcon(DiskInfo disk, int state) {
+ if (disk.isSd()) {
+ switch (state) {
+ case VolumeInfo.STATE_CHECKING:
+ case VolumeInfo.STATE_EJECTING:
+ return R.drawable.ic_sd_card_48dp;
+ default:
+ return R.drawable.ic_sd_card_48dp;
}
+ } else if (disk.isUsb()) {
+ return R.drawable.ic_usb_48dp;
+ } else {
+ return R.drawable.ic_sd_card_48dp;
+ }
+ }
- mMediaStorageNotification.defaults &= ~Notification.DEFAULT_SOUND;
+ private Notification.Builder buildNotificationBuilder(VolumeInfo vol, CharSequence title,
+ CharSequence text) {
+ return new Notification.Builder(mContext)
+ .setSmallIcon(getSmallIcon(vol.getDisk(), vol.getState()))
+ .setColor(mContext.getColor(R.color.system_notification_accent_color))
+ .setContentTitle(title)
+ .setContentText(text)
+ .setStyle(new Notification.BigTextStyle().bigText(text))
+ .setVisibility(Notification.VISIBILITY_PUBLIC)
+ .setLocalOnly(true);
+ }
- if (dismissable) {
- mMediaStorageNotification.flags = Notification.FLAG_AUTO_CANCEL;
- } else {
- mMediaStorageNotification.flags = Notification.FLAG_ONGOING_EVENT;
- }
+ private PendingIntent buildInitPendingIntent(DiskInfo disk) {
+ final Intent intent = new Intent();
+ intent.setClassName("com.android.settings",
+ "com.android.settings.deviceinfo.StorageWizardInit");
+ intent.putExtra(DiskInfo.EXTRA_DISK_ID, disk.getId());
- mMediaStorageNotification.tickerText = title;
- if (pi == null) {
- Intent intent = new Intent();
- pi = PendingIntent.getBroadcastAsUser(mContext, 0, intent, 0,
- UserHandle.CURRENT);
- }
+ final int requestKey = disk.getId().hashCode();
+ return PendingIntent.getActivityAsUser(mContext, requestKey, intent,
+ PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT);
+ }
- mMediaStorageNotification.icon = icon;
- mMediaStorageNotification.color = mContext.getResources().getColor(
- com.android.internal.R.color.system_notification_accent_color);
- mMediaStorageNotification.setLatestEventInfo(mContext, title, message, pi);
- mMediaStorageNotification.visibility = Notification.VISIBILITY_PUBLIC;
- mMediaStorageNotification.category = Notification.CATEGORY_SYSTEM;
- }
+ private PendingIntent buildInitPendingIntent(VolumeInfo vol) {
+ final Intent intent = new Intent();
+ intent.setClassName("com.android.settings",
+ "com.android.settings.deviceinfo.StorageWizardInit");
+ intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.getId());
- final int notificationId = mMediaStorageNotification.icon;
- if (visible) {
- notificationManager.notifyAsUser(null, notificationId,
- mMediaStorageNotification, UserHandle.ALL);
- } else {
- notificationManager.cancelAsUser(null, notificationId, UserHandle.ALL);
+ final int requestKey = vol.getId().hashCode();
+ return PendingIntent.getActivityAsUser(mContext, requestKey, intent,
+ PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT);
+ }
+
+ private PendingIntent buildUnmountPendingIntent(VolumeInfo vol) {
+ final Intent intent = new Intent();
+ intent.setClassName("com.android.settings",
+ "com.android.settings.deviceinfo.StorageUnmountReceiver");
+ intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.getId());
+
+ final int requestKey = vol.getId().hashCode();
+ return PendingIntent.getBroadcastAsUser(mContext, requestKey, intent,
+ PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.CURRENT);
+ }
+
+ private PendingIntent buildBrowsePendingIntent(VolumeInfo vol) {
+ final Intent intent = vol.buildBrowseIntent();
+
+ final int requestKey = vol.getId().hashCode();
+ return PendingIntent.getActivityAsUser(mContext, requestKey, intent,
+ PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT);
+ }
+
+ private PendingIntent buildVolumeSettingsPendingIntent(VolumeInfo vol) {
+ final Intent intent = new Intent();
+ switch (vol.getType()) {
+ case VolumeInfo.TYPE_PRIVATE:
+ intent.setClassName("com.android.settings",
+ "com.android.settings.Settings$PrivateVolumeSettingsActivity");
+ break;
+ case VolumeInfo.TYPE_PUBLIC:
+ intent.setClassName("com.android.settings",
+ "com.android.settings.Settings$PublicVolumeSettingsActivity");
+ break;
+ default:
+ return null;
}
+ intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.getId());
+
+ final int requestKey = vol.getId().hashCode();
+ return PendingIntent.getActivityAsUser(mContext, requestKey, intent,
+ PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT);
+ }
+
+ private PendingIntent buildSnoozeIntent(String fsUuid) {
+ final Intent intent = new Intent(ACTION_SNOOZE_VOLUME);
+ intent.putExtra(VolumeRecord.EXTRA_FS_UUID, fsUuid);
+
+ final int requestKey = fsUuid.hashCode();
+ return PendingIntent.getBroadcastAsUser(mContext, requestKey, intent,
+ PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.CURRENT);
+ }
+
+ private PendingIntent buildForgetPendingIntent(VolumeRecord rec) {
+ final Intent intent = new Intent();
+ intent.setClassName("com.android.settings",
+ "com.android.settings.Settings$PrivateVolumeForgetActivity");
+ intent.putExtra(VolumeRecord.EXTRA_FS_UUID, rec.getFsUuid());
+
+ final int requestKey = rec.getFsUuid().hashCode();
+ return PendingIntent.getActivityAsUser(mContext, requestKey, intent,
+ PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT);
+ }
+
+ private PendingIntent buildWizardMigratePendingIntent(MoveInfo move) {
+ final Intent intent = new Intent();
+ intent.setClassName("com.android.settings",
+ "com.android.settings.deviceinfo.StorageWizardMigrateProgress");
+ intent.putExtra(PackageManager.EXTRA_MOVE_ID, move.moveId);
+
+ final VolumeInfo vol = mStorageManager.findVolumeByQualifiedUuid(move.volumeUuid);
+ intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.getId());
+
+ return PendingIntent.getActivityAsUser(mContext, move.moveId, intent,
+ PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT);
+ }
+
+ private PendingIntent buildWizardMovePendingIntent(MoveInfo move) {
+ final Intent intent = new Intent();
+ intent.setClassName("com.android.settings",
+ "com.android.settings.deviceinfo.StorageWizardMoveProgress");
+ intent.putExtra(PackageManager.EXTRA_MOVE_ID, move.moveId);
+
+ return PendingIntent.getActivityAsUser(mContext, move.moveId, intent,
+ PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT);
+ }
+
+ private PendingIntent buildWizardReadyPendingIntent(DiskInfo disk) {
+ final Intent intent = new Intent();
+ intent.setClassName("com.android.settings",
+ "com.android.settings.deviceinfo.StorageWizardReady");
+ intent.putExtra(DiskInfo.EXTRA_DISK_ID, disk.getId());
+
+ final int requestKey = disk.getId().hashCode();
+ return PendingIntent.getActivityAsUser(mContext, requestKey, intent,
+ PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingSecondaryUserActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingSecondaryUserActivity.java
new file mode 100644
index 0000000..9ce771b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingSecondaryUserActivity.java
@@ -0,0 +1,96 @@
+/*
+ * 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.usb;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.UserInfo;
+import android.hardware.usb.UsbManager;
+import android.os.Bundle;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import com.android.internal.app.AlertActivity;
+import com.android.internal.app.AlertController;
+import com.android.systemui.R;
+
+public class UsbDebuggingSecondaryUserActivity extends AlertActivity
+ implements DialogInterface.OnClickListener {
+ private UsbDisconnectedReceiver mDisconnectedReceiver;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ if (SystemProperties.getInt("service.adb.tcp.port", 0) == 0) {
+ mDisconnectedReceiver = new UsbDisconnectedReceiver(this);
+ }
+
+ final AlertController.AlertParams ap = mAlertParams;
+ ap.mTitle = getString(R.string.usb_debugging_secondary_user_title);
+ UserInfo user = UserManager.get(this).getUserInfo(UserHandle.USER_OWNER);
+ ap.mMessage = getString(R.string.usb_debugging_secondary_user_message, user.name);
+ ap.mPositiveButtonText = getString(android.R.string.ok);
+ ap.mPositiveButtonListener = this;
+
+ setupAlert();
+ }
+
+ private class UsbDisconnectedReceiver extends BroadcastReceiver {
+ private final Activity mActivity;
+ public UsbDisconnectedReceiver(Activity activity) {
+ mActivity = activity;
+ }
+
+ @Override
+ public void onReceive(Context content, Intent intent) {
+ String action = intent.getAction();
+ if (UsbManager.ACTION_USB_STATE.equals(action)) {
+ boolean connected = intent.getBooleanExtra(UsbManager.USB_CONNECTED, false);
+ if (!connected) {
+ mActivity.finish();
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+
+ IntentFilter filter = new IntentFilter(UsbManager.ACTION_USB_STATE);
+ registerReceiver(mDisconnectedReceiver, filter);
+ }
+
+ @Override
+ protected void onStop() {
+ if (mDisconnectedReceiver != null) {
+ unregisterReceiver(mDisconnectedReceiver);
+ }
+ super.onStop();
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ finish();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java
index 9928f7f..ca32567 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java
@@ -93,7 +93,8 @@ public class UsbResolverActivity extends ResolverActivity {
}
@Override
- protected void onIntentSelected(ResolveInfo ri, Intent intent, boolean alwaysCheck) {
+ protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) {
+ final ResolveInfo ri = target.getResolveInfo();
try {
IBinder b = ServiceManager.getService(USB_SERVICE);
IUsbManager service = IUsbManager.Stub.asInterface(b);
@@ -121,12 +122,13 @@ public class UsbResolverActivity extends ResolverActivity {
}
try {
- startActivityAsUser(intent, new UserHandle(userId));
+ target.startAsUser(this, null, new UserHandle(userId));
} catch (ActivityNotFoundException e) {
Log.e(TAG, "startActivity failed", e);
}
} catch (RemoteException e) {
Log.e(TAG, "onIntentSelected failed", e);
}
+ return true;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPanel.java b/packages/SystemUI/src/com/android/systemui/volume/D.java
index 272c321..db7c853 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/D.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010 The Android Open Source Project
+ * 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.
@@ -14,8 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar;
+package com.android.systemui.volume;
-public interface StatusBarPanel {
- public boolean isInContentArea(int x, int y);
+import android.util.Log;
+
+class D {
+ public static boolean BUG = Log.isLoggable("volume", Log.DEBUG);
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/Events.java b/packages/SystemUI/src/com/android/systemui/volume/Events.java
new file mode 100644
index 0000000..893c939
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/Events.java
@@ -0,0 +1,217 @@
+/*
+ * 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.content.Context;
+import android.media.AudioManager;
+import android.media.AudioSystem;
+import android.provider.Settings.Global;
+import android.util.Log;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.volume.VolumeDialogController.State;
+
+import java.util.Arrays;
+
+/**
+ * Interesting events related to the volume.
+ */
+public class Events {
+ private static final String TAG = Util.logTag(Events.class);
+
+ public static final int EVENT_SHOW_DIALOG = 0; // (reason|int) (keyguard|bool)
+ public static final int EVENT_DISMISS_DIALOG = 1; // (reason|int)
+ public static final int EVENT_ACTIVE_STREAM_CHANGED = 2; // (stream|int)
+ public static final int EVENT_EXPAND = 3; // (expand|bool)
+ public static final int EVENT_KEY = 4;
+ public static final int EVENT_COLLECTION_STARTED = 5;
+ public static final int EVENT_COLLECTION_STOPPED = 6;
+ public static final int EVENT_ICON_CLICK = 7; // (stream|int) (icon_state|int)
+ public static final int EVENT_SETTINGS_CLICK = 8;
+ public static final int EVENT_TOUCH_LEVEL_CHANGED = 9; // (stream|int) (level|int)
+ public static final int EVENT_LEVEL_CHANGED = 10; // (stream|int) (level|int)
+ public static final int EVENT_INTERNAL_RINGER_MODE_CHANGED = 11; // (mode|int)
+ public static final int EVENT_EXTERNAL_RINGER_MODE_CHANGED = 12; // (mode|int)
+ public static final int EVENT_ZEN_MODE_CHANGED = 13; // (mode|int)
+ 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)
+
+ private static final String[] EVENT_TAGS = {
+ "show_dialog",
+ "dismiss_dialog",
+ "active_stream_changed",
+ "expand",
+ "key",
+ "collection_started",
+ "collection_stopped",
+ "icon_click",
+ "settings_click",
+ "touch_level_changed",
+ "level_changed",
+ "internal_ringer_mode_changed",
+ "external_ringer_mode_changed",
+ "zen_mode_changed",
+ "suppressor_changed",
+ "mute_changed",
+ "touch_level_done",
+ };
+
+ public static final int DISMISS_REASON_UNKNOWN = 0;
+ public static final int DISMISS_REASON_TOUCH_OUTSIDE = 1;
+ public static final int DISMISS_REASON_VOLUME_CONTROLLER = 2;
+ public static final int DISMISS_REASON_TIMEOUT = 3;
+ public static final int DISMISS_REASON_SCREEN_OFF = 4;
+ public static final int DISMISS_REASON_SETTINGS_CLICKED = 5;
+ public static final int DISMISS_REASON_DONE_CLICKED = 6;
+ public static final String[] DISMISS_REASONS = {
+ "unknown",
+ "touch_outside",
+ "volume_controller",
+ "timeout",
+ "screen_off",
+ "settings_clicked",
+ "done_clicked",
+ };
+
+ public static final int SHOW_REASON_UNKNOWN = 0;
+ public static final int SHOW_REASON_VOLUME_CHANGED = 1;
+ public static final int SHOW_REASON_REMOTE_VOLUME_CHANGED = 2;
+ public static final String[] SHOW_REASONS = {
+ "unknown",
+ "volume_changed",
+ "remote_volume_changed"
+ };
+
+ public static final int ICON_STATE_UNKNOWN = 0;
+ public static final int ICON_STATE_UNMUTE = 1;
+ public static final int ICON_STATE_MUTE = 2;
+ public static final int ICON_STATE_VIBRATE = 3;
+
+ public static Callback sCallback;
+
+ public static void writeEvent(Context context, int tag, Object... list) {
+ final long time = System.currentTimeMillis();
+ final StringBuilder sb = new StringBuilder("writeEvent ").append(EVENT_TAGS[tag]);
+ if (list != null && list.length > 0) {
+ sb.append(" ");
+ switch (tag) {
+ case EVENT_SHOW_DIALOG:
+ MetricsLogger.visible(context, MetricsLogger.VOLUME_DIALOG);
+ MetricsLogger.histogram(context, "volume_from_keyguard",
+ (Boolean) list[1] ? 1 : 0);
+ sb.append(SHOW_REASONS[(Integer) list[0]]).append(" keyguard=").append(list[1]);
+ break;
+ case EVENT_EXPAND:
+ MetricsLogger.visibility(context, MetricsLogger.VOLUME_DIALOG_DETAILS,
+ (Boolean) list[0]);
+ sb.append(list[0]);
+ break;
+ case EVENT_DISMISS_DIALOG:
+ MetricsLogger.hidden(context, MetricsLogger.VOLUME_DIALOG);
+ sb.append(DISMISS_REASONS[(Integer) list[0]]);
+ break;
+ case EVENT_ACTIVE_STREAM_CHANGED:
+ MetricsLogger.action(context, MetricsLogger.ACTION_VOLUME_STREAM,
+ (Integer) list[0]);
+ sb.append(AudioSystem.streamToString((Integer) list[0]));
+ break;
+ case EVENT_ICON_CLICK:
+ MetricsLogger.action(context, MetricsLogger.ACTION_VOLUME_ICON,
+ (Integer) list[1]);
+ sb.append(AudioSystem.streamToString((Integer) list[0])).append(' ')
+ .append(iconStateToString((Integer) list[1]));
+ break;
+ case EVENT_TOUCH_LEVEL_DONE:
+ MetricsLogger.action(context, MetricsLogger.ACTION_VOLUME_SLIDER,
+ (Integer) list[1]);
+ // fall through
+ case EVENT_TOUCH_LEVEL_CHANGED:
+ case EVENT_LEVEL_CHANGED:
+ case EVENT_MUTE_CHANGED:
+ sb.append(AudioSystem.streamToString((Integer) list[0])).append(' ')
+ .append(list[1]);
+ break;
+ case EVENT_KEY:
+ MetricsLogger.action(context, MetricsLogger.ACTION_VOLUME_KEY,
+ (Integer) list[1]);
+ sb.append(AudioSystem.streamToString((Integer) list[0])).append(' ')
+ .append(list[1]);
+ break;
+ case EVENT_EXTERNAL_RINGER_MODE_CHANGED:
+ MetricsLogger.action(context, MetricsLogger.ACTION_RINGER_MODE,
+ (Integer) list[0]);
+ // fall through
+ case EVENT_INTERNAL_RINGER_MODE_CHANGED:
+ sb.append(ringerModeToString((Integer) list[0]));
+ break;
+ case EVENT_ZEN_MODE_CHANGED:
+ sb.append(zenModeToString((Integer) list[0]));
+ break;
+ case EVENT_SUPPRESSOR_CHANGED:
+ sb.append(list[0]).append(' ').append(list[1]);
+ break;
+ default:
+ sb.append(Arrays.asList(list));
+ break;
+ }
+ }
+ Log.i(TAG, sb.toString());
+ if (sCallback != null) {
+ sCallback.writeEvent(time, tag, list);
+ }
+ }
+
+ public static void writeState(long time, State state) {
+ if (sCallback != null) {
+ sCallback.writeState(time, state);
+ }
+ }
+
+ private static String iconStateToString(int iconState) {
+ switch (iconState) {
+ case ICON_STATE_UNMUTE: return "unmute";
+ case ICON_STATE_MUTE: return "mute";
+ case ICON_STATE_VIBRATE: return "vibrate";
+ default: return "unknown_state_" + iconState;
+ }
+ }
+
+ private static String ringerModeToString(int ringerMode) {
+ switch (ringerMode) {
+ case AudioManager.RINGER_MODE_SILENT: return "silent";
+ case AudioManager.RINGER_MODE_VIBRATE: return "vibrate";
+ case AudioManager.RINGER_MODE_NORMAL: return "normal";
+ default: return "unknown";
+ }
+ }
+
+ private static String zenModeToString(int zenMode) {
+ switch (zenMode) {
+ case Global.ZEN_MODE_OFF: return "off";
+ case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: return "important_interruptions";
+ case Global.ZEN_MODE_ALARMS: return "alarms";
+ case Global.ZEN_MODE_NO_INTERRUPTIONS: return "no_interruptions";
+ default: return "unknown";
+ }
+ }
+
+ public interface Callback {
+ void writeEvent(long time, int tag, Object[] list);
+ void writeState(long time, State state);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/MediaSessions.java b/packages/SystemUI/src/com/android/systemui/volume/MediaSessions.java
new file mode 100644
index 0000000..712ea27
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/MediaSessions.java
@@ -0,0 +1,378 @@
+/*
+ * 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.app.PendingIntent;
+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.content.pm.ResolveInfo;
+import android.media.IRemoteVolumeController;
+import android.media.MediaMetadata;
+import android.media.session.ISessionController;
+import android.media.session.MediaController;
+import android.media.session.MediaController.PlaybackInfo;
+import android.media.session.MediaSession.QueueItem;
+import android.media.session.MediaSession.Token;
+import android.media.session.MediaSessionManager;
+import android.media.session.MediaSessionManager.OnActiveSessionsChangedListener;
+import android.media.session.PlaybackState;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Convenience client for all media session updates. Provides a callback interface for events
+ * related to remote media sessions.
+ */
+public class MediaSessions {
+ private static final String TAG = Util.logTag(MediaSessions.class);
+
+ private static final boolean USE_SERVICE_LABEL = false;
+
+ private final Context mContext;
+ private final H mHandler;
+ private final MediaSessionManager mMgr;
+ private final Map<Token, MediaControllerRecord> mRecords = new HashMap<>();
+ private final Callbacks mCallbacks;
+
+ private boolean mInit;
+
+ public MediaSessions(Context context, Looper looper, Callbacks callbacks) {
+ mContext = context;
+ mHandler = new H(looper);
+ mMgr = (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
+ mCallbacks = callbacks;
+ }
+
+ public void dump(PrintWriter writer) {
+ writer.println(getClass().getSimpleName() + " state:");
+ writer.print(" mInit: "); writer.println(mInit);
+ writer.print(" mRecords.size: "); writer.println(mRecords.size());
+ int i = 0;
+ for (MediaControllerRecord r : mRecords.values()) {
+ dump(++i, writer, r.controller);
+ }
+ }
+
+ public void init() {
+ if (D.BUG) Log.d(TAG, "init");
+ // will throw if no permission
+ mMgr.addOnActiveSessionsChangedListener(mSessionsListener, null, mHandler);
+ mInit = true;
+ postUpdateSessions();
+ mMgr.setRemoteVolumeController(mRvc);
+ }
+
+ protected void postUpdateSessions() {
+ if (!mInit) return;
+ mHandler.sendEmptyMessage(H.UPDATE_SESSIONS);
+ }
+
+ public void destroy() {
+ if (D.BUG) Log.d(TAG, "destroy");
+ mInit = false;
+ mMgr.removeOnActiveSessionsChangedListener(mSessionsListener);
+ }
+
+ public void setVolume(Token token, int level) {
+ final MediaControllerRecord r = mRecords.get(token);
+ if (r == null) {
+ Log.w(TAG, "setVolume: No record found for token " + token);
+ return;
+ }
+ if (D.BUG) Log.d(TAG, "Setting level to " + level);
+ r.controller.setVolumeTo(level, 0);
+ }
+
+ private void onRemoteVolumeChangedH(ISessionController session, int flags) {
+ final MediaController controller = new MediaController(mContext, session);
+ if (D.BUG) Log.d(TAG, "remoteVolumeChangedH " + controller.getPackageName() + " "
+ + Util.audioManagerFlagsToString(flags));
+ final Token token = controller.getSessionToken();
+ mCallbacks.onRemoteVolumeChanged(token, flags);
+ }
+
+ private void onUpdateRemoteControllerH(ISessionController session) {
+ final MediaController controller = session != null ? new MediaController(mContext, session)
+ : null;
+ final String pkg = controller != null ? controller.getPackageName() : null;
+ if (D.BUG) Log.d(TAG, "updateRemoteControllerH " + pkg);
+ // this may be our only indication that a remote session is changed, refresh
+ postUpdateSessions();
+ }
+
+ protected void onActiveSessionsUpdatedH(List<MediaController> controllers) {
+ if (D.BUG) Log.d(TAG, "onActiveSessionsUpdatedH n=" + controllers.size());
+ final Set<Token> toRemove = new HashSet<Token>(mRecords.keySet());
+ for (MediaController controller : controllers) {
+ final Token token = controller.getSessionToken();
+ final PlaybackInfo pi = controller.getPlaybackInfo();
+ toRemove.remove(token);
+ if (!mRecords.containsKey(token)) {
+ final MediaControllerRecord r = new MediaControllerRecord(controller);
+ r.name = getControllerName(controller);
+ mRecords.put(token, r);
+ controller.registerCallback(r, mHandler);
+ }
+ final MediaControllerRecord r = mRecords.get(token);
+ final boolean remote = isRemote(pi);
+ if (remote) {
+ updateRemoteH(token, r.name, pi);
+ r.sentRemote = true;
+ }
+ }
+ for (Token t : toRemove) {
+ final MediaControllerRecord r = mRecords.get(t);
+ r.controller.unregisterCallback(r);
+ mRecords.remove(t);
+ if (D.BUG) Log.d(TAG, "Removing " + r.name + " sentRemote=" + r.sentRemote);
+ if (r.sentRemote) {
+ mCallbacks.onRemoteRemoved(t);
+ r.sentRemote = false;
+ }
+ }
+ }
+
+ private static boolean isRemote(PlaybackInfo pi) {
+ return pi != null && pi.getPlaybackType() == PlaybackInfo.PLAYBACK_TYPE_REMOTE;
+ }
+
+ protected String getControllerName(MediaController controller) {
+ final PackageManager pm = mContext.getPackageManager();
+ final String pkg = controller.getPackageName();
+ try {
+ if (USE_SERVICE_LABEL) {
+ final List<ResolveInfo> ris = pm.queryIntentServices(
+ new Intent("android.media.MediaRouteProviderService").setPackage(pkg), 0);
+ if (ris != null) {
+ for (ResolveInfo ri : ris) {
+ if (ri.serviceInfo == null) continue;
+ if (pkg.equals(ri.serviceInfo.packageName)) {
+ final String serviceLabel =
+ Objects.toString(ri.serviceInfo.loadLabel(pm), "").trim();
+ if (serviceLabel.length() > 0) {
+ return serviceLabel;
+ }
+ }
+ }
+ }
+ }
+ final ApplicationInfo ai = pm.getApplicationInfo(pkg, 0);
+ final String appLabel = Objects.toString(ai.loadLabel(pm), "").trim();
+ if (appLabel.length() > 0) {
+ return appLabel;
+ }
+ } catch (NameNotFoundException e) { }
+ return pkg;
+ }
+
+ private void updateRemoteH(Token token, String name, PlaybackInfo pi) {
+ if (mCallbacks != null) {
+ mCallbacks.onRemoteUpdate(token, name, pi);
+ }
+ }
+
+ private static void dump(int n, PrintWriter writer, MediaController c) {
+ writer.println(" Controller " + n + ": " + c.getPackageName());
+ final Bundle extras = c.getExtras();
+ final long flags = c.getFlags();
+ final MediaMetadata mm = c.getMetadata();
+ final PlaybackInfo pi = c.getPlaybackInfo();
+ final PlaybackState playbackState = c.getPlaybackState();
+ final List<QueueItem> queue = c.getQueue();
+ final CharSequence queueTitle = c.getQueueTitle();
+ final int ratingType = c.getRatingType();
+ final PendingIntent sessionActivity = c.getSessionActivity();
+
+ writer.println(" PlaybackState: " + Util.playbackStateToString(playbackState));
+ writer.println(" PlaybackInfo: " + Util.playbackInfoToString(pi));
+ if (mm != null) {
+ writer.println(" MediaMetadata.desc=" + mm.getDescription());
+ }
+ writer.println(" RatingType: " + ratingType);
+ writer.println(" Flags: " + flags);
+ if (extras != null) {
+ writer.println(" Extras:");
+ for (String key : extras.keySet()) {
+ writer.println(" " + key + "=" + extras.get(key));
+ }
+ }
+ if (queueTitle != null) {
+ writer.println(" QueueTitle: " + queueTitle);
+ }
+ if (queue != null && !queue.isEmpty()) {
+ writer.println(" Queue:");
+ for (QueueItem qi : queue) {
+ writer.println(" " + qi);
+ }
+ }
+ if (pi != null) {
+ writer.println(" sessionActivity: " + sessionActivity);
+ }
+ }
+
+ public static void dumpMediaSessions(Context context) {
+ final MediaSessionManager mgr = (MediaSessionManager) context
+ .getSystemService(Context.MEDIA_SESSION_SERVICE);
+ try {
+ final List<MediaController> controllers = mgr.getActiveSessions(null);
+ final int N = controllers.size();
+ if (D.BUG) Log.d(TAG, N + " controllers");
+ for (int i = 0; i < N; i++) {
+ final StringWriter sw = new StringWriter();
+ final PrintWriter pw = new PrintWriter(sw, true);
+ dump(i + 1, pw, controllers.get(i));
+ if (D.BUG) Log.d(TAG, sw.toString());
+ }
+ } catch (SecurityException e) {
+ Log.w(TAG, "Not allowed to get sessions", e);
+ }
+ }
+
+ private final class MediaControllerRecord extends MediaController.Callback {
+ private final MediaController controller;
+
+ private boolean sentRemote;
+ private String name;
+
+ private MediaControllerRecord(MediaController controller) {
+ this.controller = controller;
+ }
+
+ private String cb(String method) {
+ return method + " " + controller.getPackageName() + " ";
+ }
+
+ @Override
+ public void onAudioInfoChanged(PlaybackInfo info) {
+ if (D.BUG) Log.d(TAG, cb("onAudioInfoChanged") + Util.playbackInfoToString(info)
+ + " sentRemote=" + sentRemote);
+ final boolean remote = isRemote(info);
+ if (!remote && sentRemote) {
+ mCallbacks.onRemoteRemoved(controller.getSessionToken());
+ sentRemote = false;
+ } else if (remote) {
+ updateRemoteH(controller.getSessionToken(), name, info);
+ sentRemote = true;
+ }
+ }
+
+ @Override
+ public void onExtrasChanged(Bundle extras) {
+ if (D.BUG) Log.d(TAG, cb("onExtrasChanged") + extras);
+ }
+
+ @Override
+ public void onMetadataChanged(MediaMetadata metadata) {
+ if (D.BUG) Log.d(TAG, cb("onMetadataChanged") + Util.mediaMetadataToString(metadata));
+ }
+
+ @Override
+ public void onPlaybackStateChanged(PlaybackState state) {
+ if (D.BUG) Log.d(TAG, cb("onPlaybackStateChanged") + Util.playbackStateToString(state));
+ }
+
+ @Override
+ public void onQueueChanged(List<QueueItem> queue) {
+ if (D.BUG) Log.d(TAG, cb("onQueueChanged") + queue);
+ }
+
+ @Override
+ public void onQueueTitleChanged(CharSequence title) {
+ if (D.BUG) Log.d(TAG, cb("onQueueTitleChanged") + title);
+ }
+
+ @Override
+ public void onSessionDestroyed() {
+ if (D.BUG) Log.d(TAG, cb("onSessionDestroyed"));
+ }
+
+ @Override
+ public void onSessionEvent(String event, Bundle extras) {
+ if (D.BUG) Log.d(TAG, cb("onSessionEvent") + "event=" + event + " extras=" + extras);
+ }
+ }
+
+ private final OnActiveSessionsChangedListener mSessionsListener =
+ new OnActiveSessionsChangedListener() {
+ @Override
+ public void onActiveSessionsChanged(List<MediaController> controllers) {
+ onActiveSessionsUpdatedH(controllers);
+ }
+ };
+
+ private final IRemoteVolumeController mRvc = new IRemoteVolumeController.Stub() {
+ @Override
+ public void remoteVolumeChanged(ISessionController session, int flags)
+ throws RemoteException {
+ mHandler.obtainMessage(H.REMOTE_VOLUME_CHANGED, flags, 0, session).sendToTarget();
+ }
+
+ @Override
+ public void updateRemoteController(final ISessionController session)
+ throws RemoteException {
+ mHandler.obtainMessage(H.UPDATE_REMOTE_CONTROLLER, session).sendToTarget();
+ }
+ };
+
+ private final class H extends Handler {
+ private static final int UPDATE_SESSIONS = 1;
+ private static final int REMOTE_VOLUME_CHANGED = 2;
+ private static final int UPDATE_REMOTE_CONTROLLER = 3;
+
+ private H(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case UPDATE_SESSIONS:
+ onActiveSessionsUpdatedH(mMgr.getActiveSessions(null));
+ break;
+ case REMOTE_VOLUME_CHANGED:
+ onRemoteVolumeChangedH((ISessionController) msg.obj, msg.arg1);
+ break;
+ case UPDATE_REMOTE_CONTROLLER:
+ onUpdateRemoteControllerH((ISessionController) msg.obj);
+ break;
+ }
+ }
+ }
+
+ public interface Callbacks {
+ void onRemoteUpdate(Token token, String name, PlaybackInfo pi);
+ void onRemoteRemoved(Token t);
+ void onRemoteVolumeChanged(Token token, int flags);
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/SafetyWarningDialog.java b/packages/SystemUI/src/com/android/systemui/volume/SafetyWarningDialog.java
new file mode 100644
index 0000000..04640a2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/SafetyWarningDialog.java
@@ -0,0 +1,109 @@
+/*
+ * 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.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.AudioManager;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.WindowManager;
+
+import com.android.systemui.statusbar.phone.SystemUIDialog;
+
+abstract public class SafetyWarningDialog extends SystemUIDialog
+ implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener {
+
+ private static final String TAG = Util.logTag(SafetyWarningDialog.class);
+
+ private static final int KEY_CONFIRM_ALLOWED_AFTER = 1000; // milliseconds
+
+ private final Context mContext;
+ private final AudioManager mAudioManager;
+
+ private long mShowTime;
+ private boolean mNewVolumeUp;
+
+ public SafetyWarningDialog(Context context, AudioManager audioManager) {
+ super(context);
+ mContext = context;
+ mAudioManager = audioManager;
+
+ getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
+ setMessage(mContext.getString(com.android.internal.R.string.safe_media_volume_warning));
+ setButton(DialogInterface.BUTTON_POSITIVE,
+ mContext.getString(com.android.internal.R.string.yes), this);
+ setButton(DialogInterface.BUTTON_NEGATIVE,
+ mContext.getString(com.android.internal.R.string.no), (OnClickListener) null);
+ setOnDismissListener(this);
+
+ final IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+ context.registerReceiver(mReceiver, filter);
+ }
+
+ abstract protected void cleanUp();
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_VOLUME_UP && event.getRepeatCount() == 0) {
+ mNewVolumeUp = true;
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_VOLUME_UP && mNewVolumeUp
+ && (System.currentTimeMillis() - mShowTime) > KEY_CONFIRM_ALLOWED_AFTER) {
+ if (D.BUG) Log.d(TAG, "Confirmed warning via VOLUME_UP");
+ mAudioManager.disableSafeMediaVolume();
+ dismiss();
+ }
+ return super.onKeyUp(keyCode, event);
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ mAudioManager.disableSafeMediaVolume();
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ mShowTime = System.currentTimeMillis();
+ }
+
+ @Override
+ public void onDismiss(DialogInterface unused) {
+ mContext.unregisterReceiver(mReceiver);
+ cleanUp();
+ }
+
+ 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();
+ }
+ }
+ };
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/volume/SegmentedButtons.java b/packages/SystemUI/src/com/android/systemui/volume/SegmentedButtons.java
index 2f02f7c..f432808 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/SegmentedButtons.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/SegmentedButtons.java
@@ -17,6 +17,7 @@
package com.android.systemui.volume;
import android.content.Context;
+import android.graphics.Typeface;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@@ -30,9 +31,12 @@ import java.util.Objects;
public class SegmentedButtons extends LinearLayout {
private static final int LABEL_RES_KEY = R.id.label;
+ private static final Typeface REGULAR = Typeface.create("sans-serif", Typeface.NORMAL);
+ private static final Typeface MEDIUM = Typeface.create("sans-serif-medium", Typeface.NORMAL);
private final Context mContext;
private final LayoutInflater mInflater;
+ private final SpTexts mSpTexts;
private Callback mCallback;
private Object mSelectedValue;
@@ -42,6 +46,7 @@ public class SegmentedButtons extends LinearLayout {
mContext = context;
mInflater = LayoutInflater.from(mContext);
setOrientation(HORIZONTAL);
+ mSpTexts = new SpTexts(mContext);
}
public void setCallback(Callback callback) {
@@ -52,7 +57,7 @@ public class SegmentedButtons extends LinearLayout {
return mSelectedValue;
}
- public void setSelectedValue(Object value) {
+ public void setSelectedValue(Object value, boolean fromClick) {
if (Objects.equals(value, mSelectedValue)) return;
mSelectedValue = value;
for (int i = 0; i < getChildCount(); i++) {
@@ -60,17 +65,16 @@ public class SegmentedButtons extends LinearLayout {
final Object tag = c.getTag();
final boolean selected = Objects.equals(mSelectedValue, tag);
c.setSelected(selected);
- c.getCompoundDrawables()[1].setTint(mContext.getResources().getColor(selected
- ? R.color.segmented_button_selected : R.color.segmented_button_unselected));
+ c.setTypeface(selected ? MEDIUM : REGULAR);
}
- fireOnSelected();
+ fireOnSelected(fromClick);
}
- public void addButton(int labelResId, int iconResId, Object value) {
+ public void addButton(int labelResId, int contentDescriptionResId, Object value) {
final Button b = (Button) mInflater.inflate(R.layout.segmented_button, this, false);
b.setTag(LABEL_RES_KEY, labelResId);
b.setText(labelResId);
- b.setCompoundDrawablesWithIntrinsicBounds(0, iconResId, 0, 0);
+ b.setContentDescription(getResources().getString(contentDescriptionResId));
final LayoutParams lp = (LayoutParams) b.getLayoutParams();
if (getChildCount() == 0) {
lp.leftMargin = lp.rightMargin = 0; // first button has no margin
@@ -85,6 +89,7 @@ public class SegmentedButtons extends LinearLayout {
fireInteraction();
}
});
+ mSpTexts.add(b);
}
public void updateLocale() {
@@ -95,9 +100,9 @@ public class SegmentedButtons extends LinearLayout {
}
}
- private void fireOnSelected() {
+ private void fireOnSelected(boolean fromClick) {
if (mCallback != null) {
- mCallback.onSelected(mSelectedValue);
+ mCallback.onSelected(mSelectedValue, fromClick);
}
}
@@ -110,11 +115,11 @@ public class SegmentedButtons extends LinearLayout {
private final View.OnClickListener mClick = new View.OnClickListener() {
@Override
public void onClick(View v) {
- setSelectedValue(v.getTag());
+ setSelectedValue(v.getTag(), true /* fromClick */);
}
};
public interface Callback extends Interaction.Callback {
- void onSelected(Object value);
+ void onSelected(Object value, boolean fromClick);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/SpTexts.java b/packages/SystemUI/src/com/android/systemui/volume/SpTexts.java
new file mode 100644
index 0000000..d8e53db
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/SpTexts.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.content.Context;
+import android.content.res.Resources;
+import android.util.ArrayMap;
+import android.util.TypedValue;
+import android.view.View;
+import android.view.View.OnAttachStateChangeListener;
+import android.widget.TextView;
+
+/**
+ * Capture initial sp values for registered textviews, and update properly when configuration
+ * changes.
+ */
+public class SpTexts {
+
+ private final Context mContext;
+ private final ArrayMap<TextView, Integer> mTexts = new ArrayMap<>();
+
+ public SpTexts(Context context) {
+ mContext = context;
+ }
+
+ public int add(final TextView text) {
+ if (text == null) return 0;
+ final Resources res = mContext.getResources();
+ final float fontScale = res.getConfiguration().fontScale;
+ final float density = res.getDisplayMetrics().density;
+ final float px = text.getTextSize();
+ final int sp = (int)(px / fontScale / density);
+ mTexts.put(text, sp);
+ text.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ }
+
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ setTextSizeH(text, sp);
+ }
+ });
+ return sp;
+ }
+
+ public void update() {
+ if (mTexts.isEmpty()) return;
+ mTexts.keyAt(0).post(mUpdateAll);
+ }
+
+ private void setTextSizeH(TextView text, int sp) {
+ text.setTextSize(TypedValue.COMPLEX_UNIT_SP, sp);
+ }
+
+ private final Runnable mUpdateAll = new Runnable() {
+ @Override
+ public void run() {
+ for (int i = 0; i < mTexts.size(); i++) {
+ setTextSizeH(mTexts.keyAt(i), mTexts.valueAt(i));
+ }
+ }
+ };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/Util.java b/packages/SystemUI/src/com/android/systemui/volume/Util.java
new file mode 100644
index 0000000..a46a44d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/Util.java
@@ -0,0 +1,174 @@
+/*
+ * 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.content.Context;
+import android.media.AudioManager;
+import android.media.MediaMetadata;
+import android.media.VolumeProvider;
+import android.media.session.MediaController.PlaybackInfo;
+import android.media.session.PlaybackState;
+import android.telephony.TelephonyManager;
+import android.view.View;
+import android.widget.TextView;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Objects;
+
+/**
+ * Static helpers for the volume dialog.
+ */
+class Util {
+
+ // Note: currently not shown (only used in the text footer)
+ private static final SimpleDateFormat HMMAA = new SimpleDateFormat("h:mm aa", Locale.US);
+
+ private static int[] AUDIO_MANAGER_FLAGS = new int[] {
+ AudioManager.FLAG_SHOW_UI,
+ AudioManager.FLAG_VIBRATE,
+ AudioManager.FLAG_PLAY_SOUND,
+ AudioManager.FLAG_ALLOW_RINGER_MODES,
+ AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE,
+ AudioManager.FLAG_SHOW_VIBRATE_HINT,
+ AudioManager.FLAG_SHOW_SILENT_HINT,
+ AudioManager.FLAG_FROM_KEY,
+ AudioManager.FLAG_SHOW_UI_WARNINGS,
+ };
+
+ private static String[] AUDIO_MANAGER_FLAG_NAMES = new String[] {
+ "SHOW_UI",
+ "VIBRATE",
+ "PLAY_SOUND",
+ "ALLOW_RINGER_MODES",
+ "REMOVE_SOUND_AND_VIBRATE",
+ "SHOW_VIBRATE_HINT",
+ "SHOW_SILENT_HINT",
+ "FROM_KEY",
+ "SHOW_UI_WARNINGS",
+ };
+
+ public static String logTag(Class<?> c) {
+ final String tag = "vol." + c.getSimpleName();
+ return tag.length() < 23 ? tag : tag.substring(0, 23);
+ }
+
+ public static String ringerModeToString(int ringerMode) {
+ switch (ringerMode) {
+ case AudioManager.RINGER_MODE_SILENT: return "RINGER_MODE_SILENT";
+ case AudioManager.RINGER_MODE_VIBRATE: return "RINGER_MODE_VIBRATE";
+ case AudioManager.RINGER_MODE_NORMAL: return "RINGER_MODE_NORMAL";
+ default: return "RINGER_MODE_UNKNOWN_" + ringerMode;
+ }
+ }
+
+ public static String mediaMetadataToString(MediaMetadata metadata) {
+ return metadata.getDescription().toString();
+ }
+
+ public static String playbackInfoToString(PlaybackInfo info) {
+ if (info == null) return null;
+ final String type = playbackInfoTypeToString(info.getPlaybackType());
+ final String vc = volumeProviderControlToString(info.getVolumeControl());
+ return String.format("PlaybackInfo[vol=%s,max=%s,type=%s,vc=%s],atts=%s",
+ info.getCurrentVolume(), info.getMaxVolume(), type, vc, info.getAudioAttributes());
+ }
+
+ public static String playbackInfoTypeToString(int type) {
+ switch (type) {
+ case PlaybackInfo.PLAYBACK_TYPE_LOCAL: return "LOCAL";
+ case PlaybackInfo.PLAYBACK_TYPE_REMOTE: return "REMOTE";
+ default: return "UNKNOWN_" + type;
+ }
+ }
+
+ public static String playbackStateStateToString(int state) {
+ switch (state) {
+ case PlaybackState.STATE_NONE: return "STATE_NONE";
+ case PlaybackState.STATE_STOPPED: return "STATE_STOPPED";
+ case PlaybackState.STATE_PAUSED: return "STATE_PAUSED";
+ case PlaybackState.STATE_PLAYING: return "STATE_PLAYING";
+ default: return "UNKNOWN_" + state;
+ }
+ }
+
+ public static String volumeProviderControlToString(int control) {
+ switch (control) {
+ case VolumeProvider.VOLUME_CONTROL_ABSOLUTE: return "VOLUME_CONTROL_ABSOLUTE";
+ case VolumeProvider.VOLUME_CONTROL_FIXED: return "VOLUME_CONTROL_FIXED";
+ case VolumeProvider.VOLUME_CONTROL_RELATIVE: return "VOLUME_CONTROL_RELATIVE";
+ default: return "VOLUME_CONTROL_UNKNOWN_" + control;
+ }
+ }
+
+ public static String playbackStateToString(PlaybackState playbackState) {
+ if (playbackState == null) return null;
+ return playbackStateStateToString(playbackState.getState()) + " " + playbackState;
+ }
+
+ public static String audioManagerFlagsToString(int value) {
+ return bitFieldToString(value, AUDIO_MANAGER_FLAGS, AUDIO_MANAGER_FLAG_NAMES);
+ }
+
+ private static String bitFieldToString(int value, int[] values, String[] names) {
+ if (value == 0) return "";
+ final StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < values.length; i++) {
+ if ((value & values[i]) != 0) {
+ if (sb.length() > 0) sb.append(',');
+ sb.append(names[i]);
+ }
+ value &= ~values[i];
+ }
+ if (value != 0) {
+ if (sb.length() > 0) sb.append(',');
+ sb.append("UNKNOWN_").append(value);
+ }
+ return sb.toString();
+ }
+
+ public static String getShortTime(long millis) {
+ return HMMAA.format(new Date(millis));
+ }
+
+ private static CharSequence emptyToNull(CharSequence str) {
+ return str == null || str.length() == 0 ? null : str;
+ }
+
+ public static boolean setText(TextView tv, CharSequence text) {
+ if (Objects.equals(emptyToNull(tv.getText()), emptyToNull(text))) return false;
+ tv.setText(text);
+ return true;
+ }
+
+ public static final void setVisOrGone(View v, boolean vis) {
+ if (v == null || (v.getVisibility() == View.VISIBLE) == vis) return;
+ v.setVisibility(vis ? View.VISIBLE : View.GONE);
+ }
+
+ public static final void setVisOrInvis(View v, boolean vis) {
+ if (v == null || (v.getVisibility() == View.VISIBLE) == vis) return;
+ v.setVisibility(vis ? View.VISIBLE : View.INVISIBLE);
+ }
+
+ public static boolean isVoiceCapable(Context context) {
+ final TelephonyManager telephony =
+ (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+ return telephony != null && telephony.isVoiceCapable();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java
index e3f8f3d..1f0ee57 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java
@@ -16,10 +16,18 @@
package com.android.systemui.volume;
+import android.content.res.Configuration;
+
import com.android.systemui.DemoMode;
import com.android.systemui.statusbar.policy.ZenModeController;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
public interface VolumeComponent extends DemoMode {
ZenModeController getZenController();
void dismissNow();
+ void onConfigurationChanged(Configuration newConfig);
+ void dump(FileDescriptor fd, PrintWriter pw, String[] args);
+ void register();
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
new file mode 100644
index 0000000..3964820
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
@@ -0,0 +1,1129 @@
+/*
+ * 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 static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK;
+import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_GENERIC;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.animation.LayoutTransition;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.annotation.SuppressLint;
+import android.app.Dialog;
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.content.res.ColorStateList;
+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;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+import android.provider.Settings.Global;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.SparseBooleanArray;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.AccessibilityDelegate;
+import android.view.View.OnAttachStateChangeListener;
+import android.view.View.OnClickListener;
+import android.view.View.OnLayoutChangeListener;
+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.LinearLayout;
+import android.widget.SeekBar;
+import android.widget.SeekBar.OnSeekBarChangeListener;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.volume.VolumeDialogController.State;
+import com.android.systemui.volume.VolumeDialogController.StreamState;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Visual presentation of the volume dialog.
+ *
+ * A client of VolumeDialogController and its state model.
+ *
+ * Methods ending in "H" must be called on the (ui) handler.
+ */
+public class VolumeDialog {
+ private static final String TAG = Util.logTag(VolumeDialog.class);
+
+ private static final long USER_ATTEMPT_GRACE_PERIOD = 1000;
+ private static final int WAIT_FOR_RIPPLE = 200;
+ private static final int UPDATE_ANIMATION_DURATION = 80;
+
+ private final Context mContext;
+ private final H mHandler = new H();
+ private final VolumeDialogController mController;
+
+ private final CustomDialog mDialog;
+ private final ViewGroup mDialogView;
+ private final ViewGroup mDialogContentView;
+ private final ImageButton mExpandButton;
+ private final View mSettingsButton;
+ private final List<VolumeRow> mRows = new ArrayList<VolumeRow>();
+ private final SpTexts mSpTexts;
+ private final SparseBooleanArray mDynamic = new SparseBooleanArray();
+ private final KeyguardManager mKeyguard;
+ private final int mExpandButtonAnimationDuration;
+ private final ZenFooter mZenFooter;
+ private final LayoutTransition mLayoutTransition;
+ private final Object mSafetyWarningLock = new Object();
+ private final Accessibility mAccessibility = new Accessibility();
+ private final ColorStateList mActiveSliderTint;
+ private final ColorStateList mInactiveSliderTint;
+ private final VolumeDialogMotion mMotion;
+
+ private boolean mShowing;
+ private boolean mExpanded;
+ private int mActiveStream;
+ private boolean mShowHeaders = VolumePrefs.DEFAULT_SHOW_HEADERS;
+ private boolean mAutomute = VolumePrefs.DEFAULT_ENABLE_AUTOMUTE;
+ private boolean mSilentMode = VolumePrefs.DEFAULT_ENABLE_SILENT_MODE;
+ private State mState;
+ private int mExpandButtonRes;
+ private boolean mExpandButtonAnimationRunning;
+ private SafetyWarningDialog mSafetyWarning;
+ private Callback mCallback;
+ private boolean mPendingStateChanged;
+ private boolean mPendingRecheckAll;
+ private long mCollapseTime;
+
+ public VolumeDialog(Context context, int windowType, VolumeDialogController controller,
+ ZenModeController zenModeController, Callback callback) {
+ mContext = context;
+ mController = controller;
+ mCallback = callback;
+ mSpTexts = new SpTexts(mContext);
+ mKeyguard = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
+
+ mDialog = new CustomDialog(mContext);
+
+ final Window window = mDialog.getWindow();
+ window.requestFeature(Window.FEATURE_NO_TITLE);
+ window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
+ window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+ window.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();
+ final WindowManager.LayoutParams lp = window.getAttributes();
+ lp.type = windowType;
+ lp.format = PixelFormat.TRANSLUCENT;
+ lp.setTitle(VolumeDialog.class.getSimpleName());
+ lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
+ lp.y = res.getDimensionPixelSize(R.dimen.volume_offset_top);
+ lp.gravity = Gravity.TOP;
+ window.setAttributes(lp);
+ window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
+
+ mActiveSliderTint = loadColorStateList(R.color.system_accent_color);
+ mInactiveSliderTint = loadColorStateList(R.color.volume_slider_inactive);
+ mDialog.setContentView(R.layout.volume_dialog);
+ mDialogView = (ViewGroup) mDialog.findViewById(R.id.volume_dialog);
+ mDialogContentView = (ViewGroup) mDialog.findViewById(R.id.volume_dialog_content);
+ mExpandButton = (ImageButton) mDialogView.findViewById(R.id.volume_expand_button);
+ mExpandButton.setOnClickListener(mClickExpand);
+ updateWindowWidthH();
+ updateExpandButtonH();
+ mLayoutTransition = new LayoutTransition();
+ mLayoutTransition.setDuration(new ValueAnimator().getDuration() / 2);
+ mDialogContentView.setLayoutTransition(mLayoutTransition);
+ 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;
+ }
+ }
+ });
+
+ addRow(AudioManager.STREAM_RING,
+ R.drawable.ic_volume_ringer, R.drawable.ic_volume_ringer_mute, true);
+ addRow(AudioManager.STREAM_MUSIC,
+ R.drawable.ic_volume_media, R.drawable.ic_volume_media_mute, true);
+ addRow(AudioManager.STREAM_ALARM,
+ R.drawable.ic_volume_alarm, R.drawable.ic_volume_alarm_mute, false);
+ addRow(AudioManager.STREAM_VOICE_CALL,
+ R.drawable.ic_volume_voice, R.drawable.ic_volume_voice, 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);
+
+ mSettingsButton = mDialog.findViewById(R.id.volume_settings_button);
+ mSettingsButton.setOnClickListener(mClickSettings);
+ mExpandButtonAnimationDuration = res.getInteger(R.integer.volume_expand_animation_duration);
+ mZenFooter = (ZenFooter) mDialog.findViewById(R.id.volume_zen_footer);
+ mZenFooter.init(zenModeController);
+
+ mAccessibility.init();
+
+ controller.addCallback(mControllerCallbackH, mHandler);
+ controller.getState();
+ }
+
+ 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.standard_notification_panel_width);
+ if (w > max) {
+ w = max;
+ }
+ w -= mContext.getResources().getDimensionPixelSize(R.dimen.notification_side_padding) * 2;
+ lp.width = w;
+ mDialogView.setLayoutParams(lp);
+ }
+
+ public void setStreamImportant(int stream, boolean important) {
+ mHandler.obtainMessage(H.SET_STREAM_IMPORTANT, stream, important ? 1 : 0).sendToTarget();
+ }
+
+ public void setShowHeaders(boolean showHeaders) {
+ if (showHeaders == mShowHeaders) return;
+ mShowHeaders = showHeaders;
+ mHandler.sendEmptyMessage(H.RECHECK_ALL);
+ }
+
+ public void setAutomute(boolean automute) {
+ if (mAutomute == automute) return;
+ mAutomute = automute;
+ mHandler.sendEmptyMessage(H.RECHECK_ALL);
+ }
+
+ public void setSilentMode(boolean silentMode) {
+ if (mSilentMode == silentMode) return;
+ mSilentMode = silentMode;
+ mHandler.sendEmptyMessage(H.RECHECK_ALL);
+ }
+
+ private void addRow(int stream, int iconRes, int iconMuteRes, boolean important) {
+ final VolumeRow row = initRow(stream, iconRes, iconMuteRes, important);
+ if (!mRows.isEmpty()) {
+ final View v = new View(mContext);
+ v.setId(android.R.id.background);
+ final int h = mContext.getResources()
+ .getDimensionPixelSize(R.dimen.volume_slider_interspacing);
+ final LinearLayout.LayoutParams lp =
+ new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, h);
+ mDialogContentView.addView(v, mDialogContentView.getChildCount() - 1, lp);
+ row.space = v;
+ }
+ row.settingsButton.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) {
+ final boolean moved = oldLeft != left || oldTop != top;
+ if (D.BUG) Log.d(TAG, "onLayoutChange moved=" + moved
+ + " old=" + new Rect(oldLeft, oldTop, oldRight, oldBottom).toShortString()
+ + " new=" + new Rect(left,top,right,bottom).toShortString());
+ if (moved) {
+ for (int i = 0; i < mDialogContentView.getChildCount(); i++) {
+ final View c = mDialogContentView.getChildAt(i);
+ if (!c.isShown()) continue;
+ if (c == row.view) {
+ repositionExpandAnim(row);
+ }
+ return;
+ }
+ }
+ }
+ });
+ // add new row just before the footer
+ mDialogContentView.addView(row.view, mDialogContentView.getChildCount() - 1);
+ mRows.add(row);
+ }
+
+ private boolean isAttached() {
+ return mDialogContentView != null && mDialogContentView.isAttachedToWindow();
+ }
+
+ private VolumeRow getActiveRow() {
+ for (VolumeRow row : mRows) {
+ if (row.stream == mActiveStream) {
+ return row;
+ }
+ }
+ return mRows.get(0);
+ }
+
+ private VolumeRow findRow(int stream) {
+ for (VolumeRow row : mRows) {
+ if (row.stream == stream) return row;
+ }
+ return null;
+ }
+
+ private void repositionExpandAnim(VolumeRow row) {
+ final int[] loc = new int[2];
+ row.settingsButton.getLocationInWindow(loc);
+ final MarginLayoutParams mlp = (MarginLayoutParams) mDialogView.getLayoutParams();
+ final int x = loc[0] - mlp.leftMargin;
+ final int y = loc[1] - mlp.topMargin;
+ if (D.BUG) Log.d(TAG, "repositionExpandAnim x=" + x + " y=" + y);
+ mExpandButton.setTranslationX(x);
+ mExpandButton.setTranslationY(y);
+ mExpandButton.setTag((Integer) y);
+ }
+
+ public void dump(PrintWriter writer) {
+ writer.println(VolumeDialog.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(" mShowHeaders: "); writer.println(mShowHeaders);
+ 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);
+ }
+
+ private static int getImpliedLevel(SeekBar seekBar, int progress) {
+ final int m = seekBar.getMax();
+ final int n = m / 100 - 1;
+ final int level = progress == 0 ? 0
+ : progress == m ? (m / 100) : (1 + (int)((progress / (float) m) * n));
+ return level;
+ }
+
+ @SuppressLint("InflateParams")
+ private VolumeRow initRow(final int stream, int iconRes, int iconMuteRes, boolean important) {
+ final VolumeRow row = new VolumeRow();
+ row.stream = stream;
+ row.iconRes = iconRes;
+ row.iconMuteRes = iconMuteRes;
+ row.important = important;
+ row.view = mDialog.getLayoutInflater().inflate(R.layout.volume_dialog_row, null);
+ row.view.setTag(row);
+ row.header = (TextView) row.view.findViewById(R.id.volume_row_header);
+ mSpTexts.add(row.header);
+ row.slider = (SeekBar) row.view.findViewById(R.id.volume_row_slider);
+ row.slider.setOnSeekBarChangeListener(new VolumeSeekBarChangeListener(row));
+
+ // 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.icon = (ImageButton) row.view.findViewById(R.id.volume_row_icon);
+ row.icon.setImageResource(iconRes);
+ 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);
+ }
+ } else {
+ mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false);
+ if (row.ss.level == 0) {
+ mController.setStreamVolume(stream, 1);
+ }
+ }
+ } else {
+ final boolean vmute = row.ss.level == 0;
+ mController.setStreamVolume(stream, vmute ? row.lastAudibleLevel : 0);
+ }
+ row.userAttempt = 0; // reset the grace period, slider should update immediately
+ }
+ });
+ row.settingsButton = (ImageButton) row.view.findViewById(R.id.volume_settings_button);
+ row.settingsButton.setOnClickListener(mClickSettings);
+ return row;
+ }
+
+ public void destroy() {
+ mController.removeCallback(mControllerCallbackH);
+ }
+
+ public void show(int reason) {
+ mHandler.obtainMessage(H.SHOW, reason, 0).sendToTarget();
+ }
+
+ public void dismiss(int reason) {
+ mHandler.obtainMessage(H.DISMISS, reason, 0).sendToTarget();
+ }
+
+ private void showH(int reason) {
+ if (D.BUG) Log.d(TAG, "showH r=" + Events.DISMISS_REASONS[reason]);
+ mHandler.removeMessages(H.SHOW);
+ mHandler.removeMessages(H.DISMISS);
+ rescheduleTimeoutH();
+ if (mShowing) return;
+ mShowing = true;
+ mMotion.startShow();
+ Events.writeEvent(mContext, Events.EVENT_SHOW_DIALOG, reason, mKeyguard.isKeyguardLocked());
+ mController.notifyVisible(true);
+ }
+
+ protected void rescheduleTimeoutH() {
+ mHandler.removeMessages(H.DISMISS);
+ final int timeout = computeTimeoutH();
+ mHandler.sendMessageDelayed(mHandler
+ .obtainMessage(H.DISMISS, Events.DISMISS_REASON_TIMEOUT, 0), timeout);
+ if (D.BUG) Log.d(TAG, "rescheduleTimeout " + timeout + " " + Debug.getCaller());
+ mController.userActivity();
+ }
+
+ private int computeTimeoutH() {
+ if (mAccessibility.mFeedbackEnabled) return 20000;
+ if (mSafetyWarning != null) return 5000;
+ if (mExpanded || mExpandButtonAnimationRunning) return 5000;
+ if (mActiveStream == AudioManager.STREAM_MUSIC) return 1500;
+ return 3000;
+ }
+
+ protected void dismissH(int reason) {
+ mHandler.removeMessages(H.DISMISS);
+ mHandler.removeMessages(H.SHOW);
+ if (!mShowing) return;
+ mShowing = false;
+ mMotion.startDismiss(new Runnable() {
+ @Override
+ public void run() {
+ setExpandedH(false);
+ }
+ });
+ Events.writeEvent(mContext, Events.EVENT_DISMISS_DIALOG, reason);
+ mController.notifyVisible(false);
+ synchronized (mSafetyWarningLock) {
+ if (mSafetyWarning != null) {
+ if (D.BUG) Log.d(TAG, "SafetyWarning dismissed");
+ mSafetyWarning.dismiss();
+ }
+ }
+ }
+
+ 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 setExpandedH(boolean expanded) {
+ if (mExpanded == expanded) return;
+ mExpanded = expanded;
+ mExpandButtonAnimationRunning = isAttached();
+ if (D.BUG) Log.d(TAG, "setExpandedH " + expanded);
+ if (!mExpanded && mExpandButtonAnimationRunning) {
+ prepareForCollapse();
+ }
+ updateRowsH();
+ 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);
+ }
+ }
+ rescheduleTimeoutH();
+ }
+
+ private void updateExpandButtonH() {
+ if (D.BUG) Log.d(TAG, "updateExpandButtonH");
+ mExpandButton.setClickable(!mExpandButtonAnimationRunning);
+ if (mExpandButtonAnimationRunning && isAttached()) return;
+ final int res = mExpanded ? R.drawable.ic_volume_collapse_animation
+ : R.drawable.ic_volume_expand_animation;
+ if (res == mExpandButtonRes) return;
+ mExpandButtonRes = res;
+ mExpandButton.setImageResource(res);
+ mExpandButton.setContentDescription(mContext.getString(mExpanded ?
+ R.string.accessibility_volume_collapse : R.string.accessibility_volume_expand));
+ }
+
+ private boolean isVisibleH(VolumeRow row, boolean isActive) {
+ return mExpanded && row.view.getVisibility() == View.VISIBLE
+ || (mExpanded && (row.important || isActive))
+ || !mExpanded && isActive;
+ }
+
+ private void updateRowsH() {
+ if (D.BUG) Log.d(TAG, "updateRowsH");
+ final VolumeRow activeRow = getActiveRow();
+ updateFooterH();
+ updateExpandButtonH();
+ if (!mShowing) {
+ trimObsoleteH();
+ }
+ // apply changes to all rows
+ for (VolumeRow row : mRows) {
+ final boolean isActive = row == activeRow;
+ final boolean visible = isVisibleH(row, isActive);
+ Util.setVisOrGone(row.view, visible);
+ Util.setVisOrGone(row.space, visible && mExpanded);
+ final int expandButtonRes = mExpanded ? R.drawable.ic_volume_settings : 0;
+ if (expandButtonRes != row.cachedExpandButtonRes) {
+ row.cachedExpandButtonRes = expandButtonRes;
+ if (expandButtonRes == 0) {
+ row.settingsButton.setImageDrawable(null);
+ } else {
+ row.settingsButton.setImageResource(expandButtonRes);
+ }
+ }
+ Util.setVisOrInvis(row.settingsButton, false);
+ updateVolumeRowHeaderVisibleH(row);
+ row.header.setAlpha(mExpanded && isActive ? 1 : 0.5f);
+ updateVolumeRowSliderTintH(row, isActive);
+ }
+ }
+
+ private void trimObsoleteH() {
+ if (D.BUG) Log.d(TAG, "trimObsoleteH");
+ for (int i = mRows.size() -1; i >= 0; i--) {
+ final VolumeRow row = mRows.get(i);
+ if (row.ss == null || !row.ss.dynamic) continue;
+ if (!mDynamic.get(row.stream)) {
+ mRows.remove(i);
+ mDialogContentView.removeView(row.view);
+ mDialogContentView.removeView(row.space);
+ }
+ }
+ }
+
+ private void onStateChangedH(State state) {
+ final boolean animating = mMotion.isAnimating();
+ if (D.BUG) Log.d(TAG, "onStateChangedH animating=" + animating);
+ mState = state;
+ if (animating) {
+ mPendingStateChanged = true;
+ return;
+ }
+ mDynamic.clear();
+ // add any new dynamic rows
+ for (int i = 0; i < state.states.size(); i++) {
+ final int stream = state.states.keyAt(i);
+ final StreamState ss = state.states.valueAt(i);
+ if (!ss.dynamic) continue;
+ mDynamic.put(stream, true);
+ if (findRow(stream) == null) {
+ addRow(stream, R.drawable.ic_volume_remote, R.drawable.ic_volume_remote_mute, true);
+ }
+ }
+
+ if (mActiveStream != state.activeStream) {
+ mActiveStream = state.activeStream;
+ updateRowsH();
+ rescheduleTimeoutH();
+ }
+ 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;
+ if (wasVisible != visible && !visible) {
+ prepareForCollapse();
+ }
+ Util.setVisOrGone(mZenFooter, visible);
+ mZenFooter.update();
+ }
+
+ private void updateVolumeRowH(VolumeRow row) {
+ if (D.BUG) Log.d(TAG, "updateVolumeRowH s=" + row.stream);
+ if (mState == null) return;
+ final StreamState ss = mState.states.get(row.stream);
+ if (ss == null) return;
+ row.ss = ss;
+ if (ss.level > 0) {
+ row.lastAudibleLevel = ss.level;
+ }
+ if (ss.level == row.requestedLevel) {
+ row.requestedLevel = -1;
+ }
+ final boolean isRingStream = row.stream == AudioManager.STREAM_RING;
+ final boolean isSystemStream = row.stream == AudioManager.STREAM_SYSTEM;
+ final boolean isAlarmStream = row.stream == AudioManager.STREAM_ALARM;
+ final boolean isMusicStream = row.stream == AudioManager.STREAM_MUSIC;
+ final boolean isRingVibrate = isRingStream
+ && mState.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE;
+ final boolean isRingSilent = isRingStream
+ && mState.ringerModeInternal == AudioManager.RINGER_MODE_SILENT;
+ final boolean isZenAlarms = mState.zenMode == Global.ZEN_MODE_ALARMS;
+ final boolean isZenNone = mState.zenMode == Global.ZEN_MODE_NO_INTERRUPTIONS;
+ final boolean isZenPriority = mState.zenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+ final boolean isRingZenNone = (isRingStream || isSystemStream) && isZenNone;
+ final boolean isRingLimited = isRingStream && isZenPriority;
+ final boolean zenMuted = isZenAlarms ? (isRingStream || isSystemStream)
+ : isZenNone ? (isRingStream || isSystemStream || isAlarmStream || isMusicStream)
+ : false;
+
+ // update slider max
+ final int max = ss.levelMax * 100;
+ if (max != row.slider.getMax()) {
+ row.slider.setMax(max);
+ }
+
+ // update header visible
+ updateVolumeRowHeaderVisibleH(row);
+
+ // update header text
+ String text = ss.name;
+ if (mShowHeaders) {
+ if (isRingZenNone) {
+ text = mContext.getString(R.string.volume_stream_muted_dnd, ss.name);
+ } else if (isRingVibrate && isRingLimited) {
+ text = mContext.getString(R.string.volume_stream_vibrate_dnd, ss.name);
+ } else if (isRingVibrate) {
+ text = mContext.getString(R.string.volume_stream_vibrate, ss.name);
+ } else if (ss.muted || mAutomute && ss.level == 0) {
+ text = mContext.getString(R.string.volume_stream_muted, ss.name);
+ } else if (isRingLimited) {
+ text = mContext.getString(R.string.volume_stream_limited_dnd, ss.name);
+ }
+ }
+ Util.setText(row.header, text);
+
+ // update icon
+ final boolean iconEnabled = (mAutomute || ss.muteSupported) && !zenMuted;
+ row.icon.setEnabled(iconEnabled);
+ row.icon.setAlpha(iconEnabled ? 1 : 0.5f);
+ final int iconRes =
+ isRingVibrate ? R.drawable.ic_volume_ringer_vibrate
+ : isRingSilent || zenMuted ? row.cachedIconRes
+ : 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.iconState =
+ iconRes == R.drawable.ic_volume_ringer_vibrate ? Events.ICON_STATE_VIBRATE
+ : (iconRes == R.drawable.ic_volume_media_bt_mute || iconRes == row.iconMuteRes)
+ ? Events.ICON_STATE_MUTE
+ : (iconRes == R.drawable.ic_volume_media_bt || iconRes == row.iconRes)
+ ? Events.ICON_STATE_UNMUTE
+ : Events.ICON_STATE_UNKNOWN;
+ row.icon.setContentDescription(ss.name);
+
+ // update slider
+ final boolean enableSlider = !zenMuted;
+ final int vlevel = row.ss.muted && (isRingVibrate || !isRingStream && !zenMuted) ? 0
+ : row.ss.level;
+ updateVolumeRowSliderH(row, enableSlider, vlevel);
+ }
+
+ private void updateVolumeRowHeaderVisibleH(VolumeRow row) {
+ final boolean dynamic = row.ss != null && row.ss.dynamic;
+ final boolean showHeaders = mShowHeaders || mExpanded && dynamic;
+ if (row.cachedShowHeaders != showHeaders) {
+ row.cachedShowHeaders = showHeaders;
+ Util.setVisOrGone(row.header, showHeaders);
+ }
+ }
+
+ private void updateVolumeRowSliderTintH(VolumeRow row, boolean isActive) {
+ if (isActive && mExpanded) {
+ row.slider.requestFocus();
+ }
+ final ColorStateList tint = isActive && row.slider.isEnabled() ? mActiveSliderTint
+ : mInactiveSliderTint;
+ if (tint == row.cachedSliderTint) return;
+ row.cachedSliderTint = tint;
+ row.slider.setProgressTintList(tint);
+ row.slider.setThumbTintList(tint);
+ }
+
+ private void updateVolumeRowSliderH(VolumeRow row, boolean enable, int vlevel) {
+ row.slider.setEnabled(enable);
+ updateVolumeRowSliderTintH(row, row.stream == mActiveStream);
+ if (row.tracking) {
+ return; // don't update if user is sliding
+ }
+ final int progress = row.slider.getProgress();
+ final int level = getImpliedLevel(row.slider, progress);
+ final boolean rowVisible = row.view.getVisibility() == View.VISIBLE;
+ final boolean inGracePeriod = (SystemClock.uptimeMillis() - row.userAttempt)
+ < USER_ATTEMPT_GRACE_PERIOD;
+ mHandler.removeMessages(H.RECHECK, row);
+ if (mShowing && rowVisible && inGracePeriod) {
+ if (D.BUG) Log.d(TAG, "inGracePeriod");
+ mHandler.sendMessageAtTime(mHandler.obtainMessage(H.RECHECK, row),
+ row.userAttempt + USER_ATTEMPT_GRACE_PERIOD);
+ return; // don't update if visible and in grace period
+ }
+ if (vlevel == level) {
+ if (mShowing && rowVisible) {
+ return; // don't clamp if visible
+ }
+ }
+ final int newProgress = vlevel * 100;
+ if (progress != newProgress) {
+ if (mShowing && rowVisible) {
+ // animate!
+ if (row.anim != null && row.anim.isRunning()
+ && row.animTargetProgress == newProgress) {
+ return; // already animating to the target progress
+ }
+ // start/update animation
+ if (row.anim == null) {
+ row.anim = ObjectAnimator.ofInt(row.slider, "progress", progress, newProgress);
+ row.anim.setInterpolator(new DecelerateInterpolator());
+ } else {
+ row.anim.cancel();
+ row.anim.setIntValues(progress, newProgress);
+ }
+ row.animTargetProgress = newProgress;
+ row.anim.setDuration(UPDATE_ANIMATION_DURATION);
+ row.anim.start();
+ } else {
+ // update slider directly to clamped value
+ if (row.anim != null) {
+ row.anim.cancel();
+ }
+ row.slider.setProgress(newProgress);
+ }
+ }
+ }
+
+ private void recheckH(VolumeRow row) {
+ if (row == null) {
+ if (D.BUG) Log.d(TAG, "recheckH ALL");
+ trimObsoleteH();
+ for (VolumeRow r : mRows) {
+ updateVolumeRowH(r);
+ }
+ } else {
+ if (D.BUG) Log.d(TAG, "recheckH " + row.stream);
+ updateVolumeRowH(row);
+ }
+ }
+
+ private void setStreamImportantH(int stream, boolean important) {
+ for (VolumeRow row : mRows) {
+ if (row.stream == stream) {
+ row.important = important;
+ return;
+ }
+ }
+ }
+
+ private void showSafetyWarningH(int flags) {
+ if ((flags & (AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_SHOW_UI_WARNINGS)) != 0
+ || mShowing) {
+ synchronized (mSafetyWarningLock) {
+ if (mSafetyWarning != null) {
+ return;
+ }
+ mSafetyWarning = new SafetyWarningDialog(mContext, mController.getAudioManager()) {
+ @Override
+ protected void cleanUp() {
+ synchronized (mSafetyWarningLock) {
+ mSafetyWarning = null;
+ }
+ recheckH(null);
+ }
+ };
+ mSafetyWarning.show();
+ }
+ recheckH(null);
+ }
+ rescheduleTimeoutH();
+ }
+
+ private final VolumeDialogController.Callbacks mControllerCallbackH
+ = new VolumeDialogController.Callbacks() {
+ @Override
+ public void onShowRequested(int reason) {
+ showH(reason);
+ }
+
+ @Override
+ public void onDismissRequested(int reason) {
+ dismissH(reason);
+ }
+
+ @Override
+ public void onScreenOff() {
+ dismissH(Events.DISMISS_REASON_SCREEN_OFF);
+ }
+
+ @Override
+ public void onStateChanged(State state) {
+ onStateChangedH(state);
+ }
+
+ @Override
+ public void onLayoutDirectionChanged(int layoutDirection) {
+ mDialogView.setLayoutDirection(layoutDirection);
+ }
+
+ @Override
+ public void onConfigurationChanged() {
+ updateWindowWidthH();
+ mSpTexts.update();
+ mZenFooter.onConfigurationChanged();
+ }
+
+ @Override
+ public void onShowVibrateHint() {
+ if (mSilentMode) {
+ mController.setRingerMode(AudioManager.RINGER_MODE_SILENT, false);
+ }
+ }
+
+ @Override
+ public void onShowSilentHint() {
+ if (mSilentMode) {
+ mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false);
+ }
+ }
+
+ @Override
+ public void onShowSafetyWarning(int flags) {
+ showSafetyWarningH(flags);
+ }
+ };
+
+ 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);
+ setExpandedH(newExpand);
+ }
+ };
+
+ private final OnClickListener mClickSettings = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mSettingsButton.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ Events.writeEvent(mContext, Events.EVENT_SETTINGS_CLICK);
+ if (mCallback != null) {
+ mCallback.onSettingsClicked();
+ }
+ }
+ }, WAIT_FOR_RIPPLE);
+ }
+ };
+
+ private final class H extends Handler {
+ private static final int SHOW = 1;
+ private static final int DISMISS = 2;
+ private static final int RECHECK = 3;
+ private static final int RECHECK_ALL = 4;
+ 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;
+
+ public H() {
+ super(Looper.getMainLooper());
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case SHOW: showH(msg.arg1); break;
+ case DISMISS: dismissH(msg.arg1); break;
+ case RECHECK: recheckH((VolumeRow) msg.obj); break;
+ case RECHECK_ALL: recheckH(null); break;
+ 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;
+ }
+ }
+ }
+
+ private final class CustomDialog extends Dialog {
+ public CustomDialog(Context context) {
+ super(context);
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ rescheduleTimeoutH();
+ return super.dispatchTouchEvent(ev);
+ }
+
+ @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);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (isShowing()) {
+ if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
+ dismissH(Events.DISMISS_REASON_TOUCH_OUTSIDE);
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ private final class VolumeSeekBarChangeListener implements OnSeekBarChangeListener {
+ private final VolumeRow mRow;
+
+ private VolumeSeekBarChangeListener(VolumeRow row) {
+ mRow = row;
+ }
+
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ if (mRow.ss == null) return;
+ if (D.BUG) Log.d(TAG, AudioSystem.streamToString(mRow.stream)
+ + " onProgressChanged " + progress + " fromUser=" + fromUser);
+ if (!fromUser) return;
+ if (mRow.ss.levelMin > 0) {
+ final int minProgress = mRow.ss.levelMin * 100;
+ if (progress < minProgress) {
+ seekBar.setProgress(minProgress);
+ }
+ }
+ final int userLevel = getImpliedLevel(seekBar, progress);
+ if (mRow.ss.level != userLevel || mRow.ss.muted && userLevel > 0) {
+ mRow.userAttempt = SystemClock.uptimeMillis();
+ if (mRow.requestedLevel != userLevel) {
+ mController.setStreamVolume(mRow.stream, userLevel);
+ mRow.requestedLevel = userLevel;
+ Events.writeEvent(mContext, Events.EVENT_TOUCH_LEVEL_CHANGED, mRow.stream,
+ userLevel);
+ }
+ }
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ if (D.BUG) Log.d(TAG, "onStartTrackingTouch"+ " " + mRow.stream);
+ mController.setActiveStream(mRow.stream);
+ mRow.tracking = true;
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ if (D.BUG) Log.d(TAG, "onStopTrackingTouch"+ " " + mRow.stream);
+ mRow.tracking = false;
+ mRow.userAttempt = SystemClock.uptimeMillis();
+ int userLevel = getImpliedLevel(seekBar, seekBar.getProgress());
+ Events.writeEvent(mContext, Events.EVENT_TOUCH_LEVEL_DONE, mRow.stream, userLevel);
+ if (mRow.ss.level != userLevel) {
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(H.RECHECK, mRow),
+ USER_ATTEMPT_GRACE_PERIOD);
+ }
+ }
+ }
+
+ private final class Accessibility extends AccessibilityDelegate {
+ private AccessibilityManager mMgr;
+ private boolean mFeedbackEnabled;
+
+ public void init() {
+ mMgr = (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
+ mDialogView.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ if (D.BUG) Log.d(TAG, "onViewDetachedFromWindow");
+ // noop
+ }
+
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ if (D.BUG) Log.d(TAG, "onViewAttachedToWindow");
+ updateFeedbackEnabled();
+ }
+ });
+ mDialogView.setAccessibilityDelegate(this);
+ mMgr.addAccessibilityStateChangeListener(new AccessibilityStateChangeListener() {
+ @Override
+ public void onAccessibilityStateChanged(boolean enabled) {
+ updateFeedbackEnabled();
+ }
+ });
+ updateFeedbackEnabled();
+ }
+
+ @Override
+ public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child,
+ AccessibilityEvent event) {
+ rescheduleTimeoutH();
+ return super.onRequestSendAccessibilityEvent(host, child, event);
+ }
+
+ private void updateFeedbackEnabled() {
+ mFeedbackEnabled = computeFeedbackEnabled();
+ }
+
+ private boolean computeFeedbackEnabled() {
+ // are there any enabled non-generic a11y services?
+ final List<AccessibilityServiceInfo> services =
+ mMgr.getEnabledAccessibilityServiceList(FEEDBACK_ALL_MASK);
+ for (AccessibilityServiceInfo asi : services) {
+ if (asi.feedbackType != 0 && asi.feedbackType != FEEDBACK_GENERIC) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ private static class VolumeRow {
+ private View view;
+ private View space;
+ private TextView header;
+ private ImageButton icon;
+ private SeekBar slider;
+ private ImageButton settingsButton;
+ private int stream;
+ private StreamState ss;
+ private long userAttempt; // last user-driven slider change
+ private boolean tracking; // tracking slider touch
+ private int requestedLevel = -1; // pending user-requested level via progress changed
+ private int iconRes;
+ private int iconMuteRes;
+ private boolean important;
+ private int cachedIconRes;
+ private ColorStateList cachedSliderTint;
+ private int iconState; // from Events
+ private boolean cachedShowHeaders = VolumePrefs.DEFAULT_SHOW_HEADERS;
+ private int cachedExpandButtonRes;
+ private ObjectAnimator anim; // slider progress animation for non-touch-related updates
+ private int animTargetProgress;
+ private int lastAudibleLevel = 1;
+ }
+
+ public interface Callback {
+ void onSettingsClicked();
+ void onZenSettingsClicked();
+ void onZenPrioritySettingsClicked();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
new file mode 100644
index 0000000..1083f40
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
@@ -0,0 +1,141 @@
+/*
+ * 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.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.media.AudioManager;
+import android.media.VolumePolicy;
+import android.os.Bundle;
+import android.os.Handler;
+import android.provider.Settings;
+import android.view.WindowManager;
+
+import com.android.systemui.SystemUI;
+import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.qs.tiles.DndTile;
+import com.android.systemui.statusbar.phone.PhoneStatusBar;
+import com.android.systemui.statusbar.policy.ZenModeController;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Implementation of VolumeComponent backed by the new volume dialog.
+ */
+public class VolumeDialogComponent implements VolumeComponent {
+ private final SystemUI mSysui;
+ private final Context mContext;
+ private final VolumeDialogController mController;
+ private final ZenModeController mZenModeController;
+ private final VolumeDialog mDialog;
+ private final VolumePolicy mVolumePolicy = new VolumePolicy(
+ true, // volumeDownToEnterSilent
+ true, // volumeUpToExitSilent
+ true, // doNotDisturbWhenSilent
+ 400 // vibrateToSilentDebounce
+ );
+
+ public VolumeDialogComponent(SystemUI sysui, Context context, Handler handler,
+ ZenModeController zen) {
+ mSysui = sysui;
+ mContext = context;
+ mController = new VolumeDialogController(context, null) {
+ @Override
+ protected void onUserActivityW() {
+ sendUserActivity();
+ }
+ };
+ mZenModeController = zen;
+ mDialog = new VolumeDialog(context, WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY,
+ mController, zen, mVolumeDialogCallback);
+ applyConfiguration();
+ }
+
+ private void sendUserActivity() {
+ final KeyguardViewMediator kvm = mSysui.getComponent(KeyguardViewMediator.class);
+ if (kvm != null) {
+ kvm.userActivity();
+ }
+ }
+
+ private void applyConfiguration() {
+ mDialog.setStreamImportant(AudioManager.STREAM_ALARM, true);
+ mDialog.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
+ mDialog.setShowHeaders(false);
+ mDialog.setAutomute(true);
+ mDialog.setSilentMode(false);
+ mController.setVolumePolicy(mVolumePolicy);
+ mController.showDndTile(true);
+ }
+
+ @Override
+ public ZenModeController getZenController() {
+ return mZenModeController;
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ // noop
+ }
+
+ @Override
+ public void dismissNow() {
+ mController.dismiss();
+ }
+
+ @Override
+ public void dispatchDemoCommand(String command, Bundle args) {
+ // noop
+ }
+
+ @Override
+ public void register() {
+ mController.register();
+ DndTile.setCombinedIcon(mContext, true);
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ mController.dump(fd, pw, args);
+ mDialog.dump(pw);
+ }
+
+ private void startSettings(Intent intent) {
+ mSysui.getComponent(PhoneStatusBar.class).startActivityDismissingKeyguard(intent,
+ true /* onlyProvisioned */, true /* dismissShade */);
+ }
+
+ private final VolumeDialog.Callback mVolumeDialogCallback = new VolumeDialog.Callback() {
+ @Override
+ public void onSettingsClicked() {
+ startSettings(new Intent(Settings.ACTION_NOTIFICATION_SETTINGS));
+ }
+
+ @Override
+ public void onZenSettingsClicked() {
+ startSettings(ZenModePanel.ZEN_SETTINGS);
+ }
+
+ @Override
+ public void onZenPrioritySettingsClicked() {
+ startSettings(ZenModePanel.ZEN_PRIORITY_SETTINGS);
+ }
+ };
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
new file mode 100644
index 0000000..32d6805
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
@@ -0,0 +1,1024 @@
+/*
+ * 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.app.NotificationManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.database.ContentObserver;
+import android.media.AudioManager;
+import android.media.AudioSystem;
+import android.media.IVolumeController;
+import android.media.VolumePolicy;
+import android.media.session.MediaController.PlaybackInfo;
+import android.media.session.MediaSession.Token;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+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.Log;
+import android.util.SparseArray;
+
+import com.android.systemui.R;
+import com.android.systemui.qs.tiles.DndTile;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Source of truth for all state / events related to the volume dialog. No presentation.
+ *
+ * All work done on a dedicated background worker thread & associated worker.
+ *
+ * Methods ending in "W" must be called on the worker thread.
+ */
+public class VolumeDialogController {
+ private static final String TAG = Util.logTag(VolumeDialogController.class);
+
+ private static final int DYNAMIC_STREAM_START_INDEX = 100;
+ private static final int VIBRATE_HINT_DURATION = 50;
+
+ private static final int[] STREAMS = {
+ AudioSystem.STREAM_ALARM,
+ AudioSystem.STREAM_BLUETOOTH_SCO,
+ AudioSystem.STREAM_DTMF,
+ AudioSystem.STREAM_MUSIC,
+ AudioSystem.STREAM_NOTIFICATION,
+ AudioSystem.STREAM_RING,
+ AudioSystem.STREAM_SYSTEM,
+ AudioSystem.STREAM_SYSTEM_ENFORCED,
+ AudioSystem.STREAM_TTS,
+ AudioSystem.STREAM_VOICE_CALL,
+ };
+
+ private final HandlerThread mWorkerThread;
+ private final W mWorker;
+ private final Context mContext;
+ private final AudioManager mAudio;
+ private final NotificationManager mNoMan;
+ private final ComponentName mComponent;
+ private final SettingObserver mObserver;
+ private final Receiver mReceiver = new Receiver();
+ private final MediaSessions mMediaSessions;
+ private final VC mVolumeController = new VC();
+ private final C mCallbacks = new C();
+ private final State mState = new State();
+ private final String[] mStreamTitles;
+ private final MediaSessionsCallbacks mMediaSessionsCallbacksW = new MediaSessionsCallbacks();
+ private final Vibrator mVibrator;
+ private final boolean mHasVibrator;
+
+ private boolean mEnabled;
+ private boolean mDestroyed;
+ private VolumePolicy mVolumePolicy;
+ private boolean mShowDndTile = true;
+
+ public VolumeDialogController(Context context, ComponentName component) {
+ mContext = context.getApplicationContext();
+ Events.writeEvent(mContext, Events.EVENT_COLLECTION_STARTED);
+ mComponent = component;
+ mWorkerThread = new HandlerThread(VolumeDialogController.class.getSimpleName());
+ mWorkerThread.start();
+ mWorker = new W(mWorkerThread.getLooper());
+ mMediaSessions = createMediaSessions(mContext, mWorkerThread.getLooper(),
+ mMediaSessionsCallbacksW);
+ mAudio = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ mNoMan = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ mObserver = new SettingObserver(mWorker);
+ mObserver.init();
+ mReceiver.init();
+ mStreamTitles = mContext.getResources().getStringArray(R.array.volume_stream_titles);
+ mVibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
+ mHasVibrator = mVibrator != null && mVibrator.hasVibrator();
+ }
+
+ public AudioManager getAudioManager() {
+ return mAudio;
+ }
+
+ public ZenModeConfig getZenModeConfig() {
+ return mNoMan.getZenModeConfig();
+ }
+
+ public void dismiss() {
+ mCallbacks.onDismissRequested(Events.DISMISS_REASON_VOLUME_CONTROLLER);
+ }
+
+ public void register() {
+ try {
+ mAudio.setVolumeController(mVolumeController);
+ } catch (SecurityException e) {
+ Log.w(TAG, "Unable to set the volume controller", e);
+ return;
+ }
+ setVolumePolicy(mVolumePolicy);
+ showDndTile(mShowDndTile);
+ try {
+ mMediaSessions.init();
+ } catch (SecurityException e) {
+ Log.w(TAG, "No access to media sessions", e);
+ }
+ }
+
+ public void setVolumePolicy(VolumePolicy policy) {
+ mVolumePolicy = policy;
+ if (mVolumePolicy == null) return;
+ try {
+ mAudio.setVolumePolicy(mVolumePolicy);
+ } catch (NoSuchMethodError e) {
+ Log.w(TAG, "No volume policy api");
+ }
+ }
+
+ protected MediaSessions createMediaSessions(Context context, Looper looper,
+ MediaSessions.Callbacks callbacks) {
+ return new MediaSessions(context, looper, callbacks);
+ }
+
+ public void destroy() {
+ if (D.BUG) Log.d(TAG, "destroy");
+ if (mDestroyed) return;
+ mDestroyed = true;
+ Events.writeEvent(mContext, Events.EVENT_COLLECTION_STOPPED);
+ mMediaSessions.destroy();
+ mObserver.destroy();
+ mReceiver.destroy();
+ mWorkerThread.quitSafely();
+ }
+
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println(VolumeDialogController.class.getSimpleName() + " state:");
+ pw.print(" mEnabled: "); pw.println(mEnabled);
+ pw.print(" mDestroyed: "); pw.println(mDestroyed);
+ pw.print(" mVolumePolicy: "); pw.println(mVolumePolicy);
+ pw.print(" mState: "); pw.println(mState.toString(4));
+ pw.print(" mShowDndTile: "); pw.println(mShowDndTile);
+ pw.print(" mHasVibrator: "); pw.println(mHasVibrator);
+ pw.print(" mRemoteStreams: "); pw.println(mMediaSessionsCallbacksW.mRemoteStreams
+ .values());
+ pw.println();
+ mMediaSessions.dump(pw);
+ }
+
+ public void addCallback(Callbacks callback, Handler handler) {
+ mCallbacks.add(callback, handler);
+ }
+
+ public void removeCallback(Callbacks callback) {
+ mCallbacks.remove(callback);
+ }
+
+ public void getState() {
+ if (mDestroyed) return;
+ mWorker.sendEmptyMessage(W.GET_STATE);
+ }
+
+ public void notifyVisible(boolean visible) {
+ if (mDestroyed) return;
+ mWorker.obtainMessage(W.NOTIFY_VISIBLE, visible ? 1 : 0, 0).sendToTarget();
+ }
+
+ public void userActivity() {
+ if (mDestroyed) return;
+ mWorker.removeMessages(W.USER_ACTIVITY);
+ mWorker.sendEmptyMessage(W.USER_ACTIVITY);
+ }
+
+ public void setRingerMode(int value, boolean external) {
+ if (mDestroyed) return;
+ mWorker.obtainMessage(W.SET_RINGER_MODE, value, external ? 1 : 0).sendToTarget();
+ }
+
+ public void setZenMode(int value) {
+ if (mDestroyed) return;
+ mWorker.obtainMessage(W.SET_ZEN_MODE, value, 0).sendToTarget();
+ }
+
+ public void setExitCondition(Condition condition) {
+ if (mDestroyed) return;
+ mWorker.obtainMessage(W.SET_EXIT_CONDITION, condition).sendToTarget();
+ }
+
+ public void setStreamMute(int stream, boolean mute) {
+ if (mDestroyed) return;
+ mWorker.obtainMessage(W.SET_STREAM_MUTE, stream, mute ? 1 : 0).sendToTarget();
+ }
+
+ public void setStreamVolume(int stream, int level) {
+ if (mDestroyed) return;
+ mWorker.obtainMessage(W.SET_STREAM_VOLUME, stream, level).sendToTarget();
+ }
+
+ public void setActiveStream(int stream) {
+ if (mDestroyed) return;
+ mWorker.obtainMessage(W.SET_ACTIVE_STREAM, stream, 0).sendToTarget();
+ }
+
+ public void vibrate() {
+ if (mHasVibrator) {
+ mVibrator.vibrate(VIBRATE_HINT_DURATION);
+ }
+ }
+
+ public boolean hasVibrator() {
+ return mHasVibrator;
+ }
+
+ private void onNotifyVisibleW(boolean visible) {
+ if (mDestroyed) return;
+ mAudio.notifyVolumeControllerVisible(mVolumeController, visible);
+ if (!visible) {
+ if (updateActiveStreamW(-1)) {
+ mCallbacks.onStateChanged(mState);
+ }
+ }
+ }
+
+ protected void onUserActivityW() {
+ // hook for subclasses
+ }
+
+ private void onShowSafetyWarningW(int flags) {
+ mCallbacks.onShowSafetyWarning(flags);
+ }
+
+ private boolean checkRoutedToBluetoothW(int stream) {
+ boolean changed = false;
+ if (stream == AudioManager.STREAM_MUSIC) {
+ final boolean routedToBluetooth =
+ (mAudio.getDevicesForStream(AudioManager.STREAM_MUSIC) &
+ (AudioManager.DEVICE_OUT_BLUETOOTH_A2DP |
+ AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES |
+ AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER)) != 0;
+ changed |= updateStreamRoutedToBluetoothW(stream, routedToBluetooth);
+ }
+ return changed;
+ }
+
+ private void onVolumeChangedW(int stream, int flags) {
+ final boolean showUI = (flags & AudioManager.FLAG_SHOW_UI) != 0;
+ final boolean fromKey = (flags & AudioManager.FLAG_FROM_KEY) != 0;
+ final boolean showVibrateHint = (flags & AudioManager.FLAG_SHOW_VIBRATE_HINT) != 0;
+ final boolean showSilentHint = (flags & AudioManager.FLAG_SHOW_SILENT_HINT) != 0;
+ boolean changed = false;
+ if (showUI) {
+ changed |= updateActiveStreamW(stream);
+ }
+ int lastAudibleStreamVolume = mAudio.getLastAudibleStreamVolume(stream);
+ changed |= updateStreamLevelW(stream, lastAudibleStreamVolume);
+ changed |= checkRoutedToBluetoothW(showUI ? AudioManager.STREAM_MUSIC : stream);
+ if (changed) {
+ mCallbacks.onStateChanged(mState);
+ }
+ if (showUI) {
+ mCallbacks.onShowRequested(Events.SHOW_REASON_VOLUME_CHANGED);
+ }
+ if (showVibrateHint) {
+ mCallbacks.onShowVibrateHint();
+ }
+ if (showSilentHint) {
+ mCallbacks.onShowSilentHint();
+ }
+ if (changed && fromKey) {
+ Events.writeEvent(mContext, Events.EVENT_KEY, stream, lastAudibleStreamVolume);
+ }
+ }
+
+ private boolean updateActiveStreamW(int activeStream) {
+ if (activeStream == mState.activeStream) return false;
+ mState.activeStream = activeStream;
+ Events.writeEvent(mContext, Events.EVENT_ACTIVE_STREAM_CHANGED, activeStream);
+ if (D.BUG) Log.d(TAG, "updateActiveStreamW " + activeStream);
+ final int s = activeStream < DYNAMIC_STREAM_START_INDEX ? activeStream : -1;
+ if (D.BUG) Log.d(TAG, "forceVolumeControlStream " + s);
+ mAudio.forceVolumeControlStream(s);
+ return true;
+ }
+
+ private StreamState streamStateW(int stream) {
+ StreamState ss = mState.states.get(stream);
+ if (ss == null) {
+ ss = new StreamState();
+ mState.states.put(stream, ss);
+ }
+ return ss;
+ }
+
+ private void onGetStateW() {
+ for (int stream : STREAMS) {
+ updateStreamLevelW(stream, mAudio.getLastAudibleStreamVolume(stream));
+ streamStateW(stream).levelMin = mAudio.getStreamMinVolume(stream);
+ streamStateW(stream).levelMax = mAudio.getStreamMaxVolume(stream);
+ updateStreamMuteW(stream, mAudio.isStreamMute(stream));
+ final StreamState ss = streamStateW(stream);
+ ss.muteSupported = mAudio.isStreamAffectedByMute(stream);
+ ss.name = mStreamTitles[stream];
+ checkRoutedToBluetoothW(stream);
+ }
+ updateRingerModeExternalW(mAudio.getRingerMode());
+ updateZenModeW();
+ updateEffectsSuppressorW(mNoMan.getEffectsSuppressor());
+ updateZenModeConfigW();
+ mCallbacks.onStateChanged(mState);
+ }
+
+ private boolean updateStreamRoutedToBluetoothW(int stream, boolean routedToBluetooth) {
+ final StreamState ss = streamStateW(stream);
+ if (ss.routedToBluetooth == routedToBluetooth) return false;
+ ss.routedToBluetooth = routedToBluetooth;
+ if (D.BUG) Log.d(TAG, "updateStreamRoutedToBluetoothW stream=" + stream
+ + " routedToBluetooth=" + routedToBluetooth);
+ return true;
+ }
+
+ private boolean updateStreamLevelW(int stream, int level) {
+ final StreamState ss = streamStateW(stream);
+ if (ss.level == level) return false;
+ ss.level = level;
+ if (isLogWorthy(stream)) {
+ Events.writeEvent(mContext, Events.EVENT_LEVEL_CHANGED, stream, level);
+ }
+ return true;
+ }
+
+ private static boolean isLogWorthy(int stream) {
+ switch (stream) {
+ case AudioSystem.STREAM_ALARM:
+ case AudioSystem.STREAM_BLUETOOTH_SCO:
+ case AudioSystem.STREAM_MUSIC:
+ case AudioSystem.STREAM_RING:
+ case AudioSystem.STREAM_SYSTEM:
+ case AudioSystem.STREAM_VOICE_CALL:
+ return true;
+ }
+ return false;
+ }
+
+ private boolean updateStreamMuteW(int stream, boolean muted) {
+ final StreamState ss = streamStateW(stream);
+ if (ss.muted == muted) return false;
+ ss.muted = muted;
+ if (isLogWorthy(stream)) {
+ Events.writeEvent(mContext, Events.EVENT_MUTE_CHANGED, stream, muted);
+ }
+ if (muted && isRinger(stream)) {
+ updateRingerModeInternalW(mAudio.getRingerModeInternal());
+ }
+ return true;
+ }
+
+ private static boolean isRinger(int stream) {
+ return stream == AudioManager.STREAM_RING || stream == AudioManager.STREAM_NOTIFICATION;
+ }
+
+ private boolean updateZenModeConfigW() {
+ final ZenModeConfig zenModeConfig = getZenModeConfig();
+ if (Objects.equals(mState.zenModeConfig, zenModeConfig)) return false;
+ mState.zenModeConfig = zenModeConfig;
+ return true;
+ }
+
+ private boolean updateEffectsSuppressorW(ComponentName effectsSuppressor) {
+ if (Objects.equals(mState.effectsSuppressor, effectsSuppressor)) return false;
+ mState.effectsSuppressor = effectsSuppressor;
+ mState.effectsSuppressorName = getApplicationName(mContext, mState.effectsSuppressor);
+ Events.writeEvent(mContext, Events.EVENT_SUPPRESSOR_CHANGED, mState.effectsSuppressor,
+ mState.effectsSuppressorName);
+ return true;
+ }
+
+ private static String getApplicationName(Context context, ComponentName component) {
+ if (component == null) return null;
+ final PackageManager pm = context.getPackageManager();
+ final String pkg = component.getPackageName();
+ try {
+ final ApplicationInfo ai = pm.getApplicationInfo(pkg, 0);
+ final String rt = Objects.toString(ai.loadLabel(pm), "").trim();
+ if (rt.length() > 0) {
+ return rt;
+ }
+ } catch (NameNotFoundException e) {}
+ return pkg;
+ }
+
+ private boolean updateZenModeW() {
+ final int zen = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF);
+ if (mState.zenMode == zen) return false;
+ mState.zenMode = zen;
+ Events.writeEvent(mContext, Events.EVENT_ZEN_MODE_CHANGED, zen);
+ return true;
+ }
+
+ private boolean updateRingerModeExternalW(int rm) {
+ if (rm == mState.ringerModeExternal) return false;
+ mState.ringerModeExternal = rm;
+ Events.writeEvent(mContext, Events.EVENT_EXTERNAL_RINGER_MODE_CHANGED, rm);
+ return true;
+ }
+
+ private boolean updateRingerModeInternalW(int rm) {
+ if (rm == mState.ringerModeInternal) return false;
+ mState.ringerModeInternal = rm;
+ Events.writeEvent(mContext, Events.EVENT_INTERNAL_RINGER_MODE_CHANGED, rm);
+ return true;
+ }
+
+ private void onSetRingerModeW(int mode, boolean external) {
+ if (external) {
+ mAudio.setRingerMode(mode);
+ } else {
+ mAudio.setRingerModeInternal(mode);
+ }
+ }
+
+ private void onSetStreamMuteW(int stream, boolean mute) {
+ mAudio.adjustStreamVolume(stream, mute ? AudioManager.ADJUST_MUTE
+ : AudioManager.ADJUST_UNMUTE, 0);
+ }
+
+ private void onSetStreamVolumeW(int stream, int level) {
+ if (D.BUG) Log.d(TAG, "onSetStreamVolume " + stream + " level=" + level);
+ if (stream >= DYNAMIC_STREAM_START_INDEX) {
+ mMediaSessionsCallbacksW.setStreamVolume(stream, level);
+ return;
+ }
+ mAudio.setStreamVolume(stream, level, 0);
+ }
+
+ private void onSetActiveStreamW(int stream) {
+ boolean changed = updateActiveStreamW(stream);
+ if (changed) {
+ mCallbacks.onStateChanged(mState);
+ }
+ }
+
+ private void onSetExitConditionW(Condition condition) {
+ mNoMan.setZenMode(mState.zenMode, condition != null ? condition.id : null, TAG);
+ }
+
+ private void onSetZenModeW(int mode) {
+ if (D.BUG) Log.d(TAG, "onSetZenModeW " + mode);
+ mNoMan.setZenMode(mode, null, TAG);
+ }
+
+ private void onDismissRequestedW(int reason) {
+ mCallbacks.onDismissRequested(reason);
+ }
+
+ public void showDndTile(boolean visible) {
+ if (D.BUG) Log.d(TAG, "showDndTile");
+ DndTile.setVisible(mContext, visible);
+ }
+
+ private final class VC extends IVolumeController.Stub {
+ private final String TAG = VolumeDialogController.TAG + ".VC";
+
+ @Override
+ public void displaySafeVolumeWarning(int flags) throws RemoteException {
+ if (D.BUG) Log.d(TAG, "displaySafeVolumeWarning "
+ + Util.audioManagerFlagsToString(flags));
+ if (mDestroyed) return;
+ mWorker.obtainMessage(W.SHOW_SAFETY_WARNING, flags, 0).sendToTarget();
+ }
+
+ @Override
+ public void volumeChanged(int streamType, int flags) throws RemoteException {
+ if (D.BUG) Log.d(TAG, "volumeChanged " + AudioSystem.streamToString(streamType)
+ + " " + Util.audioManagerFlagsToString(flags));
+ if (mDestroyed) return;
+ mWorker.obtainMessage(W.VOLUME_CHANGED, streamType, flags).sendToTarget();
+ }
+
+ @Override
+ public void masterMuteChanged(int flags) throws RemoteException {
+ if (D.BUG) Log.d(TAG, "masterMuteChanged");
+ }
+
+ @Override
+ public void setLayoutDirection(int layoutDirection) throws RemoteException {
+ if (D.BUG) Log.d(TAG, "setLayoutDirection");
+ if (mDestroyed) return;
+ mWorker.obtainMessage(W.LAYOUT_DIRECTION_CHANGED, layoutDirection, 0).sendToTarget();
+ }
+
+ @Override
+ public void dismiss() throws RemoteException {
+ if (D.BUG) Log.d(TAG, "dismiss requested");
+ if (mDestroyed) return;
+ mWorker.obtainMessage(W.DISMISS_REQUESTED, Events.DISMISS_REASON_VOLUME_CONTROLLER, 0)
+ .sendToTarget();
+ mWorker.sendEmptyMessage(W.DISMISS_REQUESTED);
+ }
+ }
+
+ private final class W extends Handler {
+ private static final int VOLUME_CHANGED = 1;
+ private static final int DISMISS_REQUESTED = 2;
+ private static final int GET_STATE = 3;
+ private static final int SET_RINGER_MODE = 4;
+ private static final int SET_ZEN_MODE = 5;
+ private static final int SET_EXIT_CONDITION = 6;
+ private static final int SET_STREAM_MUTE = 7;
+ private static final int LAYOUT_DIRECTION_CHANGED = 8;
+ private static final int CONFIGURATION_CHANGED = 9;
+ private static final int SET_STREAM_VOLUME = 10;
+ private static final int SET_ACTIVE_STREAM = 11;
+ private static final int NOTIFY_VISIBLE = 12;
+ private static final int USER_ACTIVITY = 13;
+ private static final int SHOW_SAFETY_WARNING = 14;
+
+ W(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case VOLUME_CHANGED: onVolumeChangedW(msg.arg1, msg.arg2); break;
+ case DISMISS_REQUESTED: onDismissRequestedW(msg.arg1); break;
+ case GET_STATE: onGetStateW(); break;
+ case SET_RINGER_MODE: onSetRingerModeW(msg.arg1, msg.arg2 != 0); break;
+ case SET_ZEN_MODE: onSetZenModeW(msg.arg1); break;
+ case SET_EXIT_CONDITION: onSetExitConditionW((Condition) msg.obj); break;
+ case SET_STREAM_MUTE: onSetStreamMuteW(msg.arg1, msg.arg2 != 0); break;
+ case LAYOUT_DIRECTION_CHANGED: mCallbacks.onLayoutDirectionChanged(msg.arg1); break;
+ case CONFIGURATION_CHANGED: mCallbacks.onConfigurationChanged(); break;
+ case SET_STREAM_VOLUME: onSetStreamVolumeW(msg.arg1, msg.arg2); break;
+ case SET_ACTIVE_STREAM: onSetActiveStreamW(msg.arg1); break;
+ case NOTIFY_VISIBLE: onNotifyVisibleW(msg.arg1 != 0); break;
+ case USER_ACTIVITY: onUserActivityW(); break;
+ case SHOW_SAFETY_WARNING: onShowSafetyWarningW(msg.arg1); break;
+ }
+ }
+ }
+
+ private final class C implements Callbacks {
+ private final HashMap<Callbacks, Handler> mCallbackMap = new HashMap<>();
+
+ public void add(Callbacks callback, Handler handler) {
+ if (callback == null || handler == null) throw new IllegalArgumentException();
+ mCallbackMap.put(callback, handler);
+ }
+
+ public void remove(Callbacks callback) {
+ mCallbackMap.remove(callback);
+ }
+
+ @Override
+ public void onShowRequested(final int reason) {
+ for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
+ entry.getValue().post(new Runnable() {
+ @Override
+ public void run() {
+ entry.getKey().onShowRequested(reason);
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onDismissRequested(final int reason) {
+ for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
+ entry.getValue().post(new Runnable() {
+ @Override
+ public void run() {
+ entry.getKey().onDismissRequested(reason);
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onStateChanged(final State state) {
+ final long time = System.currentTimeMillis();
+ final State copy = state.copy();
+ for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
+ entry.getValue().post(new Runnable() {
+ @Override
+ public void run() {
+ entry.getKey().onStateChanged(copy);
+ }
+ });
+ }
+ Events.writeState(time, copy);
+ }
+
+ @Override
+ public void onLayoutDirectionChanged(final int layoutDirection) {
+ for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
+ entry.getValue().post(new Runnable() {
+ @Override
+ public void run() {
+ entry.getKey().onLayoutDirectionChanged(layoutDirection);
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onConfigurationChanged() {
+ for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
+ entry.getValue().post(new Runnable() {
+ @Override
+ public void run() {
+ entry.getKey().onConfigurationChanged();
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onShowVibrateHint() {
+ for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
+ entry.getValue().post(new Runnable() {
+ @Override
+ public void run() {
+ entry.getKey().onShowVibrateHint();
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onShowSilentHint() {
+ for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
+ entry.getValue().post(new Runnable() {
+ @Override
+ public void run() {
+ entry.getKey().onShowSilentHint();
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onScreenOff() {
+ for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
+ entry.getValue().post(new Runnable() {
+ @Override
+ public void run() {
+ entry.getKey().onScreenOff();
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onShowSafetyWarning(final int flags) {
+ for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
+ entry.getValue().post(new Runnable() {
+ @Override
+ public void run() {
+ entry.getKey().onShowSafetyWarning(flags);
+ }
+ });
+ }
+ }
+ }
+
+
+ private final class SettingObserver extends ContentObserver {
+ private final Uri SERVICE_URI = Settings.Secure.getUriFor(
+ Settings.Secure.VOLUME_CONTROLLER_SERVICE_COMPONENT);
+ private final Uri ZEN_MODE_URI =
+ Settings.Global.getUriFor(Settings.Global.ZEN_MODE);
+ private final Uri ZEN_MODE_CONFIG_URI =
+ Settings.Global.getUriFor(Settings.Global.ZEN_MODE_CONFIG_ETAG);
+
+ public SettingObserver(Handler handler) {
+ super(handler);
+ }
+
+ public void init() {
+ mContext.getContentResolver().registerContentObserver(SERVICE_URI, false, this);
+ mContext.getContentResolver().registerContentObserver(ZEN_MODE_URI, false, this);
+ mContext.getContentResolver().registerContentObserver(ZEN_MODE_CONFIG_URI, false, this);
+ onChange(true, SERVICE_URI);
+ }
+
+ public void destroy() {
+ mContext.getContentResolver().unregisterContentObserver(this);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ boolean changed = false;
+ if (SERVICE_URI.equals(uri)) {
+ final String setting = Settings.Secure.getString(mContext.getContentResolver(),
+ Settings.Secure.VOLUME_CONTROLLER_SERVICE_COMPONENT);
+ final boolean enabled = setting != null && mComponent != null
+ && mComponent.equals(ComponentName.unflattenFromString(setting));
+ if (enabled == mEnabled) return;
+ if (enabled) {
+ register();
+ }
+ mEnabled = enabled;
+ }
+ if (ZEN_MODE_URI.equals(uri)) {
+ changed = updateZenModeW();
+ }
+ if (ZEN_MODE_CONFIG_URI.equals(uri)) {
+ changed = updateZenModeConfigW();
+ }
+ if (changed) {
+ mCallbacks.onStateChanged(mState);
+ }
+ }
+ }
+
+ private final class Receiver extends BroadcastReceiver {
+
+ public void init() {
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
+ filter.addAction(AudioManager.STREAM_DEVICES_CHANGED_ACTION);
+ filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
+ filter.addAction(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION);
+ filter.addAction(AudioManager.STREAM_MUTE_CHANGED_ACTION);
+ filter.addAction(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED);
+ filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
+ filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+ mContext.registerReceiver(this, filter, null, mWorker);
+ }
+
+ public void destroy() {
+ mContext.unregisterReceiver(this);
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ boolean changed = false;
+ if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {
+ final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
+ final int level = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);
+ final int oldLevel = intent
+ .getIntExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, -1);
+ if (D.BUG) Log.d(TAG, "onReceive VOLUME_CHANGED_ACTION stream=" + stream
+ + " level=" + level + " oldLevel=" + oldLevel);
+ changed = updateStreamLevelW(stream, level);
+ } else if (action.equals(AudioManager.STREAM_DEVICES_CHANGED_ACTION)) {
+ final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
+ final int devices = intent
+ .getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_DEVICES, -1);
+ final int oldDevices = intent
+ .getIntExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_DEVICES, -1);
+ if (D.BUG) Log.d(TAG, "onReceive STREAM_DEVICES_CHANGED_ACTION stream="
+ + stream + " devices=" + devices + " oldDevices=" + oldDevices);
+ changed = checkRoutedToBluetoothW(stream);
+ } else if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) {
+ final int rm = intent.getIntExtra(AudioManager.EXTRA_RINGER_MODE, -1);
+ if (D.BUG) Log.d(TAG, "onReceive RINGER_MODE_CHANGED_ACTION rm="
+ + Util.ringerModeToString(rm));
+ changed = updateRingerModeExternalW(rm);
+ } else if (action.equals(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION)) {
+ final int rm = intent.getIntExtra(AudioManager.EXTRA_RINGER_MODE, -1);
+ if (D.BUG) Log.d(TAG, "onReceive INTERNAL_RINGER_MODE_CHANGED_ACTION rm="
+ + Util.ringerModeToString(rm));
+ changed = updateRingerModeInternalW(rm);
+ } else if (action.equals(AudioManager.STREAM_MUTE_CHANGED_ACTION)) {
+ final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
+ final boolean muted = intent
+ .getBooleanExtra(AudioManager.EXTRA_STREAM_VOLUME_MUTED, false);
+ if (D.BUG) Log.d(TAG, "onReceive STREAM_MUTE_CHANGED_ACTION stream=" + stream
+ + " muted=" + muted);
+ changed = updateStreamMuteW(stream, muted);
+ } else if (action.equals(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED)) {
+ if (D.BUG) Log.d(TAG, "onReceive ACTION_EFFECTS_SUPPRESSOR_CHANGED");
+ changed = updateEffectsSuppressorW(mNoMan.getEffectsSuppressor());
+ } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
+ if (D.BUG) Log.d(TAG, "onReceive ACTION_CONFIGURATION_CHANGED");
+ mCallbacks.onConfigurationChanged();
+ } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
+ if (D.BUG) Log.d(TAG, "onReceive ACTION_SCREEN_OFF");
+ mCallbacks.onScreenOff();
+ } else if (action.equals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) {
+ if (D.BUG) Log.d(TAG, "onReceive ACTION_CLOSE_SYSTEM_DIALOGS");
+ dismiss();
+ }
+ if (changed) {
+ mCallbacks.onStateChanged(mState);
+ }
+ }
+ }
+
+ private final class MediaSessionsCallbacks implements MediaSessions.Callbacks {
+ private final HashMap<Token, Integer> mRemoteStreams = new HashMap<>();
+
+ private int mNextStream = DYNAMIC_STREAM_START_INDEX;
+
+ @Override
+ public void onRemoteUpdate(Token token, String name, PlaybackInfo pi) {
+ if (!mRemoteStreams.containsKey(token)) {
+ mRemoteStreams.put(token, mNextStream);
+ if (D.BUG) Log.d(TAG, "onRemoteUpdate: " + name + " is stream " + mNextStream);
+ mNextStream++;
+ }
+ final int stream = mRemoteStreams.get(token);
+ boolean changed = mState.states.indexOfKey(stream) < 0;
+ final StreamState ss = streamStateW(stream);
+ ss.dynamic = true;
+ ss.levelMin = 0;
+ ss.levelMax = pi.getMaxVolume();
+ if (ss.level != pi.getCurrentVolume()) {
+ ss.level = pi.getCurrentVolume();
+ changed = true;
+ }
+ if (!Objects.equals(ss.name, name)) {
+ ss.name = name;
+ changed = true;
+ }
+ if (changed) {
+ if (D.BUG) Log.d(TAG, "onRemoteUpdate: " + name + ": " + ss.level
+ + " of " + ss.levelMax);
+ mCallbacks.onStateChanged(mState);
+ }
+ }
+
+ @Override
+ public void onRemoteVolumeChanged(Token token, int flags) {
+ final int stream = mRemoteStreams.get(token);
+ final boolean showUI = (flags & AudioManager.FLAG_SHOW_UI) != 0;
+ boolean changed = updateActiveStreamW(stream);
+ if (showUI) {
+ changed |= checkRoutedToBluetoothW(AudioManager.STREAM_MUSIC);
+ }
+ if (changed) {
+ mCallbacks.onStateChanged(mState);
+ }
+ if (showUI) {
+ mCallbacks.onShowRequested(Events.SHOW_REASON_REMOTE_VOLUME_CHANGED);
+ }
+ }
+
+ @Override
+ public void onRemoteRemoved(Token token) {
+ final int stream = mRemoteStreams.get(token);
+ mState.states.remove(stream);
+ if (mState.activeStream == stream) {
+ updateActiveStreamW(-1);
+ }
+ mCallbacks.onStateChanged(mState);
+ }
+
+ public void setStreamVolume(int stream, int level) {
+ final Token t = findToken(stream);
+ if (t == null) {
+ Log.w(TAG, "setStreamVolume: No token found for stream: " + stream);
+ return;
+ }
+ mMediaSessions.setVolume(t, level);
+ }
+
+ private Token findToken(int stream) {
+ for (Map.Entry<Token, Integer> entry : mRemoteStreams.entrySet()) {
+ if (entry.getValue().equals(stream)) {
+ return entry.getKey();
+ }
+ }
+ return null;
+ }
+ }
+
+ public static final class StreamState {
+ public boolean dynamic;
+ public int level;
+ public int levelMin;
+ public int levelMax;
+ public boolean muted;
+ public boolean muteSupported;
+ public String name;
+ public boolean routedToBluetooth;
+
+ public StreamState copy() {
+ final StreamState rt = new StreamState();
+ rt.dynamic = dynamic;
+ rt.level = level;
+ rt.levelMin = levelMin;
+ rt.levelMax = levelMax;
+ rt.muted = muted;
+ rt.muteSupported = muteSupported;
+ rt.name = name;
+ rt.routedToBluetooth = routedToBluetooth;
+ return rt;
+ }
+ }
+
+ public static final class State {
+ public static int NO_ACTIVE_STREAM = -1;
+
+ public final SparseArray<StreamState> states = new SparseArray<StreamState>();
+
+ public int ringerModeInternal;
+ public int ringerModeExternal;
+ public int zenMode;
+ public ComponentName effectsSuppressor;
+ public String effectsSuppressorName;
+ public ZenModeConfig zenModeConfig;
+ public int activeStream = NO_ACTIVE_STREAM;
+
+ public State copy() {
+ final State rt = new State();
+ for (int i = 0; i < states.size(); i++) {
+ rt.states.put(states.keyAt(i), states.valueAt(i).copy());
+ }
+ rt.ringerModeExternal = ringerModeExternal;
+ rt.ringerModeInternal = ringerModeInternal;
+ rt.zenMode = zenMode;
+ if (effectsSuppressor != null) rt.effectsSuppressor = effectsSuppressor.clone();
+ rt.effectsSuppressorName = effectsSuppressorName;
+ if (zenModeConfig != null) rt.zenModeConfig = zenModeConfig.copy();
+ rt.activeStream = activeStream;
+ return rt;
+ }
+
+ @Override
+ public String toString() {
+ return toString(0);
+ }
+
+ public String toString(int indent) {
+ final StringBuilder sb = new StringBuilder("{");
+ if (indent > 0) sep(sb, indent);
+ for (int i = 0; i < states.size(); i++) {
+ if (i > 0) {
+ sep(sb, indent);
+ }
+ final int stream = states.keyAt(i);
+ final StreamState ss = states.valueAt(i);
+ sb.append(AudioSystem.streamToString(stream)).append(":").append(ss.level)
+ .append('[').append(ss.levelMin).append("..").append(ss.levelMax)
+ .append(']');
+ if (ss.muted) sb.append(" [MUTED]");
+ }
+ sep(sb, indent); sb.append("ringerModeExternal:").append(ringerModeExternal);
+ sep(sb, indent); sb.append("ringerModeInternal:").append(ringerModeInternal);
+ sep(sb, indent); sb.append("zenMode:").append(zenMode);
+ sep(sb, indent); sb.append("effectsSuppressor:").append(effectsSuppressor);
+ sep(sb, indent); sb.append("effectsSuppressorName:").append(effectsSuppressorName);
+ sep(sb, indent); sb.append("zenModeConfig:").append(zenModeConfig);
+ sep(sb, indent); sb.append("activeStream:").append(activeStream);
+ if (indent > 0) sep(sb, indent);
+ return sb.append('}').toString();
+ }
+
+ private static void sep(StringBuilder sb, int indent) {
+ if (indent > 0) {
+ sb.append('\n');
+ for (int i = 0; i < indent; i++) {
+ sb.append(' ');
+ }
+ } else {
+ sb.append(',');
+ }
+ }
+
+ public Condition getManualExitCondition() {
+ return zenModeConfig != null && zenModeConfig.manualRule != null
+ ? zenModeConfig.manualRule.condition : null;
+ }
+ }
+
+ public interface Callbacks {
+ void onShowRequested(int reason);
+ void onDismissRequested(int reason);
+ void onStateChanged(State state);
+ void onLayoutDirectionChanged(int layoutDirection);
+ void onConfigurationChanged();
+ void onShowVibrateHint();
+ void onShowSilentHint();
+ void onScreenOff();
+ void onShowSafetyWarning(int flags);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogMotion.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogMotion.java
new file mode 100644
index 0000000..4bb1011
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogMotion.java
@@ -0,0 +1,313 @@
+/*
+ * 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.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 static final int POST_SHOW_DELAY = 200;
+
+ 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);
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ startShowAnimation();
+ }
+ }, POST_SHOW_DELAY);
+ }
+ });
+ }
+
+ 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(new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ if (mChevronPositionAnimator == null) return;
+ // reposition chevron
+ final float v = (Float) mChevronPositionAnimator.getAnimatedValue();
+ final int posY = chevronPosY();
+ mChevron.setTranslationY(posY + v + -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);
+ }
+
+ private static final class LogDecelerateInterpolator implements TimeInterpolator {
+ private final float mBase;
+ private final float mDrift;
+ private final float mTimeScale;
+ private final float mOutputScale;
+
+ private 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;
+ }
+ }
+
+ private static final class LogAccelerateInterpolator implements TimeInterpolator {
+ private final int mBase;
+ private final int mDrift;
+ private final float mLogScale;
+
+ private 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/VolumePanel.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
deleted file mode 100644
index acdcfc1..0000000
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
+++ /dev/null
@@ -1,1664 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.ValueAnimator;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnDismissListener;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.content.pm.ServiceInfo;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.PixelFormat;
-import android.graphics.drawable.ColorDrawable;
-import android.media.AudioAttributes;
-import android.media.AudioManager;
-import android.media.AudioService;
-import android.media.AudioSystem;
-import android.media.RingtoneManager;
-import android.media.ToneGenerator;
-import android.media.VolumeProvider;
-import android.media.session.MediaController;
-import android.media.session.MediaController.PlaybackInfo;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.Debug;
-import android.os.Handler;
-import android.os.Message;
-import android.os.Vibrator;
-import android.util.Log;
-import android.util.SparseArray;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.view.Window;
-import android.view.WindowManager;
-import android.view.WindowManager.LayoutParams;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
-import android.widget.ImageView;
-import android.widget.SeekBar;
-import android.widget.SeekBar.OnSeekBarChangeListener;
-import android.widget.TextView;
-
-import com.android.internal.R;
-import com.android.systemui.DemoMode;
-import com.android.systemui.statusbar.phone.SystemUIDialog;
-import com.android.systemui.statusbar.policy.ZenModeController;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-
-/**
- * Handles the user interface for the volume keys.
- *
- * @hide
- */
-public class VolumePanel extends Handler implements DemoMode {
- private static final String TAG = "VolumePanel";
- private static boolean LOGD = Log.isLoggable(TAG, Log.DEBUG);
-
- private static final int PLAY_SOUND_DELAY = AudioService.PLAY_SOUND_DELAY;
-
- /**
- * The delay before vibrating. This small period exists so if the user is
- * moving to silent mode, it will not emit a short vibrate (it normally
- * would since vibrate is between normal mode and silent mode using hardware
- * keys).
- */
- public static final int VIBRATE_DELAY = 300;
-
- private static final int VIBRATE_DURATION = 300;
- private static final int BEEP_DURATION = 150;
- private static final int MAX_VOLUME = 100;
- private static final int FREE_DELAY = 10000;
- private static final int TIMEOUT_DELAY = 3000;
- private static final int TIMEOUT_DELAY_SHORT = 1500;
- private static final int TIMEOUT_DELAY_COLLAPSED = 4500;
- private static final int TIMEOUT_DELAY_SAFETY_WARNING = 5000;
- private static final int TIMEOUT_DELAY_EXPANDED = 10000;
-
- private static final int MSG_VOLUME_CHANGED = 0;
- private static final int MSG_FREE_RESOURCES = 1;
- private static final int MSG_PLAY_SOUND = 2;
- private static final int MSG_STOP_SOUNDS = 3;
- private static final int MSG_VIBRATE = 4;
- private static final int MSG_TIMEOUT = 5;
- private static final int MSG_RINGER_MODE_CHANGED = 6;
- private static final int MSG_MUTE_CHANGED = 7;
- private static final int MSG_REMOTE_VOLUME_CHANGED = 8;
- private static final int MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN = 9;
- private static final int MSG_SLIDER_VISIBILITY_CHANGED = 10;
- private static final int MSG_DISPLAY_SAFE_VOLUME_WARNING = 11;
- private static final int MSG_LAYOUT_DIRECTION = 12;
- private static final int MSG_ZEN_MODE_AVAILABLE_CHANGED = 13;
- private static final int MSG_USER_ACTIVITY = 14;
- private static final int MSG_NOTIFICATION_EFFECTS_SUPPRESSOR_CHANGED = 15;
- private static final int MSG_INTERNAL_RINGER_MODE_CHANGED = 16;
-
- // Pseudo stream type for master volume
- private static final int STREAM_MASTER = -100;
- // Pseudo stream type for remote volume
- private static final int STREAM_REMOTE_MUSIC = -200;
-
- private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
- .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
- .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
- .build();
-
- private static final int IC_AUDIO_VOL = com.android.systemui.R.drawable.ic_audio_vol;
- private static final int IC_AUDIO_VOL_MUTE = com.android.systemui.R.drawable.ic_audio_vol_mute;
- private static final int IC_AUDIO_BT = com.android.systemui.R.drawable.ic_audio_bt;
- private static final int IC_AUDIO_BT_MUTE = com.android.systemui.R.drawable.ic_audio_bt_mute;
-
- private final String mTag;
- protected final Context mContext;
- private final AudioManager mAudioManager;
- private final ZenModeController mZenController;
- private boolean mRingIsSilent;
- private boolean mVoiceCapable;
- private boolean mZenModeAvailable;
- private boolean mZenPanelExpanded;
- private int mTimeoutDelay = TIMEOUT_DELAY;
- private float mDisabledAlpha;
- private int mLastRingerMode = AudioManager.RINGER_MODE_NORMAL;
- private int mLastRingerProgress = 0;
- private int mDemoIcon;
-
- // True if we want to play tones on the system stream when the master stream is specified.
- private final boolean mPlayMasterStreamTones;
-
-
- /** Volume panel content view */
- private final View mView;
- /** Dialog hosting the panel */
- private final Dialog mDialog;
-
- /** The visible portion of the volume overlay */
- private final ViewGroup mPanel;
- /** Contains the slider and its touchable icons */
- private final ViewGroup mSliderPanel;
- /** The zen mode configuration panel view */
- private ZenModePanel mZenPanel;
- /** The component currently suppressing notification stream effects */
- private ComponentName mNotificationEffectsSuppressor;
-
- private Callback mCallback;
-
- /** Currently active stream that shows up at the top of the list of sliders */
- private int mActiveStreamType = -1;
- /** All the slider controls mapped by stream type */
- private SparseArray<StreamControl> mStreamControls;
- private final AccessibilityManager mAccessibilityManager;
- private final SecondaryIconTransition mSecondaryIconTransition;
- private final IconPulser mIconPulser;
-
- private enum StreamResources {
- BluetoothSCOStream(AudioManager.STREAM_BLUETOOTH_SCO,
- R.string.volume_icon_description_bluetooth,
- IC_AUDIO_BT,
- IC_AUDIO_BT_MUTE,
- false),
- RingerStream(AudioManager.STREAM_RING,
- R.string.volume_icon_description_ringer,
- com.android.systemui.R.drawable.ic_ringer_audible,
- com.android.systemui.R.drawable.ic_ringer_mute,
- false),
- VoiceStream(AudioManager.STREAM_VOICE_CALL,
- R.string.volume_icon_description_incall,
- com.android.systemui.R.drawable.ic_audio_phone,
- com.android.systemui.R.drawable.ic_audio_phone,
- false),
- AlarmStream(AudioManager.STREAM_ALARM,
- R.string.volume_alarm,
- com.android.systemui.R.drawable.ic_audio_alarm,
- com.android.systemui.R.drawable.ic_audio_alarm_mute,
- false),
- MediaStream(AudioManager.STREAM_MUSIC,
- R.string.volume_icon_description_media,
- IC_AUDIO_VOL,
- IC_AUDIO_VOL_MUTE,
- true),
- NotificationStream(AudioManager.STREAM_NOTIFICATION,
- R.string.volume_icon_description_notification,
- com.android.systemui.R.drawable.ic_ringer_audible,
- com.android.systemui.R.drawable.ic_ringer_mute,
- true),
- // for now, use media resources for master volume
- MasterStream(STREAM_MASTER,
- R.string.volume_icon_description_media, //FIXME should have its own description
- IC_AUDIO_VOL,
- IC_AUDIO_VOL_MUTE,
- false),
- RemoteStream(STREAM_REMOTE_MUSIC,
- R.string.volume_icon_description_media, //FIXME should have its own description
- com.android.systemui.R.drawable.ic_audio_remote,
- com.android.systemui.R.drawable.ic_audio_remote,
- false);// will be dynamically updated
-
- int streamType;
- int descRes;
- int iconRes;
- int iconMuteRes;
- // RING, VOICE_CALL & BLUETOOTH_SCO are hidden unless explicitly requested
- boolean show;
-
- StreamResources(int streamType, int descRes, int iconRes, int iconMuteRes, boolean show) {
- this.streamType = streamType;
- this.descRes = descRes;
- this.iconRes = iconRes;
- this.iconMuteRes = iconMuteRes;
- this.show = show;
- }
- }
-
- // List of stream types and their order
- private static final StreamResources[] STREAMS = {
- StreamResources.BluetoothSCOStream,
- StreamResources.RingerStream,
- StreamResources.VoiceStream,
- StreamResources.MediaStream,
- StreamResources.NotificationStream,
- StreamResources.AlarmStream,
- StreamResources.MasterStream,
- StreamResources.RemoteStream
- };
-
- /** Object that contains data for each slider */
- private class StreamControl {
- int streamType;
- MediaController controller;
- ViewGroup group;
- ImageView icon;
- SeekBar seekbarView;
- TextView suppressorView;
- View divider;
- ImageView secondaryIcon;
- int iconRes;
- int iconMuteRes;
- int iconSuppressedRes;
- }
-
- // Synchronize when accessing this
- private ToneGenerator mToneGenerators[];
- private Vibrator mVibrator;
- private boolean mHasVibrator;
-
- private static AlertDialog sSafetyWarning;
- private static Object sSafetyWarningLock = new Object();
-
- private static class SafetyWarning extends SystemUIDialog
- implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener {
- private final Context mContext;
- private final VolumePanel mVolumePanel;
- private final AudioManager mAudioManager;
-
- private boolean mNewVolumeUp;
-
- SafetyWarning(Context context, VolumePanel volumePanel, AudioManager audioManager) {
- super(context);
- mContext = context;
- mVolumePanel = volumePanel;
- mAudioManager = audioManager;
-
- setMessage(mContext.getString(com.android.internal.R.string.safe_media_volume_warning));
- setButton(DialogInterface.BUTTON_POSITIVE,
- mContext.getString(com.android.internal.R.string.yes), this);
- setButton(DialogInterface.BUTTON_NEGATIVE,
- mContext.getString(com.android.internal.R.string.no), (OnClickListener) null);
- setOnDismissListener(this);
-
- IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
- context.registerReceiver(mReceiver, filter);
- }
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_VOLUME_UP && event.getRepeatCount() == 0) {
- mNewVolumeUp = true;
- }
- return super.onKeyDown(keyCode, event);
- }
-
- @Override
- public boolean onKeyUp(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_VOLUME_UP && mNewVolumeUp) {
- if (LOGD) Log.d(TAG, "Confirmed warning via VOLUME_UP");
- mAudioManager.disableSafeMediaVolume();
- dismiss();
- }
- return super.onKeyUp(keyCode, event);
- }
-
- @Override
- public void onClick(DialogInterface dialog, int which) {
- mAudioManager.disableSafeMediaVolume();
- }
-
- @Override
- public void onDismiss(DialogInterface unused) {
- mContext.unregisterReceiver(mReceiver);
- cleanUp();
- }
-
- private void cleanUp() {
- synchronized (sSafetyWarningLock) {
- sSafetyWarning = null;
- }
- mVolumePanel.forceTimeout(0);
- mVolumePanel.updateStates();
- }
-
- private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
- if (LOGD) Log.d(TAG, "Received ACTION_CLOSE_SYSTEM_DIALOGS");
- cancel();
- cleanUp();
- }
- }
- };
- }
-
- public VolumePanel(Context context, ZenModeController zenController) {
- mTag = String.format("%s.%08x", TAG, hashCode());
- mContext = context;
- mZenController = zenController;
- mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
- mAccessibilityManager = (AccessibilityManager) context.getSystemService(
- Context.ACCESSIBILITY_SERVICE);
- mSecondaryIconTransition = new SecondaryIconTransition();
- mIconPulser = new IconPulser(context);
-
- // For now, only show master volume if master volume is supported
- final Resources res = context.getResources();
- final boolean useMasterVolume = res.getBoolean(R.bool.config_useMasterVolume);
- if (useMasterVolume) {
- for (int i = 0; i < STREAMS.length; i++) {
- StreamResources streamRes = STREAMS[i];
- streamRes.show = (streamRes.streamType == STREAM_MASTER);
- }
- }
- if (LOGD) Log.d(mTag, "new VolumePanel");
-
- mDisabledAlpha = 0.5f;
- if (mContext.getTheme() != null) {
- final TypedArray arr = mContext.getTheme().obtainStyledAttributes(
- new int[] { android.R.attr.disabledAlpha });
- mDisabledAlpha = arr.getFloat(0, mDisabledAlpha);
- arr.recycle();
- }
-
- mDialog = new Dialog(context) {
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- if (isShowing() && event.getAction() == MotionEvent.ACTION_OUTSIDE &&
- sSafetyWarning == null) {
- forceTimeout(0);
- return true;
- }
- return false;
- }
- };
-
- final Window window = mDialog.getWindow();
- window.requestFeature(Window.FEATURE_NO_TITLE);
- mDialog.setCanceledOnTouchOutside(true);
- mDialog.setContentView(com.android.systemui.R.layout.volume_dialog);
- mDialog.setOnDismissListener(new OnDismissListener() {
- @Override
- public void onDismiss(DialogInterface dialog) {
- mActiveStreamType = -1;
- mAudioManager.forceVolumeControlStream(mActiveStreamType);
- setZenPanelVisible(false);
- mDemoIcon = 0;
- mSecondaryIconTransition.cancel();
- }
- });
-
- mDialog.create();
-
- final LayoutParams lp = window.getAttributes();
- lp.token = null;
- lp.y = res.getDimensionPixelOffset(com.android.systemui.R.dimen.volume_panel_top);
- lp.type = LayoutParams.TYPE_STATUS_BAR_PANEL;
- lp.format = PixelFormat.TRANSLUCENT;
- lp.windowAnimations = com.android.systemui.R.style.VolumePanelAnimation;
- lp.setTitle(TAG);
- window.setAttributes(lp);
-
- updateWidth();
-
- window.setBackgroundDrawable(new ColorDrawable(0x00000000));
- window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
- window.addFlags(LayoutParams.FLAG_NOT_FOCUSABLE
- | LayoutParams.FLAG_NOT_TOUCH_MODAL
- | LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
- | LayoutParams.FLAG_HARDWARE_ACCELERATED);
- mView = window.findViewById(R.id.content);
- Interaction.register(mView, new Interaction.Callback() {
- @Override
- public void onInteraction() {
- resetTimeout();
- }
- });
-
- mPanel = (ViewGroup) mView.findViewById(com.android.systemui.R.id.visible_panel);
- mSliderPanel = (ViewGroup) mView.findViewById(com.android.systemui.R.id.slider_panel);
- mZenPanel = (ZenModePanel) mView.findViewById(com.android.systemui.R.id.zen_mode_panel);
- initZenModePanel();
-
- mToneGenerators = new ToneGenerator[AudioSystem.getNumStreamTypes()];
- mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
- mHasVibrator = mVibrator != null && mVibrator.hasVibrator();
- mVoiceCapable = context.getResources().getBoolean(R.bool.config_voice_capable);
-
- if (mZenController != null && !useMasterVolume) {
- mZenModeAvailable = mZenController.isZenAvailable();
- mNotificationEffectsSuppressor = mZenController.getEffectsSuppressor();
- mZenController.addCallback(mZenCallback);
- }
-
- final boolean masterVolumeOnly = res.getBoolean(R.bool.config_useMasterVolume);
- final boolean masterVolumeKeySounds = res.getBoolean(R.bool.config_useVolumeKeySounds);
- mPlayMasterStreamTones = masterVolumeOnly && masterVolumeKeySounds;
-
- registerReceiver();
- }
-
- public void onConfigurationChanged(Configuration newConfig) {
- updateWidth();
- if (mZenPanel != null) {
- mZenPanel.updateLocale();
- }
- }
-
- private void updateWidth() {
- final Resources res = mContext.getResources();
- final LayoutParams lp = mDialog.getWindow().getAttributes();
- lp.width = res.getDimensionPixelSize(com.android.systemui.R.dimen.notification_panel_width);
- lp.gravity =
- res.getInteger(com.android.systemui.R.integer.notification_panel_layout_gravity);
- mDialog.getWindow().setAttributes(lp);
- }
-
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println("VolumePanel state:");
- pw.print(" mTag="); pw.println(mTag);
- pw.print(" mRingIsSilent="); pw.println(mRingIsSilent);
- pw.print(" mVoiceCapable="); pw.println(mVoiceCapable);
- pw.print(" mHasVibrator="); pw.println(mHasVibrator);
- pw.print(" mZenModeAvailable="); pw.println(mZenModeAvailable);
- pw.print(" mZenPanelExpanded="); pw.println(mZenPanelExpanded);
- pw.print(" mNotificationEffectsSuppressor="); pw.println(mNotificationEffectsSuppressor);
- pw.print(" mTimeoutDelay="); pw.println(mTimeoutDelay);
- pw.print(" mDisabledAlpha="); pw.println(mDisabledAlpha);
- pw.print(" mLastRingerMode="); pw.println(mLastRingerMode);
- pw.print(" mLastRingerProgress="); pw.println(mLastRingerProgress);
- pw.print(" mPlayMasterStreamTones="); pw.println(mPlayMasterStreamTones);
- pw.print(" isShowing()="); pw.println(isShowing());
- pw.print(" mCallback="); pw.println(mCallback);
- pw.print(" sConfirmSafeVolumeDialog=");
- pw.println(sSafetyWarning != null ? "<not null>" : null);
- pw.print(" mActiveStreamType="); pw.println(mActiveStreamType);
- pw.print(" mStreamControls=");
- if (mStreamControls == null) {
- pw.println("null");
- } else {
- final int N = mStreamControls.size();
- pw.print("<size "); pw.print(N); pw.println('>');
- for (int i = 0; i < N; i++) {
- final StreamControl sc = mStreamControls.valueAt(i);
- pw.print(" stream "); pw.print(sc.streamType); pw.print(":");
- if (sc.seekbarView != null) {
- pw.print(" progress="); pw.print(sc.seekbarView.getProgress());
- pw.print(" of "); pw.print(sc.seekbarView.getMax());
- if (!sc.seekbarView.isEnabled()) pw.print(" (disabled)");
- }
- if (sc.icon != null && sc.icon.isClickable()) pw.print(" (clickable)");
- pw.println();
- }
- }
- if (mZenPanel != null) {
- mZenPanel.dump(fd, pw, args);
- }
- }
-
- private void initZenModePanel() {
- mZenPanel.init(mZenController);
- mZenPanel.setCallback(new ZenModePanel.Callback() {
- @Override
- public void onMoreSettings() {
- if (mCallback != null) {
- mCallback.onZenSettings();
- }
- }
-
- @Override
- public void onInteraction() {
- resetTimeout();
- }
-
- @Override
- public void onExpanded(boolean expanded) {
- if (mZenPanelExpanded == expanded) return;
- mZenPanelExpanded = expanded;
- updateTimeoutDelay();
- resetTimeout();
- }
- });
- }
-
- private void setLayoutDirection(int layoutDirection) {
- mPanel.setLayoutDirection(layoutDirection);
- updateStates();
- }
-
- private void registerReceiver() {
- final IntentFilter filter = new IntentFilter();
- filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
- filter.addAction(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION);
- filter.addAction(Intent.ACTION_SCREEN_OFF);
- mContext.registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
-
- if (AudioManager.RINGER_MODE_CHANGED_ACTION.equals(action)) {
- removeMessages(MSG_RINGER_MODE_CHANGED);
- sendEmptyMessage(MSG_RINGER_MODE_CHANGED);
- }
-
- if (AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION.equals(action)) {
- removeMessages(MSG_INTERNAL_RINGER_MODE_CHANGED);
- sendEmptyMessage(MSG_INTERNAL_RINGER_MODE_CHANGED);
- }
-
- if (Intent.ACTION_SCREEN_OFF.equals(action)) {
- postDismiss(0);
- }
- }
- }, filter);
- }
-
- private boolean isMuted(int streamType) {
- if (streamType == STREAM_MASTER) {
- return mAudioManager.isMasterMute();
- } else if (streamType == STREAM_REMOTE_MUSIC) {
- // TODO do we need to support a distinct mute property for remote?
- return false;
- } else {
- return mAudioManager.isStreamMute(streamType);
- }
- }
-
- private int getStreamMaxVolume(int streamType) {
- if (streamType == STREAM_MASTER) {
- return mAudioManager.getMasterMaxVolume();
- } else if (streamType == STREAM_REMOTE_MUSIC) {
- if (mStreamControls != null) {
- StreamControl sc = mStreamControls.get(streamType);
- if (sc != null && sc.controller != null) {
- PlaybackInfo ai = sc.controller.getPlaybackInfo();
- return ai.getMaxVolume();
- }
- }
- return -1;
- } else {
- return mAudioManager.getStreamMaxVolume(streamType);
- }
- }
-
- private int getStreamVolume(int streamType) {
- if (streamType == STREAM_MASTER) {
- return mAudioManager.getMasterVolume();
- } else if (streamType == STREAM_REMOTE_MUSIC) {
- if (mStreamControls != null) {
- StreamControl sc = mStreamControls.get(streamType);
- if (sc != null && sc.controller != null) {
- PlaybackInfo ai = sc.controller.getPlaybackInfo();
- return ai.getCurrentVolume();
- }
- }
- return -1;
- } else {
- return mAudioManager.getStreamVolume(streamType);
- }
- }
-
- private void setStreamVolume(StreamControl sc, int index, int flags) {
- if (sc.streamType == STREAM_REMOTE_MUSIC) {
- if (sc.controller != null) {
- sc.controller.setVolumeTo(index, flags);
- } else {
- Log.w(mTag, "Adjusting remote volume without a controller!");
- }
- } else if (getStreamVolume(sc.streamType) != index) {
- if (sc.streamType == STREAM_MASTER) {
- mAudioManager.setMasterVolume(index, flags);
- } else {
- mAudioManager.setStreamVolume(sc.streamType, index, flags);
- }
- }
- }
-
- private void createSliders() {
- final Resources res = mContext.getResources();
- final LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
- Context.LAYOUT_INFLATER_SERVICE);
-
- mStreamControls = new SparseArray<StreamControl>(STREAMS.length);
-
- final StreamResources notificationStream = StreamResources.NotificationStream;
- for (int i = 0; i < STREAMS.length; i++) {
- StreamResources streamRes = STREAMS[i];
-
- final int streamType = streamRes.streamType;
- final boolean isNotification = isNotificationOrRing(streamType);
-
- final StreamControl sc = new StreamControl();
- sc.streamType = streamType;
- sc.group = (ViewGroup) inflater.inflate(
- com.android.systemui.R.layout.volume_panel_item, null);
- sc.group.setTag(sc);
- sc.icon = (ImageView) sc.group.findViewById(com.android.systemui.R.id.stream_icon);
- sc.icon.setTag(sc);
- sc.icon.setContentDescription(res.getString(streamRes.descRes));
- sc.iconRes = streamRes.iconRes;
- sc.iconMuteRes = streamRes.iconMuteRes;
- sc.icon.setImageResource(sc.iconRes);
- sc.icon.setClickable(isNotification && mHasVibrator);
- if (isNotification) {
- if (mHasVibrator) {
- sc.icon.setSoundEffectsEnabled(false);
- sc.iconMuteRes = com.android.systemui.R.drawable.ic_ringer_vibrate;
- sc.icon.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- resetTimeout();
- toggleRinger(sc);
- }
- });
- }
- sc.iconSuppressedRes = com.android.systemui.R.drawable.ic_ringer_mute;
- }
- sc.seekbarView = (SeekBar) sc.group.findViewById(com.android.systemui.R.id.seekbar);
- sc.suppressorView =
- (TextView) sc.group.findViewById(com.android.systemui.R.id.suppressor);
- sc.suppressorView.setVisibility(View.GONE);
- final boolean showSecondary = !isNotification && notificationStream.show;
- sc.divider = sc.group.findViewById(com.android.systemui.R.id.divider);
- sc.secondaryIcon = (ImageView) sc.group
- .findViewById(com.android.systemui.R.id.secondary_icon);
- sc.secondaryIcon.setImageResource(com.android.systemui.R.drawable.ic_ringer_audible);
- sc.secondaryIcon.setContentDescription(res.getString(notificationStream.descRes));
- sc.secondaryIcon.setClickable(showSecondary);
- sc.divider.setVisibility(showSecondary ? View.VISIBLE : View.GONE);
- sc.secondaryIcon.setVisibility(showSecondary ? View.VISIBLE : View.GONE);
- if (showSecondary) {
- sc.secondaryIcon.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- mSecondaryIconTransition.start(sc);
- }
- });
- }
- final int plusOne = (streamType == AudioSystem.STREAM_BLUETOOTH_SCO ||
- streamType == AudioSystem.STREAM_VOICE_CALL) ? 1 : 0;
- sc.seekbarView.setMax(getStreamMaxVolume(streamType) + plusOne);
- sc.seekbarView.setOnSeekBarChangeListener(mSeekListener);
- sc.seekbarView.setTag(sc);
- mStreamControls.put(streamType, sc);
- }
- }
-
- private void toggleRinger(StreamControl sc) {
- if (!mHasVibrator) return;
- if (mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_NORMAL) {
- mAudioManager.setRingerModeInternal(AudioManager.RINGER_MODE_VIBRATE);
- postVolumeChanged(sc.streamType, AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE);
- } else {
- mAudioManager.setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL);
- postVolumeChanged(sc.streamType, AudioManager.FLAG_PLAY_SOUND);
- }
- }
-
- private void reorderSliders(int activeStreamType) {
- mSliderPanel.removeAllViews();
-
- final StreamControl active = mStreamControls.get(activeStreamType);
- if (active == null) {
- Log.e(TAG, "Missing stream type! - " + activeStreamType);
- mActiveStreamType = -1;
- } else {
- mSliderPanel.addView(active.group);
- mActiveStreamType = activeStreamType;
- active.group.setVisibility(View.VISIBLE);
- updateSlider(active, true /*forceReloadIcon*/);
- updateTimeoutDelay();
- updateZenPanelVisible();
- }
- }
-
- private void updateSliderProgress(StreamControl sc, int progress) {
- final boolean isRinger = isNotificationOrRing(sc.streamType);
- if (isRinger && mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_SILENT) {
- progress = mLastRingerProgress;
- }
- if (progress < 0) {
- progress = getStreamVolume(sc.streamType);
- }
- sc.seekbarView.setProgress(progress);
- if (isRinger) {
- mLastRingerProgress = progress;
- }
- }
-
- private void updateSliderIcon(StreamControl sc, boolean muted) {
- ComponentName suppressor = null;
- if (isNotificationOrRing(sc.streamType)) {
- suppressor = mNotificationEffectsSuppressor;
- int ringerMode = mAudioManager.getRingerModeInternal();
- if (ringerMode == AudioManager.RINGER_MODE_SILENT) {
- ringerMode = mLastRingerMode;
- } else {
- mLastRingerMode = ringerMode;
- }
- if (mHasVibrator) {
- muted = ringerMode == AudioManager.RINGER_MODE_VIBRATE;
- } else {
- muted = false;
- }
- }
- sc.icon.setImageResource(mDemoIcon != 0 ? mDemoIcon
- : suppressor != null ? sc.iconSuppressedRes
- : muted ? sc.iconMuteRes
- : sc.iconRes);
- }
-
- private void updateSliderSuppressor(StreamControl sc) {
- final ComponentName suppressor = isNotificationOrRing(sc.streamType)
- ? mNotificationEffectsSuppressor : null;
- if (suppressor == null) {
- sc.seekbarView.setVisibility(View.VISIBLE);
- sc.suppressorView.setVisibility(View.GONE);
- } else {
- sc.seekbarView.setVisibility(View.GONE);
- sc.suppressorView.setVisibility(View.VISIBLE);
- sc.suppressorView.setText(mContext.getString(R.string.muted_by,
- getSuppressorCaption(suppressor)));
- }
- }
-
- private String getSuppressorCaption(ComponentName suppressor) {
- final PackageManager pm = mContext.getPackageManager();
- try {
- final ServiceInfo info = pm.getServiceInfo(suppressor, 0);
- if (info != null) {
- final CharSequence seq = info.loadLabel(pm);
- if (seq != null) {
- final String str = seq.toString().trim();
- if (str.length() > 0) {
- return str;
- }
- }
- }
- } catch (Throwable e) {
- Log.w(TAG, "Error loading suppressor caption", e);
- }
- return suppressor.getPackageName();
- }
-
- /** Update the mute and progress state of a slider */
- private void updateSlider(StreamControl sc, boolean forceReloadIcon) {
- updateSliderProgress(sc, -1);
- final boolean muted = isMuted(sc.streamType);
- if (forceReloadIcon) {
- sc.icon.setImageDrawable(null);
- }
- updateSliderIcon(sc, muted);
- updateSliderEnabled(sc, muted, false);
- updateSliderSuppressor(sc);
- }
-
- private void updateSliderEnabled(final StreamControl sc, boolean muted, boolean fixedVolume) {
- final boolean wasEnabled = sc.seekbarView.isEnabled();
- final boolean isRinger = isNotificationOrRing(sc.streamType);
- if (sc.streamType == STREAM_REMOTE_MUSIC) {
- // never disable touch interactions for remote playback, the muting is not tied to
- // the state of the phone.
- sc.seekbarView.setEnabled(!fixedVolume);
- } else if (isRinger && mNotificationEffectsSuppressor != null) {
- sc.icon.setEnabled(true);
- sc.icon.setAlpha(1f);
- sc.icon.setClickable(false);
- } else if (isRinger
- && mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_SILENT) {
- sc.seekbarView.setEnabled(false);
- sc.icon.setEnabled(false);
- sc.icon.setAlpha(mDisabledAlpha);
- sc.icon.setClickable(false);
- } else if (fixedVolume ||
- (sc.streamType != mAudioManager.getMasterStreamType() && !isRinger && muted) ||
- (sSafetyWarning != null)) {
- sc.seekbarView.setEnabled(false);
- } else {
- sc.seekbarView.setEnabled(true);
- sc.icon.setEnabled(true);
- sc.icon.setAlpha(1f);
- }
- // show the silent hint when the disabled slider is touched in silent mode
- if (isRinger && wasEnabled != sc.seekbarView.isEnabled()) {
- if (sc.seekbarView.isEnabled()) {
- sc.group.setOnTouchListener(null);
- sc.icon.setClickable(mHasVibrator);
- } else {
- final View.OnTouchListener showHintOnTouch = new View.OnTouchListener() {
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- resetTimeout();
- showSilentHint();
- return false;
- }
- };
- sc.group.setOnTouchListener(showHintOnTouch);
- }
- }
- }
-
- private void showSilentHint() {
- if (mZenPanel != null) {
- mZenPanel.showSilentHint();
- }
- }
-
- private void showVibrateHint() {
- final StreamControl active = mStreamControls.get(mActiveStreamType);
- if (active != null) {
- mIconPulser.start(active.icon);
- if (!hasMessages(MSG_VIBRATE)) {
- sendEmptyMessageDelayed(MSG_VIBRATE, VIBRATE_DELAY);
- }
- }
- }
-
- private static boolean isNotificationOrRing(int streamType) {
- return streamType == AudioManager.STREAM_RING
- || streamType == AudioManager.STREAM_NOTIFICATION;
- }
-
- public void setCallback(Callback callback) {
- mCallback = callback;
- }
-
- private void updateTimeoutDelay() {
- mTimeoutDelay = mDemoIcon != 0 ? TIMEOUT_DELAY_EXPANDED
- : sSafetyWarning != null ? TIMEOUT_DELAY_SAFETY_WARNING
- : mActiveStreamType == AudioManager.STREAM_MUSIC ? TIMEOUT_DELAY_SHORT
- : mZenPanelExpanded ? TIMEOUT_DELAY_EXPANDED
- : isZenPanelVisible() ? TIMEOUT_DELAY_COLLAPSED
- : TIMEOUT_DELAY;
- }
-
- private boolean isZenPanelVisible() {
- return mZenPanel != null && mZenPanel.getVisibility() == View.VISIBLE;
- }
-
- private void setZenPanelVisible(boolean visible) {
- if (LOGD) Log.d(mTag, "setZenPanelVisible " + visible + " mZenPanel=" + mZenPanel);
- final boolean changing = visible != isZenPanelVisible();
- if (visible) {
- mZenPanel.setHidden(false);
- resetTimeout();
- } else {
- mZenPanel.setHidden(true);
- }
- if (changing) {
- updateTimeoutDelay();
- resetTimeout();
- }
- }
-
- private void updateStates() {
- final int count = mSliderPanel.getChildCount();
- for (int i = 0; i < count; i++) {
- StreamControl sc = (StreamControl) mSliderPanel.getChildAt(i).getTag();
- updateSlider(sc, true /*forceReloadIcon*/);
- }
- }
-
- private void updateActiveSlider() {
- final StreamControl active = mStreamControls.get(mActiveStreamType);
- if (active != null) {
- updateSlider(active, false /*forceReloadIcon*/);
- }
- }
-
- private void updateZenPanelVisible() {
- setZenPanelVisible(mZenModeAvailable && isNotificationOrRing(mActiveStreamType));
- }
-
- public void postVolumeChanged(int streamType, int flags) {
- if (hasMessages(MSG_VOLUME_CHANGED)) return;
- synchronized (this) {
- if (mStreamControls == null) {
- createSliders();
- }
- }
- removeMessages(MSG_FREE_RESOURCES);
- obtainMessage(MSG_VOLUME_CHANGED, streamType, flags).sendToTarget();
- }
-
- public void postRemoteVolumeChanged(MediaController controller, int flags) {
- if (hasMessages(MSG_REMOTE_VOLUME_CHANGED)) return;
- synchronized (this) {
- if (mStreamControls == null) {
- createSliders();
- }
- }
- removeMessages(MSG_FREE_RESOURCES);
- obtainMessage(MSG_REMOTE_VOLUME_CHANGED, flags, 0, controller).sendToTarget();
- }
-
- public void postRemoteSliderVisibility(boolean visible) {
- obtainMessage(MSG_SLIDER_VISIBILITY_CHANGED,
- STREAM_REMOTE_MUSIC, visible ? 1 : 0).sendToTarget();
- }
-
- /**
- * Called by AudioService when it has received new remote playback information that
- * would affect the VolumePanel display (mainly volumes). The difference with
- * {@link #postRemoteVolumeChanged(int, int)} is that the handling of the posted message
- * (MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN) will only update the volume slider if it is being
- * displayed.
- * This special code path is due to the fact that remote volume updates arrive to AudioService
- * asynchronously. So after AudioService has sent the volume update (which should be treated
- * as a request to update the volume), the application will likely set a new volume. If the UI
- * is still up, we need to refresh the display to show this new value.
- */
- public void postHasNewRemotePlaybackInfo() {
- if (hasMessages(MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN)) return;
- // don't create or prevent resources to be freed, if they disappear, this update came too
- // late and shouldn't warrant the panel to be displayed longer
- obtainMessage(MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN).sendToTarget();
- }
-
- public void postMasterVolumeChanged(int flags) {
- postVolumeChanged(STREAM_MASTER, flags);
- }
-
- public void postMuteChanged(int streamType, int flags) {
- if (hasMessages(MSG_VOLUME_CHANGED)) return;
- synchronized (this) {
- if (mStreamControls == null) {
- createSliders();
- }
- }
- removeMessages(MSG_FREE_RESOURCES);
- obtainMessage(MSG_MUTE_CHANGED, streamType, flags).sendToTarget();
- }
-
- public void postMasterMuteChanged(int flags) {
- postMuteChanged(STREAM_MASTER, flags);
- }
-
- public void postDisplaySafeVolumeWarning(int flags) {
- if (hasMessages(MSG_DISPLAY_SAFE_VOLUME_WARNING)) return;
- obtainMessage(MSG_DISPLAY_SAFE_VOLUME_WARNING, flags, 0).sendToTarget();
- }
-
- public void postDismiss(long delay) {
- forceTimeout(delay);
- }
-
- public void postLayoutDirection(int layoutDirection) {
- removeMessages(MSG_LAYOUT_DIRECTION);
- obtainMessage(MSG_LAYOUT_DIRECTION, layoutDirection, 0).sendToTarget();
- }
-
- private static String flagsToString(int flags) {
- return flags == 0 ? "0" : (flags + "=" + AudioManager.flagsToString(flags));
- }
-
- private static String streamToString(int stream) {
- return AudioService.streamToString(stream);
- }
-
- /**
- * Override this if you have other work to do when the volume changes (for
- * example, vibrating, playing a sound, etc.). Make sure to call through to
- * the superclass implementation.
- */
- protected void onVolumeChanged(int streamType, int flags) {
-
- if (LOGD) Log.d(mTag, "onVolumeChanged(streamType: " + streamToString(streamType)
- + ", flags: " + flagsToString(flags) + ")");
-
- if ((flags & AudioManager.FLAG_SHOW_UI) != 0) {
- synchronized (this) {
- if (mActiveStreamType != streamType) {
- reorderSliders(streamType);
- }
- onShowVolumeChanged(streamType, flags, null);
- }
- }
-
- if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 && ! mRingIsSilent) {
- removeMessages(MSG_PLAY_SOUND);
- sendMessageDelayed(obtainMessage(MSG_PLAY_SOUND, streamType, flags), PLAY_SOUND_DELAY);
- }
-
- if ((flags & AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE) != 0) {
- removeMessages(MSG_PLAY_SOUND);
- removeMessages(MSG_VIBRATE);
- onStopSounds();
- }
-
- removeMessages(MSG_FREE_RESOURCES);
- sendMessageDelayed(obtainMessage(MSG_FREE_RESOURCES), FREE_DELAY);
- resetTimeout();
- }
-
- protected void onMuteChanged(int streamType, int flags) {
-
- if (LOGD) Log.d(mTag, "onMuteChanged(streamType: " + streamToString(streamType)
- + ", flags: " + flagsToString(flags) + ")");
-
- StreamControl sc = mStreamControls.get(streamType);
- if (sc != null) {
- updateSliderIcon(sc, isMuted(sc.streamType));
- }
-
- onVolumeChanged(streamType, flags);
- }
-
- protected void onShowVolumeChanged(int streamType, int flags, MediaController controller) {
- int index = getStreamVolume(streamType);
-
- mRingIsSilent = false;
-
- if (LOGD) {
- Log.d(mTag, "onShowVolumeChanged(streamType: " + streamToString(streamType)
- + ", flags: " + flagsToString(flags) + "), index: " + index);
- }
-
- // get max volume for progress bar
-
- int max = getStreamMaxVolume(streamType);
- StreamControl sc = mStreamControls.get(streamType);
-
- switch (streamType) {
-
- case AudioManager.STREAM_RING: {
- Uri ringuri = RingtoneManager.getActualDefaultRingtoneUri(
- mContext, RingtoneManager.TYPE_RINGTONE);
- if (ringuri == null) {
- mRingIsSilent = true;
- }
- break;
- }
-
- case AudioManager.STREAM_MUSIC: {
- // Special case for when Bluetooth is active for music
- if ((mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC) &
- (AudioManager.DEVICE_OUT_BLUETOOTH_A2DP |
- AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES |
- AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER)) != 0) {
- setMusicIcon(IC_AUDIO_BT, IC_AUDIO_BT_MUTE);
- } else {
- setMusicIcon(IC_AUDIO_VOL, IC_AUDIO_VOL_MUTE);
- }
- break;
- }
-
- case AudioManager.STREAM_VOICE_CALL: {
- /*
- * For in-call voice call volume, there is no inaudible volume.
- * Rescale the UI control so the progress bar doesn't go all
- * the way to zero and don't show the mute icon.
- */
- index++;
- max++;
- break;
- }
-
- case AudioManager.STREAM_ALARM: {
- break;
- }
-
- case AudioManager.STREAM_NOTIFICATION: {
- Uri ringuri = RingtoneManager.getActualDefaultRingtoneUri(
- mContext, RingtoneManager.TYPE_NOTIFICATION);
- if (ringuri == null) {
- mRingIsSilent = true;
- }
- break;
- }
-
- case AudioManager.STREAM_BLUETOOTH_SCO: {
- /*
- * For in-call voice call volume, there is no inaudible volume.
- * Rescale the UI control so the progress bar doesn't go all
- * the way to zero and don't show the mute icon.
- */
- index++;
- max++;
- break;
- }
-
- case STREAM_REMOTE_MUSIC: {
- if (controller == null && sc != null) {
- // If we weren't passed one try using the last one set.
- controller = sc.controller;
- }
- if (controller == null) {
- // We still don't have one, ignore the command.
- Log.w(mTag, "sent remote volume change without a controller!");
- } else {
- PlaybackInfo vi = controller.getPlaybackInfo();
- index = vi.getCurrentVolume();
- max = vi.getMaxVolume();
- if ((vi.getVolumeControl() & VolumeProvider.VOLUME_CONTROL_FIXED) != 0) {
- // if the remote volume is fixed add the flag for the UI
- flags |= AudioManager.FLAG_FIXED_VOLUME;
- }
- }
- if (LOGD) { Log.d(mTag, "showing remote volume "+index+" over "+ max); }
- break;
- }
- }
-
- if (sc != null) {
- if (streamType == STREAM_REMOTE_MUSIC && controller != sc.controller) {
- if (sc.controller != null) {
- sc.controller.unregisterCallback(mMediaControllerCb);
- }
- sc.controller = controller;
- if (controller != null) {
- sc.controller.registerCallback(mMediaControllerCb);
- }
- }
- if (sc.seekbarView.getMax() != max) {
- sc.seekbarView.setMax(max);
- }
- updateSliderProgress(sc, index);
- final boolean muted = isMuted(streamType);
- updateSliderEnabled(sc, muted, (flags & AudioManager.FLAG_FIXED_VOLUME) != 0);
- if (isNotificationOrRing(streamType)) {
- // check for secondary-icon transition completion
- if (mSecondaryIconTransition.isRunning()) {
- mSecondaryIconTransition.cancel(); // safe to reset
- sc.seekbarView.setAlpha(0); sc.seekbarView.animate().alpha(1);
- mZenPanel.setAlpha(0); mZenPanel.animate().alpha(1);
- }
- updateSliderIcon(sc, muted);
- }
- }
-
- if (!isShowing()) {
- int stream = (streamType == STREAM_REMOTE_MUSIC) ? -1 : streamType;
- // when the stream is for remote playback, use -1 to reset the stream type evaluation
- if (stream != STREAM_MASTER) {
- mAudioManager.forceVolumeControlStream(stream);
- }
- mDialog.show();
- if (mCallback != null) {
- mCallback.onVisible(true);
- }
- announceDialogShown();
- }
-
- // Do a little vibrate if applicable (only when going into vibrate mode)
- if ((streamType != STREAM_REMOTE_MUSIC) &&
- ((flags & AudioManager.FLAG_VIBRATE) != 0) &&
- isNotificationOrRing(streamType) &&
- mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_VIBRATE) {
- sendMessageDelayed(obtainMessage(MSG_VIBRATE), VIBRATE_DELAY);
- }
-
- // Pulse the zen icon if an adjustment was suppressed due to silent mode.
- if ((flags & AudioManager.FLAG_SHOW_SILENT_HINT) != 0) {
- showSilentHint();
- }
-
- // Pulse the slider icon & vibrate if an adjustment down was suppressed due to vibrate mode.
- if ((flags & AudioManager.FLAG_SHOW_VIBRATE_HINT) != 0) {
- showVibrateHint();
- }
- }
-
- private void announceDialogShown() {
- mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
- }
-
- private boolean isShowing() {
- return mDialog.isShowing();
- }
-
- protected void onPlaySound(int streamType, int flags) {
-
- if (hasMessages(MSG_STOP_SOUNDS)) {
- removeMessages(MSG_STOP_SOUNDS);
- // Force stop right now
- onStopSounds();
- }
-
- synchronized (this) {
- ToneGenerator toneGen = getOrCreateToneGenerator(streamType);
- if (toneGen != null) {
- toneGen.startTone(ToneGenerator.TONE_PROP_BEEP);
- sendMessageDelayed(obtainMessage(MSG_STOP_SOUNDS), BEEP_DURATION);
- }
- }
- }
-
- protected void onStopSounds() {
-
- synchronized (this) {
- int numStreamTypes = AudioSystem.getNumStreamTypes();
- for (int i = numStreamTypes - 1; i >= 0; i--) {
- ToneGenerator toneGen = mToneGenerators[i];
- if (toneGen != null) {
- toneGen.stopTone();
- }
- }
- }
- }
-
- protected void onVibrate() {
-
- // Make sure we ended up in vibrate ringer mode
- if (mAudioManager.getRingerModeInternal() != AudioManager.RINGER_MODE_VIBRATE) {
- return;
- }
- if (mVibrator != null) {
- mVibrator.vibrate(VIBRATE_DURATION, VIBRATION_ATTRIBUTES);
- }
- }
-
- protected void onRemoteVolumeChanged(MediaController controller, int flags) {
- if (LOGD) Log.d(mTag, "onRemoteVolumeChanged(controller:" + controller + ", flags: "
- + flagsToString(flags) + ")");
-
- if (((flags & AudioManager.FLAG_SHOW_UI) != 0) || isShowing()) {
- synchronized (this) {
- if (mActiveStreamType != STREAM_REMOTE_MUSIC) {
- reorderSliders(STREAM_REMOTE_MUSIC);
- }
- onShowVolumeChanged(STREAM_REMOTE_MUSIC, flags, controller);
- }
- } else {
- if (LOGD) Log.d(mTag, "not calling onShowVolumeChanged(), no FLAG_SHOW_UI or no UI");
- }
-
- removeMessages(MSG_FREE_RESOURCES);
- sendMessageDelayed(obtainMessage(MSG_FREE_RESOURCES), FREE_DELAY);
- resetTimeout();
- }
-
- protected void onRemoteVolumeUpdateIfShown() {
- if (LOGD) Log.d(mTag, "onRemoteVolumeUpdateIfShown()");
- if (isShowing()
- && (mActiveStreamType == STREAM_REMOTE_MUSIC)
- && (mStreamControls != null)) {
- onShowVolumeChanged(STREAM_REMOTE_MUSIC, 0, null);
- }
- }
-
- /**
- * Clear the current remote stream controller.
- */
- private void clearRemoteStreamController() {
- if (mStreamControls != null) {
- StreamControl sc = mStreamControls.get(STREAM_REMOTE_MUSIC);
- if (sc != null) {
- if (sc.controller != null) {
- sc.controller.unregisterCallback(mMediaControllerCb);
- sc.controller = null;
- }
- }
- }
- }
-
- /**
- * Handler for MSG_SLIDER_VISIBILITY_CHANGED Hide or show a slider
- *
- * @param streamType can be a valid stream type value, or
- * VolumePanel.STREAM_MASTER, or VolumePanel.STREAM_REMOTE_MUSIC
- * @param visible
- */
- synchronized protected void onSliderVisibilityChanged(int streamType, int visible) {
- if (LOGD) Log.d(mTag, "onSliderVisibilityChanged(stream="+streamType+", visi="+visible+")");
- boolean isVisible = (visible == 1);
- for (int i = STREAMS.length - 1 ; i >= 0 ; i--) {
- StreamResources streamRes = STREAMS[i];
- if (streamRes.streamType == streamType) {
- streamRes.show = isVisible;
- if (!isVisible && (mActiveStreamType == streamType)) {
- mActiveStreamType = -1;
- }
- break;
- }
- }
- }
-
- protected void onDisplaySafeVolumeWarning(int flags) {
- if ((flags & (AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_SHOW_UI_WARNINGS)) != 0
- || isShowing()) {
- synchronized (sSafetyWarningLock) {
- if (sSafetyWarning != null) {
- return;
- }
- sSafetyWarning = new SafetyWarning(mContext, this, mAudioManager);
- sSafetyWarning.show();
- }
- updateStates();
- }
- if (mAccessibilityManager.isTouchExplorationEnabled()) {
- removeMessages(MSG_TIMEOUT);
- } else {
- updateTimeoutDelay();
- resetTimeout();
- }
- }
-
- /**
- * Lock on this VolumePanel instance as long as you use the returned ToneGenerator.
- */
- private ToneGenerator getOrCreateToneGenerator(int streamType) {
- if (streamType == STREAM_MASTER) {
- // For devices that use the master volume setting only but still want to
- // play a volume-changed tone, direct the master volume pseudostream to
- // the system stream's tone generator.
- if (mPlayMasterStreamTones) {
- streamType = AudioManager.STREAM_SYSTEM;
- } else {
- return null;
- }
- }
- synchronized (this) {
- if (mToneGenerators[streamType] == null) {
- try {
- mToneGenerators[streamType] = new ToneGenerator(streamType, MAX_VOLUME);
- } catch (RuntimeException e) {
- if (LOGD) {
- Log.d(mTag, "ToneGenerator constructor failed with "
- + "RuntimeException: " + e);
- }
- }
- }
- return mToneGenerators[streamType];
- }
- }
-
-
- /**
- * Switch between icons because Bluetooth music is same as music volume, but with
- * different icons.
- */
- private void setMusicIcon(int resId, int resMuteId) {
- StreamControl sc = mStreamControls.get(AudioManager.STREAM_MUSIC);
- if (sc != null) {
- sc.iconRes = resId;
- sc.iconMuteRes = resMuteId;
- updateSliderIcon(sc, isMuted(sc.streamType));
- }
- }
-
- protected void onFreeResources() {
- synchronized (this) {
- for (int i = mToneGenerators.length - 1; i >= 0; i--) {
- if (mToneGenerators[i] != null) {
- mToneGenerators[i].release();
- }
- mToneGenerators[i] = null;
- }
- }
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
-
- case MSG_VOLUME_CHANGED: {
- onVolumeChanged(msg.arg1, msg.arg2);
- break;
- }
-
- case MSG_MUTE_CHANGED: {
- onMuteChanged(msg.arg1, msg.arg2);
- break;
- }
-
- case MSG_FREE_RESOURCES: {
- onFreeResources();
- break;
- }
-
- case MSG_STOP_SOUNDS: {
- onStopSounds();
- break;
- }
-
- case MSG_PLAY_SOUND: {
- onPlaySound(msg.arg1, msg.arg2);
- break;
- }
-
- case MSG_VIBRATE: {
- onVibrate();
- break;
- }
-
- case MSG_TIMEOUT: {
- if (isShowing()) {
- mDialog.dismiss();
- clearRemoteStreamController();
- mActiveStreamType = -1;
- if (mCallback != null) {
- mCallback.onVisible(false);
- }
- }
- synchronized (sSafetyWarningLock) {
- if (sSafetyWarning != null) {
- if (LOGD) Log.d(mTag, "SafetyWarning timeout");
- sSafetyWarning.dismiss();
- }
- }
- break;
- }
-
- case MSG_RINGER_MODE_CHANGED:
- case MSG_INTERNAL_RINGER_MODE_CHANGED:
- case MSG_NOTIFICATION_EFFECTS_SUPPRESSOR_CHANGED: {
- if (isShowing()) {
- updateActiveSlider();
- }
- break;
- }
-
- case MSG_REMOTE_VOLUME_CHANGED: {
- onRemoteVolumeChanged((MediaController) msg.obj, msg.arg1);
- break;
- }
-
- case MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN:
- onRemoteVolumeUpdateIfShown();
- break;
-
- case MSG_SLIDER_VISIBILITY_CHANGED:
- onSliderVisibilityChanged(msg.arg1, msg.arg2);
- break;
-
- case MSG_DISPLAY_SAFE_VOLUME_WARNING:
- onDisplaySafeVolumeWarning(msg.arg1);
- break;
-
- case MSG_LAYOUT_DIRECTION:
- setLayoutDirection(msg.arg1);
- break;
-
- case MSG_ZEN_MODE_AVAILABLE_CHANGED:
- mZenModeAvailable = msg.arg1 != 0;
- updateZenPanelVisible();
- break;
-
- case MSG_USER_ACTIVITY:
- if (mCallback != null) {
- mCallback.onInteraction();
- }
- break;
- }
- }
-
- private void resetTimeout() {
- final boolean touchExploration = mAccessibilityManager.isTouchExplorationEnabled();
- if (LOGD) Log.d(mTag, "resetTimeout at " + System.currentTimeMillis()
- + " delay=" + mTimeoutDelay + " touchExploration=" + touchExploration);
- if (sSafetyWarning == null || !touchExploration) {
- removeMessages(MSG_TIMEOUT);
- sendEmptyMessageDelayed(MSG_TIMEOUT, mTimeoutDelay);
- removeMessages(MSG_USER_ACTIVITY);
- sendEmptyMessage(MSG_USER_ACTIVITY);
- }
- }
-
- private void forceTimeout(long delay) {
- if (LOGD) Log.d(mTag, "forceTimeout delay=" + delay + " callers=" + Debug.getCallers(3));
- removeMessages(MSG_TIMEOUT);
- sendEmptyMessageDelayed(MSG_TIMEOUT, delay);
- }
-
- public ZenModeController getZenController() {
- return mZenController;
- }
-
- @Override
- public void dispatchDemoCommand(String command, Bundle args) {
- if (!COMMAND_VOLUME.equals(command)) return;
- String icon = args.getString("icon");
- final String iconMute = args.getString("iconmute");
- final boolean mute = iconMute != null;
- icon = mute ? iconMute : icon;
- icon = icon.endsWith("Stream") ? icon : (icon + "Stream");
- final StreamResources sr = StreamResources.valueOf(icon);
- mDemoIcon = mute ? sr.iconMuteRes : sr.iconRes;
- final int forcedStreamType = StreamResources.MediaStream.streamType;
- mAudioManager.forceVolumeControlStream(forcedStreamType);
- mAudioManager.adjustStreamVolume(forcedStreamType, AudioManager.ADJUST_SAME,
- AudioManager.FLAG_SHOW_UI);
- }
-
- private final OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() {
- @Override
- public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
- final Object tag = seekBar.getTag();
- if (fromUser && tag instanceof StreamControl) {
- StreamControl sc = (StreamControl) tag;
- setStreamVolume(sc, progress,
- AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE);
- }
- resetTimeout();
- }
-
- @Override
- public void onStartTrackingTouch(SeekBar seekBar) {
- }
-
- @Override
- public void onStopTrackingTouch(SeekBar seekBar) {
- }
- };
-
- private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() {
- @Override
- public void onZenAvailableChanged(boolean available) {
- obtainMessage(MSG_ZEN_MODE_AVAILABLE_CHANGED, available ? 1 : 0, 0).sendToTarget();
- }
-
- @Override
- public void onEffectsSupressorChanged() {
- mNotificationEffectsSuppressor = mZenController.getEffectsSuppressor();
- sendEmptyMessage(MSG_NOTIFICATION_EFFECTS_SUPPRESSOR_CHANGED);
- }
- };
-
- private final MediaController.Callback mMediaControllerCb = new MediaController.Callback() {
- public void onAudioInfoChanged(PlaybackInfo info) {
- onRemoteVolumeUpdateIfShown();
- }
- };
-
- private final class SecondaryIconTransition extends AnimatorListenerAdapter
- implements Runnable {
- private static final int ANIMATION_TIME = 400;
- private static final int WAIT_FOR_SWITCH_TIME = 1000;
-
- private final int mAnimationTime = (int)(ANIMATION_TIME * ValueAnimator.getDurationScale());
- private final int mFadeOutTime = mAnimationTime / 2;
- private final int mDelayTime = mAnimationTime / 3;
-
- private final Interpolator mIconInterpolator =
- AnimationUtils.loadInterpolator(mContext, android.R.interpolator.fast_out_slow_in);
-
- private StreamControl mTarget;
-
- public void start(StreamControl sc) {
- if (sc == null) throw new IllegalArgumentException();
- if (LOGD) Log.d(mTag, "Secondary icon animation start");
- if (mTarget != null) {
- cancel();
- }
- mTarget = sc;
- mTimeoutDelay = mAnimationTime + WAIT_FOR_SWITCH_TIME;
- resetTimeout();
- mTarget.secondaryIcon.setClickable(false);
- final int N = mTarget.group.getChildCount();
- for (int i = 0; i < N; i++) {
- final View child = mTarget.group.getChildAt(i);
- if (child != mTarget.secondaryIcon) {
- child.animate().alpha(0).setDuration(mFadeOutTime).start();
- }
- }
- mTarget.secondaryIcon.animate()
- .translationXBy(mTarget.icon.getX() - mTarget.secondaryIcon.getX())
- .setInterpolator(mIconInterpolator)
- .setStartDelay(mDelayTime)
- .setDuration(mAnimationTime - mDelayTime)
- .setListener(this)
- .start();
- }
-
- public boolean isRunning() {
- return mTarget != null;
- }
-
- public void cancel() {
- if (mTarget == null) return;
- mTarget.secondaryIcon.setClickable(true);
- final int N = mTarget.group.getChildCount();
- for (int i = 0; i < N; i++) {
- final View child = mTarget.group.getChildAt(i);
- if (child != mTarget.secondaryIcon) {
- child.animate().cancel();
- child.setAlpha(1);
- }
- }
- mTarget.secondaryIcon.animate().cancel();
- mTarget.secondaryIcon.setTranslationX(0);
- mTarget = null;
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- if (mTarget == null) return;
- AsyncTask.execute(this);
- }
-
- @Override
- public void run() {
- if (mTarget == null) return;
- if (LOGD) Log.d(mTag, "Secondary icon animation complete, show notification slider");
- mAudioManager.forceVolumeControlStream(StreamResources.NotificationStream.streamType);
- mAudioManager.adjustStreamVolume(StreamResources.NotificationStream.streamType,
- AudioManager.ADJUST_SAME, AudioManager.FLAG_SHOW_UI);
- }
- }
-
- public interface Callback {
- void onZenSettings();
- void onInteraction();
- void onVisible(boolean visible);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePrefs.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePrefs.java
new file mode 100644
index 0000000..04339eb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePrefs.java
@@ -0,0 +1,65 @@
+/*
+ * 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.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.preference.PreferenceManager;
+
+/**
+ * Configuration for the volume dialog + related policy.
+ */
+public class VolumePrefs {
+
+ public static final String PREF_ENABLE_PROTOTYPE = "pref_enable_prototype"; // not persistent
+ public static final String PREF_SHOW_ALARMS = "pref_show_alarms";
+ public static final String PREF_SHOW_SYSTEM = "pref_show_system";
+ public static final String PREF_SHOW_HEADERS = "pref_show_headers";
+ public static final String PREF_SHOW_FAKE_REMOTE_1 = "pref_show_fake_remote_1";
+ public static final String PREF_SHOW_FAKE_REMOTE_2 = "pref_show_fake_remote_2";
+ public static final String PREF_ENABLE_AUTOMUTE = "pref_enable_automute";
+ public static final String PREF_ENABLE_SILENT_MODE = "pref_enable_silent_mode";
+ public static final String PREF_DEBUG_LOGGING = "pref_debug_logging";
+ public static final String PREF_SEND_LOGS = "pref_send_logs";
+ public static final String PREF_ADJUST_SYSTEM = "pref_adjust_system";
+ public static final String PREF_ADJUST_VOICE_CALLS = "pref_adjust_voice_calls";
+ public static final String PREF_ADJUST_BLUETOOTH_SCO = "pref_adjust_bluetooth_sco";
+ public static final String PREF_ADJUST_MEDIA = "pref_adjust_media";
+ public static final String PREF_ADJUST_ALARMS = "pref_adjust_alarms";
+ public static final String PREF_ADJUST_NOTIFICATION = "pref_adjust_notification";
+
+ public static final boolean DEFAULT_SHOW_HEADERS = true;
+ public static final boolean DEFAULT_ENABLE_AUTOMUTE = true;
+ public static final boolean DEFAULT_ENABLE_SILENT_MODE = true;
+
+ public static void unregisterCallbacks(Context c, OnSharedPreferenceChangeListener listener) {
+ prefs(c).unregisterOnSharedPreferenceChangeListener(listener);
+ }
+
+ public static void registerCallbacks(Context c, OnSharedPreferenceChangeListener listener) {
+ prefs(c).registerOnSharedPreferenceChangeListener(listener);
+ }
+
+ private static SharedPreferences prefs(Context context) {
+ return PreferenceManager.getDefaultSharedPreferences(context);
+ }
+
+ public static boolean get(Context context, String key, boolean def) {
+ return prefs(context).getBoolean(key, def);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
index 7102c2a..2688813 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
@@ -1,216 +1,249 @@
+/*
+ * 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.volume;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
-import android.database.ContentObserver;
import android.media.AudioManager;
-import android.media.IRemoteVolumeController;
-import android.media.IVolumeController;
-import android.media.session.ISessionController;
-import android.media.session.MediaController;
import android.media.session.MediaSessionManager;
-import android.net.Uri;
-import android.os.Bundle;
import android.os.Handler;
-import android.os.RemoteException;
import android.provider.Settings;
+import android.text.TextUtils;
import android.util.Log;
+import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
-import com.android.systemui.keyguard.KeyguardViewMediator;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
+import com.android.systemui.qs.tiles.DndTile;
+import com.android.systemui.statusbar.ServiceMonitor;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.statusbar.policy.ZenModeControllerImpl;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-/*
- * 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.
- */
-
public class VolumeUI extends SystemUI {
private static final String TAG = "VolumeUI";
- private static final String SETTING = "systemui_volume_controller"; // for testing
- private static final Uri SETTING_URI = Settings.Global.getUriFor(SETTING);
- private static final int DEFAULT = 1; // enabled by default
+ private static boolean LOGD = Log.isLoggable(TAG, Log.DEBUG);
private final Handler mHandler = new Handler();
+ private final Receiver mReceiver = new Receiver();
+ private final RestorationNotification mRestorationNotification = new RestorationNotification();
private boolean mEnabled;
private AudioManager mAudioManager;
+ private NotificationManager mNotificationManager;
private MediaSessionManager mMediaSessionManager;
- private VolumeController mVolumeController;
- private RemoteVolumeController mRemoteVolumeController;
+ private ServiceMonitor mVolumeControllerService;
- private VolumePanel mPanel;
- private int mDismissDelay;
+ private VolumeDialogComponent mVolumeComponent;
@Override
public void start() {
mEnabled = mContext.getResources().getBoolean(R.bool.enable_volume_ui);
if (!mEnabled) return;
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ mNotificationManager =
+ (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
mMediaSessionManager = (MediaSessionManager) mContext
.getSystemService(Context.MEDIA_SESSION_SERVICE);
- initPanel();
- mVolumeController = new VolumeController();
- mRemoteVolumeController = new RemoteVolumeController();
- putComponent(VolumeComponent.class, mVolumeController);
- updateController();
- mContext.getContentResolver().registerContentObserver(SETTING_URI, false, mObserver);
+ final ZenModeController zenController = new ZenModeControllerImpl(mContext, mHandler);
+ mVolumeComponent = new VolumeDialogComponent(this, mContext, null, zenController);
+ putComponent(VolumeComponent.class, getVolumeComponent());
+ mReceiver.start();
+ mVolumeControllerService = new ServiceMonitor(TAG, LOGD,
+ mContext, Settings.Secure.VOLUME_CONTROLLER_SERVICE_COMPONENT,
+ new ServiceMonitorCallbacks());
+ mVolumeControllerService.start();
+ }
+
+ private VolumeComponent getVolumeComponent() {
+ return mVolumeComponent;
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
- if (mPanel != null) {
- mPanel.onConfigurationChanged(newConfig);
- }
+ if (!mEnabled) return;
+ getVolumeComponent().onConfigurationChanged(newConfig);
}
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.print("mEnabled="); pw.println(mEnabled);
- if (mPanel != null) {
- mPanel.dump(fd, pw, args);
- }
+ if (!mEnabled) return;
+ pw.print("mVolumeControllerService="); pw.println(mVolumeControllerService.getComponent());
+ getVolumeComponent().dump(fd, pw, args);
}
- private void updateController() {
- if (Settings.Global.getInt(mContext.getContentResolver(), SETTING, DEFAULT) != 0) {
- Log.d(TAG, "Registering volume controller");
- mAudioManager.setVolumeController(mVolumeController);
- mMediaSessionManager.setRemoteVolumeController(mRemoteVolumeController);
+ private void setDefaultVolumeController(boolean register) {
+ if (register) {
+ DndTile.setVisible(mContext, true);
+ if (LOGD) Log.d(TAG, "Registering default volume controller");
+ getVolumeComponent().register();
} else {
- Log.d(TAG, "Unregistering volume controller");
+ if (LOGD) Log.d(TAG, "Unregistering default volume controller");
mAudioManager.setVolumeController(null);
mMediaSessionManager.setRemoteVolumeController(null);
}
}
- private void initPanel() {
- mDismissDelay = mContext.getResources().getInteger(R.integer.volume_panel_dismiss_delay);
- mPanel = new VolumePanel(mContext, new ZenModeControllerImpl(mContext, mHandler));
- mPanel.setCallback(new VolumePanel.Callback() {
- @Override
- public void onZenSettings() {
- mHandler.removeCallbacks(mStartZenSettings);
- mHandler.post(mStartZenSettings);
- }
-
- @Override
- public void onInteraction() {
- final KeyguardViewMediator kvm = getComponent(KeyguardViewMediator.class);
- if (kvm != null) {
- kvm.userActivity();
- }
+ private String getAppLabel(ComponentName component) {
+ final String pkg = component.getPackageName();
+ try {
+ final ApplicationInfo ai = mContext.getPackageManager().getApplicationInfo(pkg, 0);
+ final String rt = mContext.getPackageManager().getApplicationLabel(ai).toString();
+ if (!TextUtils.isEmpty(rt)) {
+ return rt;
}
+ } catch (Exception e) {
+ Log.w(TAG, "Error loading app label", e);
+ }
+ return pkg;
+ }
+ private void showServiceActivationDialog(final ComponentName component) {
+ final SystemUIDialog d = new SystemUIDialog(mContext);
+ d.setMessage(mContext.getString(R.string.volumeui_prompt_message, getAppLabel(component)));
+ d.setPositiveButton(R.string.volumeui_prompt_allow, new OnClickListener() {
@Override
- public void onVisible(boolean visible) {
- if (mAudioManager != null && mVolumeController != null) {
- mAudioManager.notifyVolumeControllerVisible(mVolumeController, visible);
- }
+ public void onClick(DialogInterface dialog, int which) {
+ mVolumeControllerService.setComponent(component);
}
});
+ d.setNegativeButton(R.string.volumeui_prompt_deny, null);
+ d.show();
}
- private final ContentObserver mObserver = new ContentObserver(mHandler) {
- public void onChange(boolean selfChange, Uri uri) {
- if (SETTING_URI.equals(uri)) {
- updateController();
- }
- }
- };
-
- private final Runnable mStartZenSettings = new Runnable() {
- @Override
- public void run() {
- getComponent(PhoneStatusBar.class).startActivityDismissingKeyguard(
- ZenModePanel.ZEN_SETTINGS, true /* onlyProvisioned */, true /* dismissShade */);
- mPanel.postDismiss(mDismissDelay);
- }
- };
-
- /** For now, simply host an unmodified base volume panel in this process. */
- private final class VolumeController extends IVolumeController.Stub implements VolumeComponent {
-
- @Override
- public void displaySafeVolumeWarning(int flags) throws RemoteException {
- mPanel.postDisplaySafeVolumeWarning(flags);
- }
-
+ private final class ServiceMonitorCallbacks implements ServiceMonitor.Callbacks {
@Override
- public void volumeChanged(int streamType, int flags)
- throws RemoteException {
- mPanel.postVolumeChanged(streamType, flags);
- }
-
- @Override
- public void masterVolumeChanged(int flags) throws RemoteException {
- mPanel.postMasterVolumeChanged(flags);
- }
-
- @Override
- public void masterMuteChanged(int flags) throws RemoteException {
- mPanel.postMasterMuteChanged(flags);
+ public void onNoService() {
+ if (LOGD) Log.d(TAG, "onNoService");
+ setDefaultVolumeController(true);
+ mRestorationNotification.hide();
+ if (!mVolumeControllerService.isPackageAvailable()) {
+ mVolumeControllerService.setComponent(null);
+ }
}
@Override
- public void setLayoutDirection(int layoutDirection)
- throws RemoteException {
- mPanel.postLayoutDirection(layoutDirection);
+ public long onServiceStartAttempt() {
+ if (LOGD) Log.d(TAG, "onServiceStartAttempt");
+ // poke the setting to update the uid
+ mVolumeControllerService.setComponent(mVolumeControllerService.getComponent());
+ setDefaultVolumeController(false);
+ getVolumeComponent().dismissNow();
+ mRestorationNotification.show();
+ return 0;
}
+ }
- @Override
- public void dismiss() throws RemoteException {
- dismissNow();
- }
+ private final class Receiver extends BroadcastReceiver {
+ private static final String ENABLE = "com.android.systemui.vui.ENABLE";
+ private static final String DISABLE = "com.android.systemui.vui.DISABLE";
+ private static final String EXTRA_COMPONENT = "component";
- @Override
- public ZenModeController getZenController() {
- return mPanel.getZenController();
- }
+ private static final String PREF = "com.android.systemui.PREF";
+ private static final String EXTRA_KEY = "key";
+ private static final String EXTRA_VALUE = "value";
- @Override
- public void dispatchDemoCommand(String command, Bundle args) {
- mPanel.dispatchDemoCommand(command, args);
+ public void start() {
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(ENABLE);
+ filter.addAction(DISABLE);
+ filter.addAction(PREF);
+ mContext.registerReceiver(this, filter, null, mHandler);
}
@Override
- public void dismissNow() {
- mPanel.postDismiss(0);
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (PREF.equals(action)) {
+ final String key = intent.getStringExtra(EXTRA_KEY);
+ if (key != null && intent.getExtras() != null) {
+ final Object value = intent.getExtras().get(EXTRA_VALUE);
+ if (value == null) {
+ Prefs.remove(mContext, key);
+ } else if (value instanceof Boolean) {
+ Prefs.putBoolean(mContext, key, (Boolean) value);
+ } else if (value instanceof Integer) {
+ Prefs.putInt(mContext, key, (Integer) value);
+ } else if (value instanceof Long) {
+ Prefs.putLong(mContext, key, (Long) value);
+ }
+ }
+ return;
+ }
+ final ComponentName component = intent.getParcelableExtra(EXTRA_COMPONENT);
+ final boolean current = component != null
+ && component.equals(mVolumeControllerService.getComponent());
+ if (ENABLE.equals(action) && component != null) {
+ if (!current) {
+ showServiceActivationDialog(component);
+ }
+ }
+ if (DISABLE.equals(action) && component != null) {
+ if (current) {
+ mVolumeControllerService.setComponent(null);
+ }
+ }
}
}
- private final class RemoteVolumeController extends IRemoteVolumeController.Stub {
-
- @Override
- public void remoteVolumeChanged(ISessionController binder, int flags)
- throws RemoteException {
- MediaController controller = new MediaController(mContext, binder);
- mPanel.postRemoteVolumeChanged(controller, flags);
+ private final class RestorationNotification {
+ public void hide() {
+ mNotificationManager.cancel(R.id.notification_volumeui);
}
- @Override
- public void updateRemoteController(ISessionController session) throws RemoteException {
- mPanel.postRemoteSliderVisibility(session != null);
- // TODO stash default session in case the slider can be opened other
- // than by remoteVolumeChanged.
+ public void show() {
+ final ComponentName component = mVolumeControllerService.getComponent();
+ if (component == null) {
+ Log.w(TAG, "Not showing restoration notification, component not active");
+ return;
+ }
+ final Intent intent = new Intent(Receiver.DISABLE)
+ .putExtra(Receiver.EXTRA_COMPONENT, component);
+ mNotificationManager.notify(R.id.notification_volumeui,
+ new Notification.Builder(mContext)
+ .setSmallIcon(R.drawable.ic_volume_media)
+ .setWhen(0)
+ .setShowWhen(false)
+ .setOngoing(true)
+ .setContentTitle(mContext.getString(
+ R.string.volumeui_notification_title, getAppLabel(component)))
+ .setContentText(mContext.getString(R.string.volumeui_notification_text))
+ .setContentIntent(PendingIntent.getBroadcast(mContext, 0, intent,
+ PendingIntent.FLAG_UPDATE_CURRENT))
+ .setPriority(Notification.PRIORITY_MIN)
+ .setVisibility(Notification.VISIBILITY_PUBLIC)
+ .setColor(mContext.getColor(
+ com.android.internal.R.color.system_notification_accent_color))
+ .build());
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java b/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java
new file mode 100644
index 0000000..af7ee08
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java
@@ -0,0 +1,145 @@
+/*
+ * 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.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+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 SpTexts mSpTexts;
+
+ private ImageView mIcon;
+ private TextView mSummaryLine1;
+ private TextView mSummaryLine2;
+ private TextView mEndNowButton;
+ private int mZen = -1;
+ private ZenModeConfig mConfig;
+ private ZenModeController mController;
+
+ public ZenFooter(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mContext = context;
+ mSpTexts = new SpTexts(mContext);
+ final LayoutTransition layoutTransition = new LayoutTransition();
+ layoutTransition.setDuration(new ValueAnimator().getDuration() / 2);
+ setLayoutTransition(layoutTransition);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mIcon = (ImageView) findViewById(R.id.volume_zen_icon);
+ mSummaryLine1 = (TextView) findViewById(R.id.volume_zen_summary_line_1);
+ mSummaryLine2 = (TextView) findViewById(R.id.volume_zen_summary_line_2);
+ mEndNowButton = (TextView) findViewById(R.id.volume_zen_end_now);
+ mSpTexts.add(mSummaryLine1);
+ mSpTexts.add(mSummaryLine2);
+ mSpTexts.add(mEndNowButton);
+ }
+
+ public void init(final ZenModeController controller) {
+ controller.addCallback(new ZenModeController.Callback() {
+ @Override
+ public void onZenChanged(int zen) {
+ setZen(zen);
+ }
+ @Override
+ public void onConfigChanged(ZenModeConfig config) {
+ setConfig(config);
+ }
+ });
+ mEndNowButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ controller.setZen(Global.ZEN_MODE_OFF, null, TAG);
+ }
+ });
+ mZen = controller.getZen();
+ mConfig = controller.getConfig();
+ mController = controller;
+ update();
+ }
+
+ private void setZen(int zen) {
+ if (mZen == zen) return;
+ mZen = zen;
+ update();
+ }
+
+ private void setConfig(ZenModeConfig config) {
+ if (Objects.equals(mConfig, config)) return;
+ mConfig = config;
+ update();
+ }
+
+ public boolean isZen() {
+ return isZenPriority() || isZenAlarms() || isZenNone();
+ }
+
+ 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 boolean isForever = mConfig != null && mConfig.manualRule != null
+ && mConfig.manualRule.conditionId == null;
+ final String line2 =
+ isForever ? mContext.getString(com.android.internal.R.string.zen_mode_forever_dnd)
+ : ZenModeConfig.getConditionSummary(mContext, mConfig, mController.getCurrentUser(),
+ true /*shortVersion*/);
+ Util.setText(mSummaryLine2, line2);
+ }
+
+ public void onConfigurationChanged() {
+ mSpTexts.update();
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
index d40a2c0..3c9a7fc 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
@@ -19,12 +19,11 @@ package com.android.systemui.volume;
import android.animation.LayoutTransition;
import android.animation.LayoutTransition.TransitionListener;
import android.app.ActivityManager;
-import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
-import android.content.res.Resources;
+import android.content.res.Configuration;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Handler;
@@ -34,7 +33,9 @@ import android.provider.Settings;
import android.provider.Settings.Global;
import android.service.notification.Condition;
import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.ZenRule;
import android.text.TextUtils;
+import android.text.format.DateFormat;
import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.Log;
@@ -42,8 +43,6 @@ import android.util.MathUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ImageView;
@@ -51,12 +50,15 @@ import android.widget.LinearLayout;
import android.widget.RadioButton;
import android.widget.TextView;
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.statusbar.policy.ZenModeController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Arrays;
+import java.util.Locale;
import java.util.Objects;
public class ZenModePanel extends LinearLayout {
@@ -75,37 +77,38 @@ public class ZenModePanel extends LinearLayout {
private static final int FOREVER_CONDITION_INDEX = 0;
private static final int COUNTDOWN_CONDITION_INDEX = 1;
- public static final Intent ZEN_SETTINGS = new Intent(Settings.ACTION_ZEN_MODE_SETTINGS);
+ public static final Intent ZEN_SETTINGS
+ = new Intent(Settings.ACTION_ZEN_MODE_SETTINGS);
+ public static final Intent ZEN_PRIORITY_SETTINGS
+ = new Intent(Settings.ACTION_ZEN_MODE_PRIORITY_SETTINGS);
private final Context mContext;
private final LayoutInflater mInflater;
private final H mHandler = new H();
- private final Prefs mPrefs;
+ private final ZenPrefs mPrefs;
private final IconPulser mIconPulser;
- private final int mSubheadWarningColor;
- private final int mSubheadColor;
- private final Interpolator mInterpolator;
- private final int mMaxConditions;
- private final int mMaxOptionalConditions;
- private final boolean mCountdownConditionSupported;
- private final int mFirstConditionIndex;
private final TransitionHelper mTransitionHelper = new TransitionHelper();
private final Uri mForeverId;
+ private final SpTexts mSpTexts;
private String mTag = TAG + "/" + Integer.toHexString(System.identityHashCode(this));
private SegmentedButtons mZenButtons;
- private View mZenSubhead;
- private TextView mZenSubheadCollapsed;
- private TextView mZenSubheadExpanded;
- private View mMoreSettings;
+ private View mZenIntroduction;
+ private TextView mZenIntroductionMessage;
+ private View mZenIntroductionConfirm;
+ private TextView mZenIntroductionCustomize;
private LinearLayout mZenConditions;
+ private TextView mZenAlarmWarning;
private Callback mCallback;
private ZenModeController mController;
+ private boolean mCountdownConditionSupported;
+ private int mMaxConditions;
+ private int mMaxOptionalConditions;
+ private int mFirstConditionIndex;
private boolean mRequestingConditions;
private Condition mExitCondition;
- private String mExitConditionText;
private int mBucketIndex = -1;
private boolean mExpanded;
private boolean mHidden;
@@ -115,27 +118,17 @@ public class ZenModePanel extends LinearLayout {
private Condition mSessionExitCondition;
private Condition[] mConditions;
private Condition mTimeCondition;
+ private boolean mVoiceCapable;
public ZenModePanel(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
- mPrefs = new Prefs();
+ mPrefs = new ZenPrefs();
mInflater = LayoutInflater.from(mContext.getApplicationContext());
mIconPulser = new IconPulser(mContext);
- final Resources res = mContext.getResources();
- mSubheadWarningColor = res.getColor(R.color.system_warning_color);
- mSubheadColor = res.getColor(R.color.qs_subhead);
- mInterpolator = AnimationUtils.loadInterpolator(mContext,
- com.android.internal.R.interpolator.fast_out_slow_in);
- mCountdownConditionSupported = NotificationManager.from(mContext)
- .isSystemConditionProviderEnabled(ZenModeConfig.COUNTDOWN_PATH);
- final int countdownDelta = mCountdownConditionSupported ? 1 : 0;
- mFirstConditionIndex = COUNTDOWN_CONDITION_INDEX + countdownDelta;
- final int minConditions = 1 /*forever*/ + countdownDelta;
- mMaxConditions = MathUtils.constrain(res.getInteger(R.integer.zen_mode_max_conditions),
- minConditions, 100);
- mMaxOptionalConditions = mMaxConditions - minConditions;
mForeverId = Condition.newId(mContext).appendPath("forever").build();
+ mSpTexts = new SpTexts(mContext);
+ mVoiceCapable = Util.isVoiceCapable(mContext);
if (DEBUG) Log.d(mTag, "new ZenModePanel");
}
@@ -149,6 +142,11 @@ public class ZenModePanel extends LinearLayout {
pw.print(" mExpanded="); pw.println(mExpanded);
pw.print(" mSessionZen="); pw.println(mSessionZen);
pw.print(" mAttachedZen="); pw.println(mAttachedZen);
+ pw.print(" mConfirmedPriorityIntroduction=");
+ pw.println(mPrefs.mConfirmedPriorityIntroduction);
+ pw.print(" mConfirmedSilenceIntroduction=");
+ pw.println(mPrefs.mConfirmedSilenceIntroduction);
+ pw.print(" mVoiceCapable="); pw.println(mVoiceCapable);
mTransitionHelper.dump(fd, pw, args);
}
@@ -157,58 +155,68 @@ public class ZenModePanel extends LinearLayout {
super.onFinishInflate();
mZenButtons = (SegmentedButtons) findViewById(R.id.zen_buttons);
- mZenButtons.addButton(R.string.interruption_level_none, R.drawable.ic_zen_none,
+ mZenButtons.addButton(R.string.interruption_level_none_twoline,
+ R.string.interruption_level_none_with_warning,
Global.ZEN_MODE_NO_INTERRUPTIONS);
- mZenButtons.addButton(R.string.interruption_level_priority, R.drawable.ic_zen_important,
+ mZenButtons.addButton(R.string.interruption_level_alarms_twoline,
+ R.string.interruption_level_alarms,
+ Global.ZEN_MODE_ALARMS);
+ mZenButtons.addButton(R.string.interruption_level_priority_twoline,
+ R.string.interruption_level_priority,
Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS);
- mZenButtons.addButton(R.string.interruption_level_all, R.drawable.ic_zen_all,
- Global.ZEN_MODE_OFF);
mZenButtons.setCallback(mZenButtonsCallback);
- final ViewGroup zenButtonsContainer = (ViewGroup) findViewById(R.id.zen_buttons_container);
- zenButtonsContainer.setLayoutTransition(newLayoutTransition(null));
-
- mZenSubhead = findViewById(R.id.zen_subhead);
-
- mZenSubheadCollapsed = (TextView) findViewById(R.id.zen_subhead_collapsed);
- mZenSubheadCollapsed.setOnClickListener(new View.OnClickListener() {
+ mZenIntroduction = findViewById(R.id.zen_introduction);
+ mZenIntroductionMessage = (TextView) findViewById(R.id.zen_introduction_message);
+ mSpTexts.add(mZenIntroductionMessage);
+ mZenIntroductionConfirm = findViewById(R.id.zen_introduction_confirm);
+ mZenIntroductionConfirm.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
- setExpanded(true);
+ confirmZenIntroduction();
}
});
- Interaction.register(mZenSubheadCollapsed, mInteractionCallback);
-
- mZenSubheadExpanded = (TextView) findViewById(R.id.zen_subhead_expanded);
- Interaction.register(mZenSubheadExpanded, mInteractionCallback);
-
- mMoreSettings = findViewById(R.id.zen_more_settings);
- mMoreSettings.setOnClickListener(new View.OnClickListener() {
+ mZenIntroductionCustomize = (TextView) findViewById(R.id.zen_introduction_customize);
+ mZenIntroductionCustomize.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
- fireMoreSettings();
+ confirmZenIntroduction();
+ if (mCallback != null) {
+ mCallback.onPrioritySettings();
+ }
}
});
- Interaction.register(mMoreSettings, mInteractionCallback);
+ mSpTexts.add(mZenIntroductionCustomize);
mZenConditions = (LinearLayout) findViewById(R.id.zen_conditions);
- for (int i = 0; i < mMaxConditions; i++) {
- mZenConditions.addView(mInflater.inflate(R.layout.zen_mode_condition, this, false));
+ mZenAlarmWarning = (TextView) findViewById(R.id.zen_alarm_warning);
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ if (mZenButtons != null) {
+ mZenButtons.updateLocale();
}
+ }
- setLayoutTransition(newLayoutTransition(mTransitionHelper));
+ private void confirmZenIntroduction() {
+ final String prefKey = prefKeyForConfirmation(getSelectedZen(Global.ZEN_MODE_OFF));
+ if (prefKey == null) return;
+ if (DEBUG) Log.d(TAG, "confirmZenIntroduction " + prefKey);
+ Prefs.putBoolean(mContext, prefKey, true);
+ mHandler.sendEmptyMessage(H.UPDATE_WIDGETS);
}
- private LayoutTransition newLayoutTransition(TransitionListener listener) {
- final LayoutTransition transition = new LayoutTransition();
- transition.disableTransitionType(LayoutTransition.DISAPPEARING);
- transition.disableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
- transition.disableTransitionType(LayoutTransition.APPEARING);
- transition.setInterpolator(LayoutTransition.CHANGE_APPEARING, mInterpolator);
- if (listener != null) {
- transition.addTransitionListener(listener);
+ private static String prefKeyForConfirmation(int zen) {
+ switch (zen) {
+ case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
+ return Prefs.Key.DND_CONFIRMED_PRIORITY_INTRODUCTION;
+ case Global.ZEN_MODE_NO_INTERRUPTIONS:
+ return Prefs.Key.DND_CONFIRMED_SILENCE_INTRODUCTION;
+ default:
+ return null;
}
- return transition;
}
@Override
@@ -220,7 +228,6 @@ public class ZenModePanel extends LinearLayout {
mSessionZen = mAttachedZen;
mTransitionHelper.clear();
setSessionExitCondition(copy(mExitCondition));
- refreshExitConditionText();
updateWidgets();
setRequestingConditions(!mHidden);
}
@@ -234,7 +241,6 @@ public class ZenModePanel extends LinearLayout {
mAttachedZen = -1;
mSessionZen = -1;
setSessionExitCondition(null);
- setExpanded(false);
setRequestingConditions(false);
mTransitionHelper.clear();
}
@@ -266,8 +272,9 @@ public class ZenModePanel extends LinearLayout {
private void setExpanded(boolean expanded) {
if (expanded == mExpanded) return;
+ if (DEBUG) Log.d(mTag, "setExpanded " + expanded);
mExpanded = expanded;
- if (mExpanded) {
+ if (mExpanded && isShown()) {
ensureSelection();
}
updateWidgets();
@@ -288,7 +295,7 @@ public class ZenModePanel extends LinearLayout {
});
}
if (mRequestingConditions) {
- mTimeCondition = parseExistingTimeCondition(mExitCondition);
+ mTimeCondition = parseExistingTimeCondition(mContext, mExitCondition);
if (mTimeCondition != null) {
mBucketIndex = -1;
} else {
@@ -306,10 +313,18 @@ public class ZenModePanel extends LinearLayout {
public void init(ZenModeController controller) {
mController = controller;
- setExitCondition(mController.getExitCondition());
- refreshExitConditionText();
+ mCountdownConditionSupported = mController.isCountdownConditionSupported();
+ final int countdownDelta = mCountdownConditionSupported ? 1 : 0;
+ mFirstConditionIndex = COUNTDOWN_CONDITION_INDEX + countdownDelta;
+ final int minConditions = 1 /*forever*/ + countdownDelta;
+ mMaxConditions = MathUtils.constrain(mContext.getResources()
+ .getInteger(R.integer.zen_mode_max_conditions), minConditions, 100);
+ mMaxOptionalConditions = mMaxConditions - minConditions;
+ for (int i = 0; i < mMaxConditions; i++) {
+ mZenConditions.addView(mInflater.inflate(R.layout.zen_mode_condition, this, false));
+ }
mSessionZen = getSelectedZen(-1);
- handleUpdateZen(mController.getZen());
+ handleUpdateManualRule(mController.getManualRule());
if (DEBUG) Log.d(mTag, "init mExitCondition=" + mExitCondition);
hideAllConditions();
mController.addCallback(mZenCallback);
@@ -323,7 +338,6 @@ public class ZenModePanel extends LinearLayout {
if (Objects.equals(mExitCondition, exitCondition)) return;
mExitCondition = exitCondition;
if (DEBUG) Log.d(mTag, "mExitCondition=" + getConditionId(mExitCondition));
- refreshExitConditionText();
updateWidgets();
}
@@ -331,6 +345,10 @@ public class ZenModePanel extends LinearLayout {
return condition != null ? condition.id : null;
}
+ private Uri getRealConditionId(Condition condition) {
+ return isForever(condition) ? null : getConditionId(condition);
+ }
+
private static boolean sameConditionId(Condition lhs, Condition rhs) {
return lhs == null ? rhs == null : rhs != null && lhs.id.equals(rhs.id);
}
@@ -339,14 +357,14 @@ public class ZenModePanel extends LinearLayout {
return condition == null ? null : condition.copy();
}
- private void refreshExitConditionText() {
- if (mExitCondition == null) {
- mExitConditionText = foreverSummary();
- } else if (isCountdown(mExitCondition)) {
- final Condition condition = parseExistingTimeCondition(mExitCondition);
- mExitConditionText = condition != null ? condition.summary : foreverSummary();
+ public static String getExitConditionText(Context context, Condition exitCondition) {
+ if (exitCondition == null) {
+ return foreverSummary(context);
+ } else if (isCountdown(exitCondition)) {
+ final Condition condition = parseExistingTimeCondition(context, exitCondition);
+ return condition != null ? condition.summary : foreverSummary(context);
} else {
- mExitConditionText = mExitCondition.summary;
+ return exitCondition.summary;
}
}
@@ -361,12 +379,19 @@ public class ZenModePanel extends LinearLayout {
mIconPulser.start(noneButton);
}
+ private void handleUpdateManualRule(ZenRule rule) {
+ final int zen = rule != null ? rule.zenMode : Global.ZEN_MODE_OFF;
+ handleUpdateZen(zen);
+ final Condition c = rule != null ? rule.condition : null;
+ handleExitConditionChanged(c);
+ }
+
private void handleUpdateZen(int zen) {
if (mSessionZen != -1 && mSessionZen != zen) {
- setExpanded(zen != Global.ZEN_MODE_OFF);
+ setExpanded(isShown());
mSessionZen = zen;
}
- mZenButtons.setSelectedValue(zen);
+ mZenButtons.setSelectedValue(zen, false /* fromClick */);
updateWidgets();
handleUpdateConditions();
if (mExpanded) {
@@ -377,6 +402,20 @@ public class ZenModePanel extends LinearLayout {
}
}
+ private void handleExitConditionChanged(Condition exitCondition) {
+ setExitCondition(exitCondition);
+ if (DEBUG) Log.d(mTag, "handleExitConditionChanged " + mExitCondition);
+ final int N = getVisibleConditions();
+ for (int i = 0; i < N; i++) {
+ final ConditionTag tag = getConditionTagAt(i);
+ if (tag != null) {
+ if (sameConditionId(tag.condition, mExitCondition)) {
+ bind(exitCondition, mZenConditions.getChildAt(i));
+ }
+ }
+ }
+ }
+
private Condition getSelectedCondition() {
final int N = getVisibleConditions();
for (int i = 0; i < N; i++) {
@@ -399,38 +438,65 @@ public class ZenModePanel extends LinearLayout {
return;
}
final int zen = getSelectedZen(Global.ZEN_MODE_OFF);
- final boolean zenOff = zen == Global.ZEN_MODE_OFF;
final boolean zenImportant = zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
final boolean zenNone = zen == Global.ZEN_MODE_NO_INTERRUPTIONS;
- final boolean expanded = !mHidden && mExpanded;
+ final boolean introduction = (zenImportant && !mPrefs.mConfirmedPriorityIntroduction
+ || zenNone && !mPrefs.mConfirmedSilenceIntroduction);
mZenButtons.setVisibility(mHidden ? GONE : VISIBLE);
- mZenSubhead.setVisibility(!mHidden && !zenOff ? VISIBLE : GONE);
- mZenSubheadExpanded.setVisibility(expanded ? VISIBLE : GONE);
- mZenSubheadCollapsed.setVisibility(!expanded ? VISIBLE : GONE);
- mMoreSettings.setVisibility(zenImportant && expanded ? VISIBLE : GONE);
- mZenConditions.setVisibility(!zenOff && expanded ? VISIBLE : GONE);
+ mZenIntroduction.setVisibility(introduction ? VISIBLE : GONE);
+ if (introduction) {
+ mZenIntroductionMessage.setText(zenImportant ? R.string.zen_priority_introduction
+ : mVoiceCapable ? R.string.zen_silence_introduction_voice
+ : R.string.zen_silence_introduction);
+ mZenIntroductionCustomize.setVisibility(zenImportant ? VISIBLE : GONE);
+ }
+ final String warning = computeAlarmWarningText(zenNone);
+ mZenAlarmWarning.setVisibility(warning != null ? VISIBLE : GONE);
+ mZenAlarmWarning.setText(warning);
+ }
- if (zenNone) {
- mZenSubheadExpanded.setText(R.string.zen_no_interruptions_with_warning);
- mZenSubheadCollapsed.setText(mExitConditionText);
- } else if (zenImportant) {
- mZenSubheadExpanded.setText(R.string.zen_important_interruptions);
- mZenSubheadCollapsed.setText(mExitConditionText);
+ private String computeAlarmWarningText(boolean zenNone) {
+ if (!zenNone) {
+ return null;
+ }
+ final long now = System.currentTimeMillis();
+ final long nextAlarm = mController.getNextAlarm();
+ if (nextAlarm < now) {
+ return null;
}
- mZenSubheadExpanded.setTextColor(zenNone && mPrefs.isNoneDangerous()
- ? mSubheadWarningColor : mSubheadColor);
+ int warningRes = 0;
+ if (mSessionExitCondition == null || isForever(mSessionExitCondition)) {
+ warningRes = R.string.zen_alarm_warning_indef;
+ } else {
+ final long time = ZenModeConfig.tryParseCountdownConditionId(mSessionExitCondition.id);
+ if (time > now && nextAlarm < time) {
+ warningRes = R.string.zen_alarm_warning;
+ }
+ }
+ if (warningRes == 0) {
+ return null;
+ }
+ final boolean soon = (nextAlarm - now) < 24 * 60 * 60 * 1000;
+ final boolean is24 = DateFormat.is24HourFormat(mContext, ActivityManager.getCurrentUser());
+ final String skeleton = soon ? (is24 ? "Hm" : "hma") : (is24 ? "EEEHm" : "EEEhma");
+ final String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton);
+ final CharSequence formattedTime = DateFormat.format(pattern, nextAlarm);
+ final int templateRes = soon ? R.string.alarm_template : R.string.alarm_template_far;
+ final String template = getResources().getString(templateRes, formattedTime);
+ return getResources().getString(warningRes, template);
}
- private Condition parseExistingTimeCondition(Condition condition) {
+ private static Condition parseExistingTimeCondition(Context context, Condition condition) {
if (condition == null) return null;
final long time = ZenModeConfig.tryParseCountdownConditionId(condition.id);
if (time == 0) return null;
final long now = System.currentTimeMillis();
final long span = time - now;
if (span <= 0 || span > MAX_BUCKET_MINUTES * MINUTES_MS) return null;
- return ZenModeConfig.toTimeCondition(mContext,
- time, Math.round(span / (float) MINUTES_MS), now, ActivityManager.getCurrentUser());
+ return ZenModeConfig.toTimeCondition(context,
+ time, Math.round(span / (float) MINUTES_MS), now, ActivityManager.getCurrentUser(),
+ false /*shortVersion*/);
}
private void handleUpdateConditions(Condition[] conditions) {
@@ -489,18 +555,18 @@ public class ZenModePanel extends LinearLayout {
mZenConditions.getChildAt(i).setVisibility(GONE);
}
// ensure something is selected
- if (mExpanded) {
+ if (mExpanded && isShown()) {
ensureSelection();
}
}
private Condition forever() {
- return new Condition(mForeverId, foreverSummary(), "", "", 0 /*icon*/, Condition.STATE_TRUE,
- 0 /*flags*/);
+ return new Condition(mForeverId, foreverSummary(mContext), "", "", 0 /*icon*/,
+ Condition.STATE_TRUE, 0 /*flags*/);
}
- private String foreverSummary() {
- return mContext.getString(com.android.internal.R.string.zen_mode_forever);
+ private static String foreverSummary(Context context) {
+ return context.getString(com.android.internal.R.string.zen_mode_forever);
}
private ConditionTag getConditionTagAt(int index) {
@@ -549,21 +615,7 @@ public class ZenModePanel extends LinearLayout {
}
}
- private void handleExitConditionChanged(Condition exitCondition) {
- setExitCondition(exitCondition);
- if (DEBUG) Log.d(mTag, "handleExitConditionChanged " + mExitCondition);
- final int N = getVisibleConditions();
- for (int i = 0; i < N; i++) {
- final ConditionTag tag = getConditionTagAt(i);
- if (tag != null) {
- if (sameConditionId(tag.condition, mExitCondition)) {
- bind(exitCondition, mZenConditions.getChildAt(i));
- }
- }
- }
- }
-
- private boolean isCountdown(Condition c) {
+ private static boolean isCountdown(Condition c) {
return c != null && ZenModeConfig.isValidCountdownConditionId(c.id);
}
@@ -605,6 +657,7 @@ public class ZenModePanel extends LinearLayout {
if (childTag == null || childTag == tag) continue;
childTag.rb.setChecked(false);
}
+ MetricsLogger.action(mContext, MetricsLogger.QS_DND_CONDITION_SELECT);
select(tag.condition);
announceConditionSelection(tag);
}
@@ -616,9 +669,11 @@ public class ZenModePanel extends LinearLayout {
}
if (tag.line1 == null) {
tag.line1 = (TextView) row.findViewById(android.R.id.text1);
+ mSpTexts.add(tag.line1);
}
if (tag.line2 == null) {
tag.line2 = (TextView) row.findViewById(android.R.id.text2);
+ mSpTexts.add(tag.line2);
}
final String line1 = !TextUtils.isEmpty(condition.line1) ? condition.line1
: condition.summary;
@@ -691,12 +746,15 @@ public class ZenModePanel extends LinearLayout {
String modeText;
switch(zen) {
case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
- modeText = mContext.getString(R.string.zen_important_interruptions);
+ modeText = mContext.getString(R.string.interruption_level_priority);
break;
case Global.ZEN_MODE_NO_INTERRUPTIONS:
- modeText = mContext.getString(R.string.zen_no_interruptions);
+ modeText = mContext.getString(R.string.interruption_level_none);
+ break;
+ case Global.ZEN_MODE_ALARMS:
+ modeText = mContext.getString(R.string.interruption_level_alarms);
break;
- default:
+ default:
return;
}
announceForAccessibility(mContext.getString(R.string.zen_mode_and_condition, modeText,
@@ -704,6 +762,7 @@ public class ZenModePanel extends LinearLayout {
}
private void onClickTimeButton(View row, ConditionTag tag, boolean up) {
+ MetricsLogger.action(mContext, MetricsLogger.QS_DND_TIME, up);
Condition newCondition = null;
final int N = MINUTE_BUCKETS.length;
if (mBucketIndex == -1) {
@@ -718,7 +777,8 @@ public class ZenModePanel extends LinearLayout {
if (up && bucketTime > time || !up && bucketTime < time) {
mBucketIndex = j;
newCondition = ZenModeConfig.toTimeCondition(mContext,
- bucketTime, bucketMinutes, now, ActivityManager.getCurrentUser());
+ bucketTime, bucketMinutes, now, ActivityManager.getCurrentUser(),
+ false /*shortVersion*/);
break;
}
}
@@ -742,17 +802,21 @@ public class ZenModePanel extends LinearLayout {
private void select(final Condition condition) {
if (DEBUG) Log.d(mTag, "select " + condition);
- final boolean isForever = isForever(condition);
+ if (mSessionZen == -1 || mSessionZen == Global.ZEN_MODE_OFF) {
+ if (DEBUG) Log.d(mTag, "Ignoring condition selection outside of manual zen");
+ return;
+ }
+ final Uri realConditionId = getRealConditionId(condition);
if (mController != null) {
AsyncTask.execute(new Runnable() {
@Override
public void run() {
- mController.setExitCondition(isForever ? null : condition);
+ mController.setZen(mSessionZen, realConditionId, TAG + ".selectCondition");
}
});
}
setExitCondition(condition);
- if (isForever) {
+ if (realConditionId == null) {
mPrefs.setMinuteIndex(-1);
} else if (isCountdown(condition) && mBucketIndex != -1) {
mPrefs.setMinuteIndex(mBucketIndex);
@@ -760,12 +824,6 @@ public class ZenModePanel extends LinearLayout {
setSessionExitCondition(copy(condition));
}
- private void fireMoreSettings() {
- if (mCallback != null) {
- mCallback.onMoreSettings();
- }
- }
-
private void fireInteraction() {
if (mCallback != null) {
mCallback.onInteraction();
@@ -780,24 +838,20 @@ public class ZenModePanel extends LinearLayout {
private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() {
@Override
- public void onZenChanged(int zen) {
- mHandler.obtainMessage(H.UPDATE_ZEN, zen, 0).sendToTarget();
- }
- @Override
public void onConditionsChanged(Condition[] conditions) {
mHandler.obtainMessage(H.UPDATE_CONDITIONS, conditions).sendToTarget();
}
@Override
- public void onExitConditionChanged(Condition exitCondition) {
- mHandler.obtainMessage(H.EXIT_CONDITION_CHANGED, exitCondition).sendToTarget();
+ public void onManualRuleChanged(ZenRule rule) {
+ mHandler.obtainMessage(H.MANUAL_RULE_CHANGED, rule).sendToTarget();
}
};
private final class H extends Handler {
private static final int UPDATE_CONDITIONS = 1;
- private static final int EXIT_CONDITION_CHANGED = 2;
- private static final int UPDATE_ZEN = 3;
+ private static final int MANUAL_RULE_CHANGED = 2;
+ private static final int UPDATE_WIDGETS = 3;
private H() {
super(Looper.getMainLooper());
@@ -805,18 +859,16 @@ public class ZenModePanel extends LinearLayout {
@Override
public void handleMessage(Message msg) {
- if (msg.what == UPDATE_CONDITIONS) {
- handleUpdateConditions((Condition[]) msg.obj);
- } else if (msg.what == EXIT_CONDITION_CHANGED) {
- handleExitConditionChanged((Condition) msg.obj);
- } else if (msg.what == UPDATE_ZEN) {
- handleUpdateZen(msg.arg1);
+ switch (msg.what) {
+ case UPDATE_CONDITIONS: handleUpdateConditions((Condition[]) msg.obj); break;
+ case MANUAL_RULE_CHANGED: handleUpdateManualRule((ZenRule) msg.obj); break;
+ case UPDATE_WIDGETS: updateWidgets(); break;
}
}
}
public interface Callback {
- void onMoreSettings();
+ void onPrioritySettings();
void onInteraction();
void onExpanded(boolean expanded);
}
@@ -830,32 +882,29 @@ public class ZenModePanel extends LinearLayout {
Condition condition;
}
- private final class Prefs implements OnSharedPreferenceChangeListener {
- private static final String KEY_MINUTE_INDEX = "minuteIndex";
- private static final String KEY_NONE_SELECTED = "noneSelected";
-
+ private final class ZenPrefs implements OnSharedPreferenceChangeListener {
private final int mNoneDangerousThreshold;
private int mMinuteIndex;
private int mNoneSelected;
+ private boolean mConfirmedPriorityIntroduction;
+ private boolean mConfirmedSilenceIntroduction;
- private Prefs() {
+ private ZenPrefs() {
mNoneDangerousThreshold = mContext.getResources()
.getInteger(R.integer.zen_mode_alarm_warning_threshold);
- prefs().registerOnSharedPreferenceChangeListener(this);
+ Prefs.registerListener(mContext, this);
updateMinuteIndex();
updateNoneSelected();
- }
-
- public boolean isNoneDangerous() {
- return mNoneSelected < mNoneDangerousThreshold;
+ updateConfirmedPriorityIntroduction();
+ updateConfirmedSilenceIntroduction();
}
public void trackNoneSelected() {
mNoneSelected = clampNoneSelected(mNoneSelected + 1);
if (DEBUG) Log.d(mTag, "Setting none selected: " + mNoneSelected + " threshold="
+ mNoneDangerousThreshold);
- prefs().edit().putInt(KEY_NONE_SELECTED, mNoneSelected).apply();
+ Prefs.putInt(mContext, Prefs.Key.DND_NONE_SELECTED, mNoneSelected);
}
public int getMinuteIndex() {
@@ -867,21 +916,20 @@ public class ZenModePanel extends LinearLayout {
if (minuteIndex == mMinuteIndex) return;
mMinuteIndex = clampIndex(minuteIndex);
if (DEBUG) Log.d(mTag, "Setting favorite minute index: " + mMinuteIndex);
- prefs().edit().putInt(KEY_MINUTE_INDEX, mMinuteIndex).apply();
+ Prefs.putInt(mContext, Prefs.Key.DND_FAVORITE_BUCKET_INDEX, mMinuteIndex);
}
@Override
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
updateMinuteIndex();
updateNoneSelected();
- }
-
- private SharedPreferences prefs() {
- return mContext.getSharedPreferences(mContext.getPackageName(), 0);
+ updateConfirmedPriorityIntroduction();
+ updateConfirmedSilenceIntroduction();
}
private void updateMinuteIndex() {
- mMinuteIndex = clampIndex(prefs().getInt(KEY_MINUTE_INDEX, DEFAULT_BUCKET_INDEX));
+ mMinuteIndex = clampIndex(Prefs.getInt(mContext,
+ Prefs.Key.DND_FAVORITE_BUCKET_INDEX, DEFAULT_BUCKET_INDEX));
if (DEBUG) Log.d(mTag, "Favorite minute index: " + mMinuteIndex);
}
@@ -890,24 +938,51 @@ public class ZenModePanel extends LinearLayout {
}
private void updateNoneSelected() {
- mNoneSelected = clampNoneSelected(prefs().getInt(KEY_NONE_SELECTED, 0));
+ mNoneSelected = clampNoneSelected(Prefs.getInt(mContext,
+ Prefs.Key.DND_NONE_SELECTED, 0));
if (DEBUG) Log.d(mTag, "None selected: " + mNoneSelected);
}
private int clampNoneSelected(int noneSelected) {
return MathUtils.constrain(noneSelected, 0, Integer.MAX_VALUE);
}
+
+ private void updateConfirmedPriorityIntroduction() {
+ final boolean confirmed = Prefs.getBoolean(mContext,
+ Prefs.Key.DND_CONFIRMED_PRIORITY_INTRODUCTION, false);
+ if (confirmed == mConfirmedPriorityIntroduction) return;
+ mConfirmedPriorityIntroduction = confirmed;
+ if (DEBUG) Log.d(mTag, "Confirmed priority introduction: "
+ + mConfirmedPriorityIntroduction);
+ }
+
+ private void updateConfirmedSilenceIntroduction() {
+ final boolean confirmed = Prefs.getBoolean(mContext,
+ Prefs.Key.DND_CONFIRMED_SILENCE_INTRODUCTION, false);
+ if (confirmed == mConfirmedSilenceIntroduction) return;
+ mConfirmedSilenceIntroduction = confirmed;
+ if (DEBUG) Log.d(mTag, "Confirmed silence introduction: "
+ + mConfirmedSilenceIntroduction);
+ }
}
private final SegmentedButtons.Callback mZenButtonsCallback = new SegmentedButtons.Callback() {
@Override
- public void onSelected(final Object value) {
- if (value != null && mZenButtons.isShown()) {
- if (DEBUG) Log.d(mTag, "mZenButtonsCallback selected=" + value);
+ public void onSelected(final Object value, boolean fromClick) {
+ if (value != null && mZenButtons.isShown() && isAttachedToWindow()) {
+ final int zen = (Integer) value;
+ if (fromClick) {
+ MetricsLogger.action(mContext, MetricsLogger.QS_DND_ZEN_SELECT, zen);
+ }
+ if (DEBUG) Log.d(mTag, "mZenButtonsCallback selected=" + zen);
+ final Uri realConditionId = getRealConditionId(mSessionExitCondition);
AsyncTask.execute(new Runnable() {
@Override
public void run() {
- mController.setZen((Integer) value);
+ mController.setZen(zen, realConditionId, TAG + ".selectZen");
+ if (zen != Global.ZEN_MODE_OFF) {
+ Prefs.putInt(mContext, Prefs.Key.DND_FAVORITE_ZEN, zen);
+ }
}
});
}