summaryrefslogtreecommitdiffstats
path: root/packages/SystemUI/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/SystemUI/src')
-rw-r--r--packages/SystemUI/src/com/android/systemui/DemoMode.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/EventLogConstants.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/EventLogTags.logtags19
-rw-r--r--packages/SystemUI/src/com/android/systemui/ExpandHelper.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/ImageWallpaper.java220
-rw-r--r--packages/SystemUI/src/com/android/systemui/SwipeHelper.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/ViewInvertHelper.java101
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeHost.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeLog.java46
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeService.java129
-rw-r--r--packages/SystemUI/src/com/android/systemui/egg/LLand.java89
-rw-r--r--packages/SystemUI/src/com/android/systemui/egg/LLandActivity.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java104
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java221
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java58
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/PowerUI.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSContainer.java76
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSDualTileLabel.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanel.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSTile.java116
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSTileView.java53
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/SignalTileView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/UsageTracker.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java79
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/DataUsageDetailView.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java99
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java45
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java56
-rw-r--r--packages/SystemUI/src/com/android/systemui/recent/Recents.java44
-rw-r--r--packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/recent/ScreenPinningRequest.java283
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java500
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/Constants.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java207
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java78
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsUserEventProxyReceiver.java70
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/misc/DozeTrigger.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java88
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/model/KeyStoreLruCache.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java64
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java237
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java349
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/model/Task.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java101
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java118
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java314
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java104
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java28
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java68
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java364
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskViewFooter.java98
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java115
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java220
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/ViewAnimation.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/ViewPool.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java106
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedImageView.java51
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java169
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/DismissView.java40
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/DismissViewButton.java136
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/DismissViewImageButton.java64
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java39
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java104
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationCustomViewWrapper.java44
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaViewWrapper.java (renamed from packages/SystemUI/src/com/android/systemui/statusbar/policy/Prefs.java)25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java251
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java61
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java201
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java266
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java62
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java43
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardTouchDelegate.java208
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java385
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java90
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java411
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java174
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/SecureCameraLaunchManager.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java47
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/TrustDrawable.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java53
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java (renamed from packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiAccessPointController.java)135
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityContentDescriptions.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java334
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothUtil.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java92
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java45
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java88
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherScrim.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileDataControllerImpl.java (renamed from packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileDataController.java)23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java72
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java2781
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitClockView.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java144
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoController.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiIcons.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/WimaxIcons.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java228
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java35
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/IconPulser.java48
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/SegmentedButtons.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java337
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java493
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/ZenToast.java163
156 files changed, 9165 insertions, 4862 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/DemoMode.java b/packages/SystemUI/src/com/android/systemui/DemoMode.java
index c16c3a1..9c206e2 100644
--- a/packages/SystemUI/src/com/android/systemui/DemoMode.java
+++ b/packages/SystemUI/src/com/android/systemui/DemoMode.java
@@ -32,4 +32,5 @@ public interface DemoMode {
public static final String COMMAND_BARS = "bars";
public static final String COMMAND_STATUS = "status";
public static final String COMMAND_NOTIFICATIONS = "notifications";
+ public static final String COMMAND_VOLUME = "volume";
}
diff --git a/packages/SystemUI/src/com/android/systemui/EventLogConstants.java b/packages/SystemUI/src/com/android/systemui/EventLogConstants.java
new file mode 100644
index 0000000..c8af2d4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/EventLogConstants.java
@@ -0,0 +1,37 @@
+/*
+ * 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;
+
+/**
+ * Constants to be passed as sysui_* eventlog parameters.
+ */
+public class EventLogConstants {
+ /** The user swiped up on the lockscreen, unlocking the device. */
+ public static final int SYSUI_LOCKSCREEN_GESTURE_SWIPE_UP_UNLOCK = 1;
+ /** The user swiped down on the lockscreen, going to the full shade. */
+ public static final int SYSUI_LOCKSCREEN_GESTURE_SWIPE_DOWN_FULL_SHADE = 2;
+ /** The user tapped in an empty area, causing the unlock hint to be shown. */
+ public static final int SYSUI_LOCKSCREEN_GESTURE_TAP_UNLOCK_HINT = 3;
+ /** The user swiped inward on the camera icon, launching the camera. */
+ public static final int SYSUI_LOCKSCREEN_GESTURE_SWIPE_CAMERA = 4;
+ /** The user swiped inward on the dialer icon, launching the dialer. */
+ public static final int SYSUI_LOCKSCREEN_GESTURE_SWIPE_DIALER = 5;
+ /** The user tapped the lock, locking the device. */
+ 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;
+}
diff --git a/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags b/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags
index 5f2c348..d2ce94b 100644
--- a/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags
+++ b/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags
@@ -7,6 +7,16 @@ option java_package com.android.systemui;
# ---------------------------
36000 sysui_statusbar_touch (type|1),(x|1),(y|1),(enabled|1)
36001 sysui_heads_up_status (key|3),(visible|1)
+36002 sysui_fullscreen_notification (key|3)
+36003 sysui_heads_up_escalation (key|3)
+# sysui_status_bar_state: Logged whenever the status bar / keyguard state changes
+## state: 0: SHADE, 1: KEYGUARD, 2: SHADE_LOCKED
+## keyguardShowing: 1: Keyguard shown to the user (or keyguardOccluded)
+## keyguardOccluded: 1: Keyguard active, but another activity is occluding it
+## bouncerShowing: 1: Bouncer currently shown to the user
+## secure: 1: The user has set up a secure unlock method (PIN, password, etc.)
+## currentlyInsecure: 1: No secure unlock method set up (!secure), or trusted environment (TrustManager)
+36004 sysui_status_bar_state (state|1),(keyguardShowing|1),(keyguardOccluded|1),(bouncerShowing|1),(secure|1),(currentlyInsecure|1)
# ---------------------------
# PhoneStatusBarView.java
@@ -17,6 +27,15 @@ option java_package com.android.systemui;
# NotificationPanelView.java
# ---------------------------
36020 sysui_notificationpanel_touch (type|1),(x|1),(y|1)
+## type: 1: SWIPE_UP_UNLOCK Swiped up to dismiss the lockscreen.
+## 2: SWIPE_DOWN_FULL_SHADE Swiped down to enter full shade.
+## 3: TAP_UNLOCK_HINT Tapped in empty area, causes unlock hint.
+## 4: SWIPE_CAMERA Swiped the camera icon, launches.
+## 5: SWIPE_DIALER Swiped the dialer icon, launches.
+## 6: TAP_LOCK Tapped the (unlocked) lock icon, locks the device.
+## 7: TAP_NOTIFICATION_ACTIVATE Tapped a lockscreen notification, causes "tap again" hint.
+## Note: Second tap logged as notification_clicked.
+36021 sysui_lockscreen_gesture (type|1),(lengthDp|1),(velocityDp|1)
# ---------------------------
# SettingsPanelView.java
diff --git a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
index 0f8fe1c..d42ac61 100644
--- a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
@@ -351,6 +351,9 @@ public class ExpandHelper implements Gefingerpoken {
mVelocityTracker.addMovement(event);
break;
case MotionEvent.ACTION_MOVE:
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
mVelocityTracker.addMovement(event);
break;
default:
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index 4857adc..7c725b3 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -105,10 +105,6 @@ public class ImageWallpaper extends WallpaperService {
static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
static final int EGL_OPENGL_ES2_BIT = 4;
- // TODO: Not currently used, keeping around until we know we don't need it
- @SuppressWarnings({"UnusedDeclaration"})
- private WallpaperObserver mReceiver;
-
Bitmap mBackground;
int mBackgroundWidth = -1, mBackgroundHeight = -1;
int mLastSurfaceWidth = -1, mLastSurfaceHeight = -1;
@@ -151,22 +147,6 @@ public class ImageWallpaper extends WallpaperService {
private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
- class WallpaperObserver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (DEBUG) {
- Log.d(TAG, "onReceive");
- }
-
- mLastSurfaceWidth = mLastSurfaceHeight = -1;
- mBackground = null;
- mBackgroundWidth = -1;
- mBackgroundHeight = -1;
- mRedrawNeeded = true;
- drawFrame();
- }
- }
-
public DrawableEngine() {
super();
setFixedSizeAllowed(true);
@@ -174,7 +154,7 @@ public class ImageWallpaper extends WallpaperService {
public void trimMemory(int level) {
if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW &&
- mBackground != null && mIsHwAccelerated) {
+ mBackground != null) {
if (DEBUG) {
Log.d(TAG, "trimMemory");
}
@@ -194,12 +174,6 @@ public class ImageWallpaper extends WallpaperService {
super.onCreate(surfaceHolder);
- // TODO: Don't need this currently because the wallpaper service
- // will restart the image wallpaper whenever the image changes.
- //IntentFilter filter = new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED);
- //mReceiver = new WallpaperObserver();
- //registerReceiver(mReceiver, filter, null, mHandler);
-
updateSurfaceSize(surfaceHolder);
setOffsetNotificationsEnabled(false);
@@ -208,10 +182,8 @@ public class ImageWallpaper extends WallpaperService {
@Override
public void onDestroy() {
super.onDestroy();
- if (mReceiver != null) {
- unregisterReceiver(mReceiver);
- }
mBackground = null;
+ mWallpaperManager.forgetLoadedWallpaper();
}
void updateSurfaceSize(SurfaceHolder surfaceHolder) {
@@ -337,111 +309,116 @@ public class ImageWallpaper extends WallpaperService {
}
void drawFrame() {
- int newRotation = ((WindowManager) getSystemService(WINDOW_SERVICE)).
- getDefaultDisplay().getRotation();
-
- // 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());
- }
- SurfaceHolder sh = getSurfaceHolder();
- final Rect frame = sh.getSurfaceFrame();
- final int dw = frame.width();
- final int dh = frame.height();
- boolean surfaceDimensionsChanged = dw != mLastSurfaceWidth || dh != mLastSurfaceHeight;
-
- boolean redrawNeeded = surfaceDimensionsChanged || newRotation != mLastRotation;
- if (!redrawNeeded && !mOffsetsChanged) {
- if (DEBUG) {
- Log.d(TAG, "Suppressed drawFrame since redraw is not needed "
- + "and offsets have not changed.");
+ try {
+ int newRotation = ((WindowManager) getSystemService(WINDOW_SERVICE)).
+ getDefaultDisplay().getRotation();
+
+ // 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());
}
- return;
- }
- mLastRotation = newRotation;
+ SurfaceHolder sh = getSurfaceHolder();
+ final Rect frame = sh.getSurfaceFrame();
+ final int dw = frame.width();
+ final int dh = frame.height();
+ boolean surfaceDimensionsChanged = dw != mLastSurfaceWidth
+ || dh != mLastSurfaceHeight;
- // Load bitmap if it is not yet loaded or if it was loaded at a different size
- if (mBackground == null || surfaceDimensionsChanged) {
- if (DEBUG) {
- Log.d(TAG, "Reloading bitmap: mBackground, bgw, bgh, dw, dh = " +
- mBackground + ", " +
- ((mBackground == null) ? 0 : mBackground.getWidth()) + ", " +
- ((mBackground == null) ? 0 : mBackground.getHeight()) + ", " +
- dw + ", " + dh);
- }
- mWallpaperManager.forgetLoadedWallpaper();
- updateWallpaperLocked();
- if (mBackground == null) {
+ boolean redrawNeeded = surfaceDimensionsChanged || newRotation != mLastRotation;
+ if (!redrawNeeded && !mOffsetsChanged) {
if (DEBUG) {
- Log.d(TAG, "Unable to load bitmap");
+ Log.d(TAG, "Suppressed drawFrame since redraw is not needed "
+ + "and offsets have not changed.");
}
return;
}
- if (DEBUG) {
- if (dw != mBackground.getWidth() || dh != mBackground.getHeight()) {
- Log.d(TAG, "Surface != bitmap dimensions: surface w/h, bitmap w/h: " +
- dw + ", " + dh + ", " + mBackground.getWidth() + ", " +
- mBackground.getHeight());
+ mLastRotation = newRotation;
+
+ // Load bitmap if it is not yet loaded or if it was loaded at a different size
+ if (mBackground == null || surfaceDimensionsChanged) {
+ if (DEBUG) {
+ Log.d(TAG, "Reloading bitmap: mBackground, bgw, bgh, dw, dh = " +
+ mBackground + ", " +
+ ((mBackground == null) ? 0 : mBackground.getWidth()) + ", " +
+ ((mBackground == null) ? 0 : mBackground.getHeight()) + ", " +
+ dw + ", " + dh);
+ }
+ mWallpaperManager.forgetLoadedWallpaper();
+ updateWallpaperLocked();
+ if (mBackground == null) {
+ if (DEBUG) {
+ Log.d(TAG, "Unable to load bitmap");
+ }
+ return;
+ }
+ if (DEBUG) {
+ if (dw != mBackground.getWidth() || dh != mBackground.getHeight()) {
+ Log.d(TAG, "Surface != bitmap dimensions: surface w/h, bitmap w/h: " +
+ dw + ", " + dh + ", " + mBackground.getWidth() + ", " +
+ mBackground.getHeight());
+ }
}
}
- }
- // Center the scaled image
- mScale = Math.max(1f, Math.max(dw / (float) mBackground.getWidth(),
- dh / (float) mBackground.getHeight()));
- final int availw = dw - (int) (mBackground.getWidth() * mScale);
- final int availh = dh - (int) (mBackground.getHeight() * mScale);
- int xPixels = availw / 2;
- int yPixels = availh / 2;
-
- // Adjust the image for xOffset/yOffset values. If window manager is handling offsets,
- // mXOffset and mYOffset are set to 0.5f by default and therefore xPixels and yPixels
- // will remain unchanged
- final int availwUnscaled = dw - mBackground.getWidth();
- final int availhUnscaled = dh - mBackground.getHeight();
- if (availwUnscaled < 0) xPixels += (int)(availwUnscaled * (mXOffset - .5f) + .5f);
- if (availhUnscaled < 0) yPixels += (int)(availhUnscaled * (mYOffset - .5f) + .5f);
-
- mOffsetsChanged = false;
- mRedrawNeeded = false;
- if (surfaceDimensionsChanged) {
- mLastSurfaceWidth = dw;
- mLastSurfaceHeight = dh;
- }
- if (!redrawNeeded && xPixels == mLastXTranslation && yPixels == mLastYTranslation) {
- if (DEBUG) {
- Log.d(TAG, "Suppressed drawFrame since the image has not "
- + "actually moved an integral number of pixels.");
+ // Center the scaled image
+ mScale = Math.max(1f, Math.max(dw / (float) mBackground.getWidth(),
+ dh / (float) mBackground.getHeight()));
+ final int availw = dw - (int) (mBackground.getWidth() * mScale);
+ final int availh = dh - (int) (mBackground.getHeight() * mScale);
+ int xPixels = availw / 2;
+ int yPixels = availh / 2;
+
+ // Adjust the image for xOffset/yOffset values. If window manager is handling offsets,
+ // mXOffset and mYOffset are set to 0.5f by default and therefore xPixels and yPixels
+ // will remain unchanged
+ final int availwUnscaled = dw - mBackground.getWidth();
+ final int availhUnscaled = dh - mBackground.getHeight();
+ if (availwUnscaled < 0)
+ xPixels += (int) (availwUnscaled * (mXOffset - .5f) + .5f);
+ if (availhUnscaled < 0)
+ yPixels += (int) (availhUnscaled * (mYOffset - .5f) + .5f);
+
+ mOffsetsChanged = false;
+ mRedrawNeeded = false;
+ if (surfaceDimensionsChanged) {
+ mLastSurfaceWidth = dw;
+ mLastSurfaceHeight = dh;
}
- return;
- }
- mLastXTranslation = xPixels;
- mLastYTranslation = yPixels;
+ if (!redrawNeeded && xPixels == mLastXTranslation && yPixels == mLastYTranslation) {
+ if (DEBUG) {
+ Log.d(TAG, "Suppressed drawFrame since the image has not "
+ + "actually moved an integral number of pixels.");
+ }
+ return;
+ }
+ mLastXTranslation = xPixels;
+ mLastYTranslation = yPixels;
- if (DEBUG) {
- Log.d(TAG, "Redrawing wallpaper");
- }
+ if (DEBUG) {
+ Log.d(TAG, "Redrawing wallpaper");
+ }
- if (mIsHwAccelerated) {
- if (!drawWallpaperWithOpenGL(sh, availw, availh, xPixels, yPixels)) {
+ if (mIsHwAccelerated) {
+ if (!drawWallpaperWithOpenGL(sh, availw, availh, xPixels, yPixels)) {
+ drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels);
+ }
+ } else {
drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels);
}
- } else {
- drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels);
- if (FIXED_SIZED_SURFACE) {
+ } finally {
+ if (FIXED_SIZED_SURFACE && !mIsHwAccelerated) {
// If the surface is fixed-size, we should only need to
// draw it once and then we'll let the window manager
// position it appropriately. As such, we no longer needed
// the loaded bitmap. Yay!
- // hw-accelerated path retains bitmap for faster rotation
+ // hw-accelerated renderer retains bitmap for faster rotation
mBackground = null;
mWallpaperManager.forgetLoadedWallpaper();
}
}
-
}
private void updateWallpaperLocked() {
@@ -556,7 +533,7 @@ public class ImageWallpaper extends WallpaperService {
boolean status = mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);
checkEglError();
- finishGL();
+ finishGL(texture, program);
return status;
}
@@ -609,21 +586,18 @@ public class ImageWallpaper extends WallpaperService {
int program = glCreateProgram();
glAttachShader(program, vertexShader);
- checkGlError();
-
glAttachShader(program, fragmentShader);
- checkGlError();
-
glLinkProgram(program);
checkGlError();
+ glDeleteShader(vertexShader);
+ glDeleteShader(fragmentShader);
+
int[] status = new int[1];
glGetProgramiv(program, GL_LINK_STATUS, status, 0);
if (status[0] != GL_TRUE) {
String error = glGetProgramInfoLog(program);
Log.d(GL_LOG_TAG, "Error while linking program:\n" + error);
- glDeleteShader(vertexShader);
- glDeleteShader(fragmentShader);
glDeleteProgram(program);
return 0;
}
@@ -666,7 +640,11 @@ public class ImageWallpaper extends WallpaperService {
}
}
- private void finishGL() {
+ private void finishGL(int texture, int program) {
+ int[] textures = new int[1];
+ textures[0] = texture;
+ glDeleteTextures(1, textures, 0);
+ glDeleteProgram(program);
mEgl.eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
mEgl.eglDestroyContext(mEglDisplay, mEglContext);
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 0d393bf..33f6564 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -311,11 +311,14 @@ public class SwipeHelper implements Gefingerpoken {
final View animView = mCallback.getChildContentView(view);
final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(view);
float newPos;
+ boolean isLayoutRtl = view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
if (velocity < 0
|| (velocity == 0 && getTranslation(animView) < 0)
// if we use the Menu to dismiss an item in landscape, animate up
- || (velocity == 0 && getTranslation(animView) == 0 && mSwipeDirection == Y)) {
+ || (velocity == 0 && getTranslation(animView) == 0 && mSwipeDirection == Y)
+ // if the language is rtl we prefer swiping to the left
+ || (velocity == 0 && getTranslation(animView) == 0 && isLayoutRtl)) {
newPos = -getSize(animView);
} else {
newPos = getSize(animView);
@@ -450,7 +453,8 @@ public class SwipeHelper implements Gefingerpoken {
&& !mTouchAboveFalsingThreshold;
boolean dismissChild = mCallback.canChildBeDismissed(mCurrView)
- && !falsingDetected && (childSwipedFastEnough || childSwipedFarEnough);
+ && !falsingDetected && (childSwipedFastEnough || childSwipedFarEnough)
+ && ev.getActionMasked() == MotionEvent.ACTION_UP;
if (dismissChild) {
// flingadingy
diff --git a/packages/SystemUI/src/com/android/systemui/ViewInvertHelper.java b/packages/SystemUI/src/com/android/systemui/ViewInvertHelper.java
new file mode 100644
index 0000000..2ff8f8a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ViewInvertHelper.java
@@ -0,0 +1,101 @@
+/*
+ * 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.ValueAnimator;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Paint;
+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.
+ */
+public class ViewInvertHelper {
+
+ private final Paint mDarkPaint = new Paint();
+ private final Interpolator mLinearOutSlowInInterpolator;
+ private final View mTarget;
+ private final ColorMatrix mMatrix = new ColorMatrix();
+ private final ColorMatrix mGrayscaleMatrix = new ColorMatrix();
+ private final long mFadeDuration;
+
+ public ViewInvertHelper(View target, long fadeDuration) {
+ mTarget = target;
+ mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(mTarget.getContext(),
+ android.R.interpolator.linear_out_slow_in);
+ mFadeDuration = fadeDuration;
+ }
+
+ public void fade(final boolean invert, long delay) {
+ float startIntensity = invert ? 0f : 1f;
+ float endIntensity = invert ? 1f : 0f;
+ ValueAnimator animator = ValueAnimator.ofFloat(startIntensity, endIntensity);
+ animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ updateInvertPaint((Float) animation.getAnimatedValue());
+ mTarget.setLayerType(View.LAYER_TYPE_HARDWARE, mDarkPaint);
+ }
+ });
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (!invert) {
+ mTarget.setLayerType(View.LAYER_TYPE_NONE, null);
+ }
+ }
+ });
+ animator.setDuration(mFadeDuration);
+ animator.setInterpolator(mLinearOutSlowInInterpolator);
+ animator.setStartDelay(delay);
+ animator.start();
+ }
+
+ public void update(boolean invert) {
+ if (invert) {
+ updateInvertPaint(1f);
+ mTarget.setLayerType(View.LAYER_TYPE_HARDWARE, mDarkPaint);
+ } else {
+ mTarget.setLayerType(View.LAYER_TYPE_NONE, null);
+ }
+ }
+
+ public View getTarget() {
+ return mTarget;
+ }
+
+ private void updateInvertPaint(float intensity) {
+ float components = 1 - 2 * intensity;
+ final float[] invert = {
+ components, 0f, 0f, 0f, 255f * intensity,
+ 0f, components, 0f, 0f, 255f * intensity,
+ 0f, 0f, components, 0f, 255f * intensity,
+ 0f, 0f, 0f, 1f, 0f
+ };
+ mMatrix.set(invert);
+ mGrayscaleMatrix.setSaturation(1 - intensity);
+ mMatrix.preConcat(mGrayscaleMatrix);
+ mDarkPaint.setColorFilter(new ColorMatrixColorFilter(mMatrix));
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
index e92f988..89a2c74 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
@@ -25,9 +25,10 @@ public interface DozeHost {
void addCallback(@NonNull Callback callback);
void removeCallback(@NonNull Callback callback);
void startDozing(@NonNull Runnable ready);
- void pulseWhileDozing(@NonNull PulseCallback callback);
+ void pulseWhileDozing(@NonNull PulseCallback callback, int reason);
void stopDozing();
boolean isPowerSaveActive();
+ boolean isNotificationLightOn();
public interface Callback {
void onNewNotifications();
@@ -40,4 +41,4 @@ public interface DozeHost {
void onPulseStarted();
void onPulseFinished();
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index fcdbfc1..1f3a830 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -35,6 +35,13 @@ public class DozeLog {
private static final int SIZE = Build.IS_DEBUGGABLE ? 400 : 50;
private static final SimpleDateFormat FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
+ private static final int PULSE_REASONS = 4;
+
+ public static final int PULSE_REASON_INTENT = 0;
+ public static final int PULSE_REASON_NOTIFICATION = 1;
+ public static final int PULSE_REASON_SENSOR_SIGMOTION = 2;
+ public static final int PULSE_REASON_SENSOR_PICKUP = 3;
+
private static long[] sTimes;
private static String[] sMessages;
private static int sPosition;
@@ -48,8 +55,7 @@ public class DozeLog {
private static SummaryStats sScreenOnPulsingStats;
private static SummaryStats sScreenOnNotPulsingStats;
private static SummaryStats sEmergencyCallStats;
- private static SummaryStats sProxNearStats;
- private static SummaryStats sProxFarStats;
+ private static SummaryStats[][] sProxStats; // [reason][near/far]
public static void tracePickupPulse(boolean withinVibrationThreshold) {
if (!ENABLED) return;
@@ -58,10 +64,10 @@ public class DozeLog {
: sPickupPulseNotNearVibrationStats).append();
}
- public static void tracePulseStart() {
+ public static void tracePulseStart(int reason) {
if (!ENABLED) return;
sPulsing = true;
- log("pulseStart");
+ log("pulseStart reason=" + pulseReasonToString(reason));
}
public static void tracePulseFinish() {
@@ -90,8 +96,11 @@ public class DozeLog {
sScreenOnPulsingStats = new SummaryStats();
sScreenOnNotPulsingStats = new SummaryStats();
sEmergencyCallStats = new SummaryStats();
- sProxNearStats = new SummaryStats();
- sProxFarStats = new SummaryStats();
+ sProxStats = new SummaryStats[PULSE_REASONS][2];
+ for (int i = 0; i < PULSE_REASONS; i++) {
+ sProxStats[i][0] = new SummaryStats();
+ sProxStats[i][1] = new SummaryStats();
+ }
log("init");
KeyguardUpdateMonitor.getInstance(context).registerCallback(sKeyguardCallback);
}
@@ -137,10 +146,21 @@ public class DozeLog {
}
}
- public static void traceProximityResult(boolean near, long millis) {
+ public static void traceProximityResult(boolean near, long millis, int pulseReason) {
if (!ENABLED) return;
- log("proximityResult near=" + near + " millis=" + millis);
- (near ? sProxNearStats : sProxFarStats).append();
+ log("proximityResult reason=" + pulseReasonToString(pulseReason) + " near=" + near
+ + " millis=" + millis);
+ sProxStats[pulseReason][near ? 0 : 1].append();
+ }
+
+ public static String pulseReasonToString(int pulseReason) {
+ switch (pulseReason) {
+ case PULSE_REASON_INTENT: return "intent";
+ case PULSE_REASON_NOTIFICATION: return "notification";
+ case PULSE_REASON_SENSOR_SIGMOTION: return "sigmotion";
+ case PULSE_REASON_SENSOR_PICKUP: return "pickup";
+ default: throw new IllegalArgumentException("bad reason: " + pulseReason);
+ }
}
public static void dump(PrintWriter pw) {
@@ -164,8 +184,11 @@ public class DozeLog {
sScreenOnPulsingStats.dump(pw, "Screen on (pulsing)");
sScreenOnNotPulsingStats.dump(pw, "Screen on (not pulsing)");
sEmergencyCallStats.dump(pw, "Emergency call");
- sProxNearStats.dump(pw, "Proximity (near)");
- sProxFarStats.dump(pw, "Proximity (far)");
+ for (int i = 0; i < PULSE_REASONS; i++) {
+ final String reason = pulseReasonToString(i);
+ sProxStats[i][0].dump(pw, "Proximity near (" + reason + ")");
+ sProxStats[i][1].dump(pw, "Proximity far (" + reason + ")");
+ }
}
}
@@ -188,6 +211,7 @@ public class DozeLog {
}
public void dump(PrintWriter pw, String type) {
+ if (mCount == 0) return;
pw.print(" ");
pw.print(type);
pw.print(": n=");
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
index f8c5e9c..8d27450 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
@@ -56,6 +56,17 @@ public class DozeService extends DreamService {
private static final String NOTIFICATION_PULSE_ACTION = ACTION_BASE + ".notification_pulse";
private static final String EXTRA_INSTANCE = "instance";
+ /**
+ * Earliest time we pulse due to a notification light after the service started.
+ *
+ * <p>Incoming notification light events during the blackout period are
+ * delayed to the earliest time defined by this constant.</p>
+ *
+ * <p>This delay avoids a pulse immediately after screen off, at which
+ * point the notification light is re-enabled again by NoMan.</p>
+ */
+ private static final int EARLIEST_LIGHT_PULSE_AFTER_START_MS = 10 * 1000;
+
private final String mTag = String.format(TAG + ".%08x", hashCode());
private final Context mContext = this;
private final DozeParameters mDozeParameters = new DozeParameters(mContext);
@@ -77,6 +88,7 @@ public class DozeService extends DreamService {
private boolean mPowerSaveActive;
private boolean mCarMode;
private long mNotificationPulseTime;
+ private long mEarliestPulseDueToLight;
private int mScheduleResetsRemaining;
public DozeService() {
@@ -118,9 +130,11 @@ public class DozeService extends DreamService {
mSensors = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
mSigMotionSensor = new TriggerSensor(Sensor.TYPE_SIGNIFICANT_MOTION,
- mDozeParameters.getPulseOnSigMotion(), mDozeParameters.getVibrateOnSigMotion());
+ mDozeParameters.getPulseOnSigMotion(), mDozeParameters.getVibrateOnSigMotion(),
+ DozeLog.PULSE_REASON_SENSOR_SIGMOTION);
mPickupSensor = new TriggerSensor(Sensor.TYPE_PICK_UP_GESTURE,
- mDozeParameters.getPulseOnPickup(), mDozeParameters.getVibrateOnPickup());
+ mDozeParameters.getPulseOnPickup(), mDozeParameters.getVibrateOnPickup(),
+ DozeLog.PULSE_REASON_SENSOR_PICKUP);
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, mTag);
mWakeLock.setReferenceCounted(true);
@@ -159,8 +173,9 @@ public class DozeService extends DreamService {
}
mDreaming = true;
- listenForPulseSignals(true);
rescheduleNotificationPulse(false /*predicate*/); // cancel any pending pulse alarms
+ mEarliestPulseDueToLight = System.currentTimeMillis() + EARLIEST_LIGHT_PULSE_AFTER_START_MS;
+ listenForPulseSignals(true);
// Ask the host to get things ready to start dozing.
// Once ready, we call startDozing() at which point the CPU may suspend
@@ -195,20 +210,37 @@ public class DozeService extends DreamService {
mHost.stopDozing();
}
- private void requestPulse() {
+ private void requestPulse(final int reason) {
if (mHost != null && mDreaming && !mPulsing) {
// Let the host know we want to pulse. Wait for it to be ready, then
// turn the screen on. When finished, turn the screen off again.
// Here we need a wakelock to stay awake until the pulse is finished.
mWakeLock.acquire();
mPulsing = true;
+ if (!mDozeParameters.getProxCheckBeforePulse()) {
+ // skip proximity check
+ continuePulsing(reason);
+ return;
+ }
final long start = SystemClock.uptimeMillis();
+ final boolean nonBlocking = reason == DozeLog.PULSE_REASON_SENSOR_PICKUP
+ && mDozeParameters.getPickupPerformsProxCheck();
+ if (nonBlocking) {
+ // proximity check is only done to capture statistics, continue pulsing
+ continuePulsing(reason);
+ }
+ // perform a proximity check
new ProximityCheck() {
@Override
public void onProximityResult(int result) {
- // avoid pulsing in pockets
final boolean isNear = result == RESULT_NEAR;
- DozeLog.traceProximityResult(isNear, SystemClock.uptimeMillis() - start);
+ final long end = SystemClock.uptimeMillis();
+ DozeLog.traceProximityResult(isNear, end - start, reason);
+ if (nonBlocking) {
+ // we already continued
+ return;
+ }
+ // avoid pulsing in pockets
if (isNear) {
mPulsing = false;
mWakeLock.release();
@@ -216,28 +248,32 @@ public class DozeService extends DreamService {
}
// not in-pocket, continue pulsing
- mHost.pulseWhileDozing(new DozeHost.PulseCallback() {
- @Override
- public void onPulseStarted() {
- if (mPulsing && mDreaming) {
- turnDisplayOn();
- }
- }
-
- @Override
- public void onPulseFinished() {
- if (mPulsing && mDreaming) {
- mPulsing = false;
- turnDisplayOff();
- }
- mWakeLock.release(); // needs to be unconditional to balance acquire
- }
- });
+ continuePulsing(reason);
}
}.check();
}
}
+ private void continuePulsing(int reason) {
+ mHost.pulseWhileDozing(new DozeHost.PulseCallback() {
+ @Override
+ public void onPulseStarted() {
+ if (mPulsing && mDreaming) {
+ turnDisplayOn();
+ }
+ }
+
+ @Override
+ public void onPulseFinished() {
+ if (mPulsing && mDreaming) {
+ mPulsing = false;
+ turnDisplayOff();
+ }
+ mWakeLock.release(); // needs to be unconditional to balance acquire
+ }
+ }, reason);
+ }
+
private void turnDisplayOff() {
if (DEBUG) Log.d(mTag, "Display off");
setDozeScreenState(Display.STATE_OFF);
@@ -285,6 +321,12 @@ public class DozeService extends DreamService {
if (listen) {
resetNotificationResets();
mHost.addCallback(mHostCallback);
+
+ // Continue to pulse for existing LEDs.
+ mNotificationLightOn = mHost.isNotificationLightOn();
+ if (mNotificationLightOn) {
+ updateNotificationPulseDueToLight();
+ }
} else {
mHost.removeCallback(mHostCallback);
}
@@ -295,21 +337,27 @@ public class DozeService extends DreamService {
mScheduleResetsRemaining = mDozeParameters.getPulseScheduleResets();
}
- private void updateNotificationPulse() {
- if (DEBUG) Log.d(mTag, "updateNotificationPulse");
+ private void updateNotificationPulseDueToLight() {
+ long timeMs = System.currentTimeMillis();
+ timeMs = Math.max(timeMs, mEarliestPulseDueToLight);
+ updateNotificationPulse(timeMs);
+ }
+
+ private void updateNotificationPulse(long notificationTimeMs) {
+ if (DEBUG) Log.d(mTag, "updateNotificationPulse notificationTimeMs=" + notificationTimeMs);
if (!mDozeParameters.getPulseOnNotifications()) return;
if (mScheduleResetsRemaining <= 0) {
if (DEBUG) Log.d(mTag, "No more schedule resets remaining");
return;
}
- final long now = System.currentTimeMillis();
- if ((now - mNotificationPulseTime) < mDozeParameters.getPulseDuration()) {
+ final long pulseDuration = mDozeParameters.getPulseDuration(false /*pickup*/);
+ if ((notificationTimeMs - mNotificationPulseTime) < pulseDuration) {
if (DEBUG) Log.d(mTag, "Recently updated, not resetting schedule");
return;
}
mScheduleResetsRemaining--;
if (DEBUG) Log.d(mTag, "mScheduleResetsRemaining = " + mScheduleResetsRemaining);
- mNotificationPulseTime = now;
+ mNotificationPulseTime = notificationTimeMs;
rescheduleNotificationPulse(true /*predicate*/);
}
@@ -370,13 +418,13 @@ public class DozeService extends DreamService {
public void onReceive(Context context, Intent intent) {
if (PULSE_ACTION.equals(intent.getAction())) {
if (DEBUG) Log.d(mTag, "Received pulse intent");
- requestPulse();
+ requestPulse(DozeLog.PULSE_REASON_INTENT);
}
if (NOTIFICATION_PULSE_ACTION.equals(intent.getAction())) {
final long instance = intent.getLongExtra(EXTRA_INSTANCE, -1);
if (DEBUG) Log.d(mTag, "Received notification pulse intent instance=" + instance);
DozeLog.traceNotificationPulse(instance);
- requestPulse();
+ requestPulse(DozeLog.PULSE_REASON_NOTIFICATION);
rescheduleNotificationPulse(mNotificationLightOn);
}
if (UiModeManager.ACTION_ENTER_CAR_MODE.equals(intent.getAction())) {
@@ -391,14 +439,14 @@ public class DozeService extends DreamService {
private final DozeHost.Callback mHostCallback = new DozeHost.Callback() {
@Override
public void onNewNotifications() {
- if (DEBUG) Log.d(mTag, "onNewNotifications");
+ if (DEBUG) Log.d(mTag, "onNewNotifications (noop)");
// noop for now
}
@Override
public void onBuzzBeepBlinked() {
if (DEBUG) Log.d(mTag, "onBuzzBeepBlinked");
- updateNotificationPulse();
+ updateNotificationPulse(System.currentTimeMillis());
}
@Override
@@ -407,7 +455,7 @@ public class DozeService extends DreamService {
if (mNotificationLightOn == on) return;
mNotificationLightOn = on;
if (mNotificationLightOn) {
- updateNotificationPulse();
+ updateNotificationPulseDueToLight();
}
}
@@ -424,15 +472,17 @@ public class DozeService extends DreamService {
private final Sensor mSensor;
private final boolean mConfigured;
private final boolean mDebugVibrate;
+ private final int mPulseReason;
private boolean mRequested;
private boolean mRegistered;
private boolean mDisabled;
- public TriggerSensor(int type, boolean configured, boolean debugVibrate) {
+ public TriggerSensor(int type, boolean configured, boolean debugVibrate, int pulseReason) {
mSensor = mSensors.getDefaultSensor(type);
mConfigured = configured;
mDebugVibrate = debugVibrate;
+ mPulseReason = pulseReason;
}
public void setListening(boolean listen) {
@@ -449,10 +499,12 @@ public class DozeService extends DreamService {
private void updateListener() {
if (!mConfigured || mSensor == null) return;
- if (mRequested && !mDisabled) {
+ if (mRequested && !mDisabled && !mRegistered) {
mRegistered = mSensors.requestTriggerSensor(this, mSensor);
+ if (DEBUG) Log.d(mTag, "requestTriggerSensor " + mRegistered);
} else if (mRegistered) {
- mSensors.cancelTriggerSensor(this, mSensor);
+ final boolean rt = mSensors.cancelTriggerSensor(this, mSensor);
+ if (DEBUG) Log.d(mTag, "cancelTriggerSensor " + rt);
mRegistered = false;
}
}
@@ -482,8 +534,9 @@ public class DozeService extends DreamService {
}
}
- requestPulse();
- setListening(true); // reregister, this sensor only fires once
+ requestPulse(mPulseReason);
+ mRegistered = false;
+ updateListener(); // reregister, this sensor only fires once
// reset the notification pulse schedule, but only if we think we were not triggered
// by a notification-related vibration
diff --git a/packages/SystemUI/src/com/android/systemui/egg/LLand.java b/packages/SystemUI/src/com/android/systemui/egg/LLand.java
index cdfe6e5..fa257b1 100644
--- a/packages/SystemUI/src/com/android/systemui/egg/LLand.java
+++ b/packages/SystemUI/src/com/android/systemui/egg/LLand.java
@@ -29,11 +29,15 @@ import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.os.Vibrator;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.MotionEvent;
+import android.util.Slog;
import android.view.View;
import android.view.ViewOutlineProvider;
import android.view.animation.DecelerateInterpolator;
@@ -51,9 +55,9 @@ public class LLand extends FrameLayout {
public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
public static final boolean DEBUG_DRAW = false; // DEBUG
- public static final void L(String s, Object ... objects) {
+ public static void L(String s, Object ... objects) {
if (DEBUG) {
- Log.d(TAG, String.format(s, objects));
+ Slog.d(TAG, objects.length == 0 ? s : String.format(s, objects));
}
}
@@ -61,17 +65,18 @@ public class LLand extends FrameLayout {
public static final boolean HAVE_STARS = true;
public static final float DEBUG_SPEED_MULTIPLIER = 1f; // 0.1f;
- public static final boolean DEBUG_IDDQD = false;
+ public static final boolean DEBUG_IDDQD = Log.isLoggable(TAG + ".iddqd", Log.DEBUG);
final static int[] POPS = {
- // resid // spinny!
- R.drawable.pop_belt, 0,
- R.drawable.pop_droid, 0,
- R.drawable.pop_pizza, 1,
- R.drawable.pop_stripes, 0,
- R.drawable.pop_swirl, 1,
- R.drawable.pop_vortex, 1,
- R.drawable.pop_vortex2, 1,
+ // resid // spinny! // alpha
+ R.drawable.pop_belt, 0, 255,
+ R.drawable.pop_droid, 0, 255,
+ R.drawable.pop_pizza, 1, 255,
+ R.drawable.pop_stripes, 0, 255,
+ R.drawable.pop_swirl, 1, 255,
+ R.drawable.pop_vortex, 1, 255,
+ R.drawable.pop_vortex2, 1, 255,
+ R.drawable.pop_ball, 0, 190,
};
private static class Params {
@@ -117,10 +122,20 @@ public class LLand extends FrameLayout {
PLAYER_Z = res.getDimensionPixelSize(R.dimen.player_z);
PLAYER_Z_BOOST = res.getDimensionPixelSize(R.dimen.player_z_boost);
HUD_Z = res.getDimensionPixelSize(R.dimen.hud_z);
+
+ // Sanity checking
+ if (OBSTACLE_MIN <= OBSTACLE_WIDTH / 2) {
+ Slog.e(TAG, "error: obstacles might be too short, adjusting");
+ OBSTACLE_MIN = OBSTACLE_WIDTH / 2 + 1;
+ }
}
}
private TimeAnimator mAnim;
+ private Vibrator mVibrator;
+ private AudioManager mAudioManager;
+ private final AudioAttributes mAudioAttrs = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_GAME).build();
private TextView mScoreField;
private View mSplash;
@@ -158,9 +173,14 @@ public class LLand extends FrameLayout {
public LLand(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
+ mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
+ mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
setFocusable(true);
PARAMS = new Params(getResources());
mTimeOfDay = irand(0, SKIES.length);
+
+ // we assume everything will be laid out left|top
+ setLayoutDirection(LAYOUT_DIRECTION_LTR);
}
@Override
@@ -198,7 +218,15 @@ public class LLand extends FrameLayout {
final float hsv[] = {0, 0, 0};
- private void reset() {
+ private void thump() {
+ if (mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_SILENT) {
+ // No interruptions. Not even game haptics.
+ return;
+ }
+ mVibrator.vibrate(80, mAudioAttrs);
+ }
+
+ public void reset() {
L("reset");
final Drawable sky = new GradientDrawable(
GradientDrawable.Orientation.BOTTOM_TOP,
@@ -313,14 +341,16 @@ public class LLand extends FrameLayout {
private void setScore(int score) {
mScore = score;
- if (mScoreField != null) mScoreField.setText(String.valueOf(score));
+ if (mScoreField != null) {
+ mScoreField.setText(DEBUG_IDDQD ? "??" : String.valueOf(score));
+ }
}
private void addScore(int incr) {
setScore(mScore + incr);
}
- private void start(boolean startPlaying) {
+ public void start(boolean startPlaying) {
L("start(startPlaying=%s)", startPlaying?"true":"false");
if (startPlaying) {
mPlaying = true;
@@ -352,7 +382,7 @@ public class LLand extends FrameLayout {
}
}
- private void stop() {
+ public void stop() {
if (mAnimating) {
mAnim.cancel();
mAnim = null;
@@ -417,8 +447,10 @@ public class LLand extends FrameLayout {
if (mPlaying && mDroid.below(mHeight)) {
if (DEBUG_IDDQD) {
poke();
+ unpoke();
} else {
L("player hit the floor");
+ thump();
stop();
}
}
@@ -429,6 +461,7 @@ public class LLand extends FrameLayout {
final Obstacle ob = mObstaclesInPlay.get(j);
if (mPlaying && ob.intersects(mDroid) && !DEBUG_IDDQD) {
L("player hit an obstacle");
+ thump();
stop();
} else if (ob.cleared(mDroid)) {
if (ob instanceof Stem) passedBarrier = true;
@@ -459,8 +492,9 @@ public class LLand extends FrameLayout {
// 3. Time for more obstacles!
if (mPlaying && (t - mLastPipeTime) > PARAMS.OBSTACLE_PERIOD) {
mLastPipeTime = t;
- final int obstacley = (int) (Math.random()
- * (mHeight - 2*PARAMS.OBSTACLE_MIN - PARAMS.OBSTACLE_GAP)) + PARAMS.OBSTACLE_MIN;
+ final int obstacley =
+ (int)(frand() * (mHeight - 2*PARAMS.OBSTACLE_MIN - PARAMS.OBSTACLE_GAP)) +
+ PARAMS.OBSTACLE_MIN;
final int inset = (PARAMS.OBSTACLE_WIDTH - PARAMS.OBSTACLE_STEM_WIDTH) / 2;
final int yinset = PARAMS.OBSTACLE_WIDTH/2;
@@ -539,7 +573,7 @@ public class LLand extends FrameLayout {
@Override
public boolean onTouchEvent(MotionEvent ev) {
- if (DEBUG) L("touch: %s", ev);
+ L("touch: %s", ev);
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
poke();
@@ -553,7 +587,7 @@ public class LLand extends FrameLayout {
@Override
public boolean onTrackballEvent(MotionEvent ev) {
- if (DEBUG) L("trackball: %s", ev);
+ L("trackball: %s", ev);
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
poke();
@@ -567,7 +601,7 @@ public class LLand extends FrameLayout {
@Override
public boolean onKeyDown(int keyCode, KeyEvent ev) {
- if (DEBUG) L("keyDown: %d", keyCode);
+ L("keyDown: %d", keyCode);
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_CENTER:
case KeyEvent.KEYCODE_DPAD_UP:
@@ -582,7 +616,7 @@ public class LLand extends FrameLayout {
@Override
public boolean onKeyUp(int keyCode, KeyEvent ev) {
- if (DEBUG) L("keyDown: %d", keyCode);
+ L("keyDown: %d", keyCode);
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_CENTER:
case KeyEvent.KEYCODE_DPAD_UP:
@@ -597,7 +631,7 @@ public class LLand extends FrameLayout {
@Override
public boolean onGenericMotionEvent (MotionEvent ev) {
- if (DEBUG) L("generic: %s", ev);
+ L("generic: %s", ev);
return false;
}
@@ -684,6 +718,10 @@ public class LLand extends FrameLayout {
private boolean mBoosting;
+ private final int[] sColors = new int[] {
+ 0xFF78C557,
+ };
+
private final float[] sHull = new float[] {
0.3f, 0f, // left antenna
0.7f, 0f, // right antenna
@@ -692,7 +730,7 @@ public class LLand extends FrameLayout {
0.6f, 1f, // right foot
0.4f, 1f, // left foot BLUE!
0.08f, 0.75f, // sinistram
- 0.08f, 0.33f, // cold shoulder
+ 0.08f, 0.33f, // cold shoulder
};
public final float[] corners = new float[sHull.length];
@@ -701,7 +739,7 @@ public class LLand extends FrameLayout {
setBackgroundResource(R.drawable.android);
getBackground().setTintMode(PorterDuff.Mode.SRC_ATOP);
- getBackground().setTint(0xFF00FF00);
+ getBackground().setTint(sColors[0]);
setOutlineProvider(new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
@@ -822,8 +860,9 @@ public class LLand extends FrameLayout {
int cx, cy, r;
public Pop(Context context, float h) {
super(context, h);
- int idx = 2*irand(0, POPS.length/2);
+ int idx = 3*irand(0, POPS.length/3);
setBackgroundResource(POPS[idx]);
+ setAlpha((float)(POPS[idx+2])/255);
setScaleX(frand() < 0.5f ? -1 : 1);
mRotate = POPS[idx+1] == 0 ? 0 : (frand() < 0.5f ? -1 : 1);
setOutlineProvider(new ViewOutlineProvider() {
diff --git a/packages/SystemUI/src/com/android/systemui/egg/LLandActivity.java b/packages/SystemUI/src/com/android/systemui/egg/LLandActivity.java
index 88fd952..b9f8106 100644
--- a/packages/SystemUI/src/com/android/systemui/egg/LLandActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/egg/LLandActivity.java
@@ -24,13 +24,21 @@ import android.widget.TextView;
import com.android.systemui.R;
public class LLandActivity extends Activity {
+ LLand mLand;
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.lland);
- LLand world = (LLand) findViewById(R.id.world);
- world.setScoreField((TextView) findViewById(R.id.score));
- world.setSplash(findViewById(R.id.welcome));
- Log.v(LLand.TAG, "focus: " + world.requestFocus());
+ mLand = (LLand) findViewById(R.id.world);
+ mLand.setScoreField((TextView) findViewById(R.id.score));
+ mLand.setSplash(findViewById(R.id.welcome));
+ //Log.v(LLand.TAG, "focus: " + mLand.requestFocus());
+ }
+
+ @Override
+ public void onPause() {
+ mLand.stop();
+ super.onPause();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index ee699d2..73fa2ed 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -22,13 +22,13 @@ import android.os.Binder;
import android.os.Bundle;
import android.os.Debug;
import android.os.IBinder;
+import android.os.Process;
import android.util.Log;
-import android.view.MotionEvent;
import com.android.internal.policy.IKeyguardExitCallback;
import com.android.internal.policy.IKeyguardService;
-import com.android.internal.policy.IKeyguardServiceConstants;
import com.android.internal.policy.IKeyguardShowCallback;
+import com.android.internal.policy.IKeyguardStateCallback;
import com.android.systemui.SystemUIApplication;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
@@ -52,6 +52,10 @@ public class KeyguardService extends Service {
}
void checkPermission() {
+ // Avoid deadlock by avoiding calling back into the system process.
+ if (Binder.getCallingUid() == Process.SYSTEM_UID) return;
+
+ // Otherwise,explicitly check for caller permission ...
if (getBaseContext().checkCallingOrSelfPermission(PERMISSION) != PERMISSION_GRANTED) {
Log.w(TAG, "Caller needs permission '" + PERMISSION + "' to call " + Debug.getCaller());
throw new SecurityException("Access denied to process: " + Binder.getCallingPid()
@@ -61,143 +65,85 @@ public class KeyguardService extends Service {
private final IKeyguardService.Stub mBinder = new IKeyguardService.Stub() {
- private boolean mIsOccluded;
-
- @Override
- public boolean isShowing() {
- return mKeyguardViewMediator.isShowing();
- }
-
- @Override
- public boolean isSecure() {
- return mKeyguardViewMediator.isSecure();
- }
-
- @Override
- public boolean isShowingAndNotOccluded() {
- return mKeyguardViewMediator.isShowingAndNotOccluded();
- }
-
- @Override
- public boolean isInputRestricted() {
- return mKeyguardViewMediator.isInputRestricted();
+ @Override // Binder interface
+ public void addStateMonitorCallback(IKeyguardStateCallback callback) {
+ checkPermission();
+ mKeyguardViewMediator.addStateMonitorCallback(callback);
}
- @Override
+ @Override // Binder interface
public void verifyUnlock(IKeyguardExitCallback callback) {
checkPermission();
mKeyguardViewMediator.verifyUnlock(callback);
}
- @Override
+ @Override // Binder interface
public void keyguardDone(boolean authenticated, boolean wakeup) {
checkPermission();
mKeyguardViewMediator.keyguardDone(authenticated, wakeup);
}
- @Override
- public int setOccluded(boolean isOccluded) {
- checkPermission();
- synchronized (this) {
- int result;
- if (isOccluded && mKeyguardViewMediator.isShowing()
- && !mIsOccluded) {
- result = IKeyguardServiceConstants
- .KEYGUARD_SERVICE_SET_OCCLUDED_RESULT_UNSET_FLAGS;
- } else if (!isOccluded && mKeyguardViewMediator.isShowing()
- && mIsOccluded) {
- result = IKeyguardServiceConstants
- .KEYGUARD_SERVICE_SET_OCCLUDED_RESULT_SET_FLAGS;
- } else {
- result = IKeyguardServiceConstants.KEYGUARD_SERVICE_SET_OCCLUDED_RESULT_NONE;
- }
- if (mIsOccluded != isOccluded) {
- mKeyguardViewMediator.setOccluded(isOccluded);
-
- // Cache the value so we always have a fresh view in whether Keyguard is occluded.
- // If we would just call mKeyguardViewMediator.isOccluded(), this might be stale
- // because that value gets updated in another thread.
- mIsOccluded = isOccluded;
- }
- return result;
- }
+ @Override // Binder interface
+ public void setOccluded(boolean isOccluded) {
+ checkPermission();
+ mKeyguardViewMediator.setOccluded(isOccluded);
}
- @Override
+ @Override // Binder interface
public void dismiss() {
checkPermission();
mKeyguardViewMediator.dismiss();
}
- @Override
+ @Override // Binder interface
public void onDreamingStarted() {
checkPermission();
mKeyguardViewMediator.onDreamingStarted();
}
- @Override
+ @Override // Binder interface
public void onDreamingStopped() {
checkPermission();
mKeyguardViewMediator.onDreamingStopped();
}
- @Override
+ @Override // Binder interface
public void onScreenTurnedOff(int reason) {
checkPermission();
mKeyguardViewMediator.onScreenTurnedOff(reason);
}
- @Override
+ @Override // Binder interface
public void onScreenTurnedOn(IKeyguardShowCallback callback) {
checkPermission();
mKeyguardViewMediator.onScreenTurnedOn(callback);
}
- @Override
+ @Override // Binder interface
public void setKeyguardEnabled(boolean enabled) {
checkPermission();
mKeyguardViewMediator.setKeyguardEnabled(enabled);
}
- @Override
- public boolean isDismissable() {
- return mKeyguardViewMediator.isDismissable();
- }
-
- @Override
+ @Override // Binder interface
public void onSystemReady() {
checkPermission();
mKeyguardViewMediator.onSystemReady();
}
- @Override
+ @Override // Binder interface
public void doKeyguardTimeout(Bundle options) {
checkPermission();
mKeyguardViewMediator.doKeyguardTimeout(options);
}
- @Override
+ @Override // Binder interface
public void setCurrentUser(int userId) {
checkPermission();
mKeyguardViewMediator.setCurrentUser(userId);
}
@Override
- public void showAssistant() {
- checkPermission();
- }
-
- @Override
- public void dispatch(MotionEvent event) {
- checkPermission();
- }
-
- @Override
- public void launchCamera() {
- checkPermission();
- }
-
- @Override
public void onBootCompleted() {
checkPermission();
mKeyguardViewMediator.onBootCompleted();
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 4af8499..e66934e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -23,6 +23,7 @@ import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.SearchManager;
import android.app.StatusBarManager;
+import android.app.trust.TrustManager;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
@@ -42,6 +43,7 @@ import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
+import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.util.EventLog;
import android.util.Log;
@@ -54,8 +56,10 @@ import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import com.android.internal.policy.IKeyguardExitCallback;
import com.android.internal.policy.IKeyguardShowCallback;
+import com.android.internal.policy.IKeyguardStateCallback;
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.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
@@ -67,6 +71,9 @@ import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.StatusBarWindowManager;
+import java.util.ArrayList;
+import java.util.List;
+
import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT;
@@ -113,7 +120,10 @@ import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT;
*/
public class KeyguardViewMediator extends SystemUI {
private static final int KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT = 30000;
- final static boolean DEBUG = false;
+ private static final long KEYGUARD_DONE_PENDING_TIMEOUT_MS = 3000;
+
+ private static final boolean DEBUG = KeyguardConstants.DEBUG;
+ private static final boolean DEBUG_SIM_STATES = KeyguardConstants.DEBUG_SIM_STATES;
private final static boolean DBG_WAKE = false;
private final static String TAG = "KeyguardViewMediator";
@@ -136,6 +146,7 @@ public class KeyguardViewMediator extends SystemUI {
private static final int DISMISS = 17;
private static final int START_KEYGUARD_EXIT_ANIM = 18;
private static final int ON_ACTIVITY_DRAWN = 19;
+ private static final int KEYGUARD_DONE_PENDING_TIMEOUT = 20;
/**
* The default amount of time we stay awake (used for all key input)
@@ -184,8 +195,9 @@ public class KeyguardViewMediator extends SystemUI {
/** High level access to the window manager for dismissing keyguard animation */
private IWindowManager mWM;
- /** UserManager for querying number of users */
- private UserManager mUserManager;
+
+ /** TrustManager for letting it know when we change visibility */
+ private TrustManager mTrustManager;
/** SearchManager for determining whether or not search assistant is available */
private SearchManager mSearchManager;
@@ -216,6 +228,9 @@ public class KeyguardViewMediator extends SystemUI {
// answer whether the input should be restricted)
private boolean mShowing;
+ /** Cached value of #isInputRestricted */
+ private boolean mInputRestricted;
+
// true if the keyguard is hidden by another window
private boolean mOccluded = false;
@@ -285,6 +300,8 @@ public class KeyguardViewMediator extends SystemUI {
*/
private KeyguardDisplayManager mKeyguardDisplayManager;
+ private final ArrayList<IKeyguardStateCallback> mKeyguardStateCallbacks = new ArrayList<>();
+
KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() {
@Override
@@ -294,7 +311,7 @@ public class KeyguardViewMediator extends SystemUI {
// ActivityManagerService) will not reconstruct the keyguard if it is already showing.
synchronized (KeyguardViewMediator.this) {
mSwitchingUser = true;
- mKeyguardDonePending = false;
+ resetKeyguardDonePendingLocked();
resetStateLocked();
adjustStatusBarLocked();
// When we switch users we want to bring the new user to the biometric unlock even
@@ -352,11 +369,26 @@ public class KeyguardViewMediator extends SystemUI {
@Override
public void onDeviceProvisioned() {
sendUserPresentBroadcast();
+ updateInputRestricted();
}
@Override
- public void onSimStateChanged(IccCardConstants.State simState) {
- if (DEBUG) Log.d(TAG, "onSimStateChanged: " + simState);
+ public void onSimStateChanged(int subId, int slotId, IccCardConstants.State simState) {
+
+ if (DEBUG_SIM_STATES) {
+ Log.d(TAG, "onSimStateChanged(subId=" + subId + ", slotId=" + slotId
+ + ",state=" + simState + ")");
+ }
+
+ try {
+ int size = mKeyguardStateCallbacks.size();
+ boolean simPinSecure = mUpdateMonitor.isSimPinSecure();
+ for (int i = 0; i < size; i++) {
+ mKeyguardStateCallbacks.get(i).onSimSecureStateChanged(simPinSecure);
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to call onSimSecureStateChanged", e);
+ }
switch (simState) {
case NOT_READY:
@@ -364,9 +396,9 @@ public class KeyguardViewMediator extends SystemUI {
// only force lock screen in case of missing sim if user hasn't
// gone through setup wizard
synchronized (this) {
- if (!mUpdateMonitor.isDeviceProvisioned()) {
- if (!isShowing()) {
- if (DEBUG) Log.d(TAG, "ICC_ABSENT isn't showing,"
+ if (shouldWaitForProvisioning()) {
+ if (!mShowing) {
+ if (DEBUG_SIM_STATES) Log.d(TAG, "ICC_ABSENT isn't showing,"
+ " we need to show the keyguard since the "
+ "device isn't provisioned yet.");
doKeyguardLocked(null);
@@ -379,8 +411,9 @@ public class KeyguardViewMediator extends SystemUI {
case PIN_REQUIRED:
case PUK_REQUIRED:
synchronized (this) {
- if (!isShowing()) {
- if (DEBUG) Log.d(TAG, "INTENT_VALUE_ICC_LOCKED and keygaurd isn't "
+ if (!mShowing) {
+ if (DEBUG_SIM_STATES) Log.d(TAG,
+ "INTENT_VALUE_ICC_LOCKED and keygaurd isn't "
+ "showing; need to show keyguard so user can enter sim pin");
doKeyguardLocked(null);
} else {
@@ -390,12 +423,12 @@ public class KeyguardViewMediator extends SystemUI {
break;
case PERM_DISABLED:
synchronized (this) {
- if (!isShowing()) {
- if (DEBUG) Log.d(TAG, "PERM_DISABLED and "
+ if (!mShowing) {
+ if (DEBUG_SIM_STATES) Log.d(TAG, "PERM_DISABLED and "
+ "keygaurd isn't showing.");
doKeyguardLocked(null);
} else {
- if (DEBUG) Log.d(TAG, "PERM_DISABLED, resetStateLocked to"
+ if (DEBUG_SIM_STATES) Log.d(TAG, "PERM_DISABLED, resetStateLocked to"
+ "show permanently disabled message in lockscreen.");
resetStateLocked();
}
@@ -403,11 +436,14 @@ public class KeyguardViewMediator extends SystemUI {
break;
case READY:
synchronized (this) {
- if (isShowing()) {
+ if (mShowing) {
resetStateLocked();
}
}
break;
+ default:
+ if (DEBUG_SIM_STATES) Log.v(TAG, "Ignoring state: " + simState);
+ break;
}
}
@@ -426,7 +462,9 @@ public class KeyguardViewMediator extends SystemUI {
}
public void keyguardDone(boolean authenticated) {
- KeyguardViewMediator.this.keyguardDone(authenticated, true);
+ if (!mKeyguardDonePending) {
+ KeyguardViewMediator.this.keyguardDone(authenticated, true);
+ }
}
public void keyguardDoneDrawing() {
@@ -448,6 +486,8 @@ public class KeyguardViewMediator extends SystemUI {
mKeyguardDonePending = true;
mHideAnimationRun = true;
mStatusBarKeyguardViewManager.startPreHideAnimation(null /* finishRunnable */);
+ mHandler.sendEmptyMessageDelayed(KEYGUARD_DONE_PENDING_TIMEOUT,
+ KEYGUARD_DONE_PENDING_TIMEOUT_MS);
}
@Override
@@ -468,16 +508,22 @@ public class KeyguardViewMediator extends SystemUI {
public void playTrustedSound() {
KeyguardViewMediator.this.playTrustedSound();
}
+
+ @Override
+ public boolean isInputRestricted() {
+ return KeyguardViewMediator.this.isInputRestricted();
+ }
};
public void userActivity() {
mPM.userActivity(SystemClock.uptimeMillis(), false);
}
- private void setup() {
+ private void setupLocked() {
mPM = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mWM = WindowManagerGlobal.getWindowManagerService();
- mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ mTrustManager = (TrustManager) mContext.getSystemService(Context.TRUST_SERVICE);
+
mShowKeyguardWakeLock = mPM.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "show keyguard");
mShowKeyguardWakeLock.setReferenceCounted(false);
@@ -493,8 +539,8 @@ public class KeyguardViewMediator extends SystemUI {
mLockPatternUtils.setCurrentUser(ActivityManager.getCurrentUser());
// Assume keyguard is showing (unless it's disabled) until we know for sure...
- mShowing = (mUpdateMonitor.isDeviceProvisioned() || mLockPatternUtils.isSecure())
- && !mLockPatternUtils.isLockScreenDisabled();
+ setShowingLocked(!shouldWaitForProvisioning() && !mLockPatternUtils.isLockScreenDisabled());
+ mTrustManager.reportKeyguardShowingChanged();
mStatusBarKeyguardViewManager = new StatusBarKeyguardViewManager(mContext,
mViewMediatorCallback, mLockPatternUtils);
@@ -535,7 +581,9 @@ public class KeyguardViewMediator extends SystemUI {
@Override
public void start() {
- setup();
+ synchronized (this) {
+ setupLocked();
+ }
putComponent(KeyguardViewMediator.class, this);
}
@@ -583,7 +631,7 @@ public class KeyguardViewMediator extends SystemUI {
mScreenOn = false;
if (DEBUG) Log.d(TAG, "onScreenTurnedOff(" + why + ")");
- mKeyguardDonePending = false;
+ resetKeyguardDonePendingLocked();
mHideAnimationRun = false;
// Lock immediately based on setting if secure (user has a pin/pattern/password).
@@ -739,12 +787,14 @@ public class KeyguardViewMediator extends SystemUI {
if (DEBUG) Log.d(TAG, "remembering to reshow, hiding keyguard, "
+ "disabling status bar expansion");
mNeedToReshowWhenReenabled = true;
+ updateInputRestrictedLocked();
hideLocked();
} else if (enabled && mNeedToReshowWhenReenabled) {
// reenabled after previously hidden, reshow
if (DEBUG) Log.d(TAG, "previously hidden, reshowing, reenabling "
+ "status bar expansion");
mNeedToReshowWhenReenabled = false;
+ updateInputRestrictedLocked();
if (mExitSecureCallback != null) {
if (DEBUG) Log.d(TAG, "onKeyguardExitResult(false), resetting");
@@ -783,7 +833,7 @@ public class KeyguardViewMediator extends SystemUI {
public void verifyUnlock(IKeyguardExitCallback callback) {
synchronized (this) {
if (DEBUG) Log.d(TAG, "verifyUnlock");
- if (!mUpdateMonitor.isDeviceProvisioned()) {
+ if (shouldWaitForProvisioning()) {
// don't allow this api when the device isn't provisioned
if (DEBUG) Log.d(TAG, "ignoring because device isn't provisioned");
try {
@@ -816,17 +866,6 @@ public class KeyguardViewMediator extends SystemUI {
}
/**
- * Is the keyguard currently showing?
- */
- public boolean isShowing() {
- return mShowing;
- }
-
- public boolean isOccluded() {
- return mOccluded;
- }
-
- /**
* Is the keyguard currently showing and not being force hidden?
*/
public boolean isShowingAndNotOccluded() {
@@ -873,7 +912,27 @@ 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 || !mUpdateMonitor.isDeviceProvisioned();
+ return mShowing || mNeedToReshowWhenReenabled || shouldWaitForProvisioning();
+ }
+
+ private void updateInputRestricted() {
+ synchronized (this) {
+ updateInputRestrictedLocked();
+ }
+ }
+ private void updateInputRestrictedLocked() {
+ boolean inputRestricted = isInputRestricted();
+ if (mInputRestricted != inputRestricted) {
+ mInputRestricted = inputRestricted;
+ try {
+ int size = mKeyguardStateCallbacks.size();
+ for (int i = 0; i < size; i++) {
+ mKeyguardStateCallbacks.get(i).onInputRestrictedStateChanged(inputRestricted);
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to call onDeviceProvisioned", e);
+ }
+ }
}
/**
@@ -899,20 +958,20 @@ public class KeyguardViewMediator extends SystemUI {
// if the keyguard is already showing, don't bother
if (mStatusBarKeyguardViewManager.isShowing()) {
if (DEBUG) Log.d(TAG, "doKeyguard: not showing because it is already showing");
+ resetStateLocked();
return;
}
// if the setup wizard hasn't run yet, don't show
- final boolean requireSim = !SystemProperties.getBoolean("keyguard.no_require_sim",
- false);
- final boolean provisioned = mUpdateMonitor.isDeviceProvisioned();
- final IccCardConstants.State state = mUpdateMonitor.getSimState();
- final boolean lockedOrMissing = state.isPinLocked()
- || ((state == IccCardConstants.State.ABSENT
- || state == IccCardConstants.State.PERM_DISABLED)
- && requireSim);
-
- if (!lockedOrMissing && !provisioned) {
+ final boolean requireSim = !SystemProperties.getBoolean("keyguard.no_require_sim", false);
+ final boolean absent = SubscriptionManager.isValidSubscriptionId(
+ mUpdateMonitor.getNextSubIdForState(IccCardConstants.State.ABSENT));
+ final boolean disabled = SubscriptionManager.isValidSubscriptionId(
+ mUpdateMonitor.getNextSubIdForState(IccCardConstants.State.PERM_DISABLED));
+ final boolean lockedOrMissing = mUpdateMonitor.isSimPinSecure()
+ || ((absent || disabled) && requireSim);
+
+ if (!lockedOrMissing && shouldWaitForProvisioning()) {
if (DEBUG) Log.d(TAG, "doKeyguard: not showing because device isn't provisioned"
+ " and the sim is not locked or missing");
return;
@@ -926,7 +985,7 @@ public class KeyguardViewMediator extends SystemUI {
if (mLockPatternUtils.checkVoldPassword()) {
if (DEBUG) Log.d(TAG, "Not showing lock screen since just decrypted");
// Without this, settings is not enabled until the lock screen first appears
- mShowing = false;
+ setShowingLocked(false);
hideLocked();
return;
}
@@ -935,6 +994,10 @@ public class KeyguardViewMediator extends SystemUI {
showLocked(options);
}
+ private boolean shouldWaitForProvisioning() {
+ return !mUpdateMonitor.isDeviceProvisioned() && !isSecure();
+ }
+
/**
* Dismiss the keyguard through the security layers.
*/
@@ -1047,9 +1110,6 @@ public class KeyguardViewMediator extends SystemUI {
public void keyguardDone(boolean authenticated, boolean wakeup) {
if (DEBUG) Log.d(TAG, "keyguardDone(" + authenticated + ")");
EventLog.writeEvent(70000, 2);
- synchronized (this) {
- mKeyguardDonePending = false;
- }
Message msg = mHandler.obtainMessage(KEYGUARD_DONE, authenticated ? 1 : 0, wakeup ? 1 : 0);
mHandler.sendMessage(msg);
}
@@ -1107,6 +1167,9 @@ public class KeyguardViewMediator extends SystemUI {
StartKeyguardExitAnimParams params = (StartKeyguardExitAnimParams) msg.obj;
handleStartKeyguardExitAnimation(params.startTime, params.fadeoutDuration);
break;
+ case KEYGUARD_DONE_PENDING_TIMEOUT:
+ Log.w(TAG, "Timeout while waiting for activity drawn!");
+ // Fall through.
case ON_ACTIVITY_DRAWN:
handleOnActivityDrawn();
break;
@@ -1120,6 +1183,9 @@ public class KeyguardViewMediator extends SystemUI {
*/
private void handleKeyguardDone(boolean authenticated, boolean wakeup) {
if (DEBUG) Log.d(TAG, "handleKeyguardDone");
+ synchronized (this) {
+ resetKeyguardDonePendingLocked();
+ }
if (authenticated) {
mUpdateMonitor.clearFailedUnlockAttempts();
@@ -1140,6 +1206,7 @@ public class KeyguardViewMediator extends SystemUI {
// the keyguard when they've released the lock
mExternallyEnabled = true;
mNeedToReshowWhenReenabled = false;
+ updateInputRestricted();
}
}
@@ -1150,7 +1217,12 @@ public class KeyguardViewMediator extends SystemUI {
synchronized (this) {
if (mBootCompleted) {
final UserHandle currentUser = new UserHandle(mLockPatternUtils.getCurrentUser());
- mContext.sendBroadcastAsUser(USER_PRESENT_INTENT, currentUser);
+ final UserManager um = (UserManager) mContext.getSystemService(
+ Context.USER_SERVICE);
+ List <UserInfo> userHandles = um.getProfiles(currentUser.getIdentifier());
+ for (UserInfo ui : userHandles) {
+ mContext.sendBroadcastAsUser(USER_PRESENT_INTENT, ui.getUserHandle());
+ }
} else {
mBootSendUserPresent = true;
}
@@ -1235,10 +1307,10 @@ public class KeyguardViewMediator extends SystemUI {
if (DEBUG) Log.d(TAG, "handleShow");
}
+ setShowingLocked(true);
mStatusBarKeyguardViewManager.show(options);
mHiding = false;
- mShowing = true;
- mKeyguardDonePending = false;
+ resetKeyguardDonePendingLocked();
mHideAnimationRun = false;
updateActivityLockScreenState();
adjustStatusBarLocked();
@@ -1295,6 +1367,7 @@ public class KeyguardViewMediator extends SystemUI {
}
private void handleOnActivityDrawn() {
+ if (DEBUG) Log.d(TAG, "handleOnActivityDrawn: mKeyguardDonePending=" + mKeyguardDonePending);
if (mKeyguardDonePending) {
mStatusBarKeyguardViewManager.onActivityDrawn();
}
@@ -1314,9 +1387,9 @@ public class KeyguardViewMediator extends SystemUI {
playSounds(false);
}
+ setShowingLocked(false);
mStatusBarKeyguardViewManager.hide(startTime, fadeoutDuration);
- mShowing = false;
- mKeyguardDonePending = false;
+ resetKeyguardDonePendingLocked();
mHideAnimationRun = false;
updateActivityLockScreenState();
adjustStatusBarLocked();
@@ -1375,8 +1448,8 @@ public class KeyguardViewMediator extends SystemUI {
private void handleVerifyUnlock() {
synchronized (KeyguardViewMediator.this) {
if (DEBUG) Log.d(TAG, "handleVerifyUnlock");
+ setShowingLocked(true);
mStatusBarKeyguardViewManager.verifyUnlock();
- mShowing = true;
updateActivityLockScreenState();
}
}
@@ -1403,13 +1476,9 @@ public class KeyguardViewMediator extends SystemUI {
}
}
- public boolean isDismissable() {
- return mKeyguardDonePending || !isSecure();
- }
-
- private boolean isAssistantAvailable() {
- return mSearchManager != null
- && mSearchManager.getAssistIntent(mContext, false, UserHandle.USER_CURRENT) != null;
+ private void resetKeyguardDonePendingLocked() {
+ mKeyguardDonePending = false;
+ mHandler.removeMessages(KEYGUARD_DONE_PENDING_TIMEOUT);
}
public void onBootCompleted() {
@@ -1453,4 +1522,32 @@ public class KeyguardViewMediator extends SystemUI {
this.fadeoutDuration = fadeoutDuration;
}
}
+
+ private void setShowingLocked(boolean showing) {
+ if (showing != mShowing) {
+ mShowing = showing;
+ try {
+ int size = mKeyguardStateCallbacks.size();
+ for (int i = 0; i < size; i++) {
+ mKeyguardStateCallbacks.get(i).onShowingStateChanged(showing);
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to call onShowingStateChanged", e);
+ }
+ updateInputRestrictedLocked();
+ mTrustManager.reportKeyguardShowingChanged();
+ }
+ }
+
+ public void addStateMonitorCallback(IKeyguardStateCallback callback) {
+ synchronized (this) {
+ mKeyguardStateCallbacks.add(callback);
+ try {
+ callback.onSimSecureStateChanged(mUpdateMonitor.isSimPinSecure());
+ callback.onShowingStateChanged(mShowing);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to call onShowingStateChanged or onSimSecureStateChanged", e);
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
index b441eaa..c23f45d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
@@ -16,6 +16,7 @@
package com.android.systemui.media;
+import android.app.Activity;
import android.app.AlertDialog;
import android.app.PendingIntent;
import android.content.Context;
@@ -32,6 +33,7 @@ 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;
@@ -39,9 +41,11 @@ 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 AlertActivity
- implements DialogInterface.OnClickListener, CheckBox.OnCheckedChangeListener {
+public class MediaProjectionPermissionActivity extends Activity
+ implements DialogInterface.OnClickListener, CheckBox.OnCheckedChangeListener,
+ DialogInterface.OnCancelListener {
private static final String TAG = "MediaProjectionPermissionActivity";
private boolean mPermanentGrant;
@@ -49,11 +53,12 @@ public class MediaProjectionPermissionActivity extends AlertActivity
private int mUid;
private IMediaProjectionManager mService;
+ private AlertDialog mDialog;
+
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
- Intent intent = getIntent();
mPackageName = getCallingPackage();
IBinder b = ServiceManager.getService(MEDIA_PROJECTION_SERVICE);
mService = IMediaProjectionManager.Stub.asInterface(b);
@@ -89,22 +94,29 @@ public class MediaProjectionPermissionActivity extends AlertActivity
String appName = aInfo.loadLabel(packageManager).toString();
- final AlertController.AlertParams ap = mAlertParams;
- ap.mIcon = aInfo.loadIcon(packageManager);
- ap.mMessage = getString(R.string.media_projection_dialog_text, appName);
- ap.mPositiveButtonText = getString(R.string.media_projection_action_text);
- ap.mNegativeButtonText = getString(android.R.string.cancel);
- ap.mPositiveButtonListener = this;
- ap.mNegativeButtonListener = this;
-
- // add "always use" checkbox
- LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- ap.mView = inflater.inflate(R.layout.remember_permission_checkbox, null);
- CheckBox rememberPermissionCheckbox =
- (CheckBox)ap.mView.findViewById(R.id.remember);
- rememberPermissionCheckbox.setOnCheckedChangeListener(this);
-
- setupAlert();
+ mDialog = new AlertDialog.Builder(this)
+ .setIcon(aInfo.loadIcon(packageManager))
+ .setMessage(getString(R.string.media_projection_dialog_text, appName))
+ .setPositiveButton(R.string.media_projection_action_text, this)
+ .setNegativeButton(android.R.string.cancel, this)
+ .setView(R.layout.remember_permission_checkbox)
+ .setOnCancelListener(this)
+ .create();
+
+ mDialog.create();
+
+ ((CheckBox) mDialog.findViewById(R.id.remember)).setOnCheckedChangeListener(this);
+ mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+
+ mDialog.show();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (mDialog != null) {
+ mDialog.dismiss();
+ }
}
@Override
@@ -118,6 +130,9 @@ public class MediaProjectionPermissionActivity extends AlertActivity
Log.e(TAG, "Error granting projection permission", e);
setResult(RESULT_CANCELED);
} finally {
+ if (mDialog != null) {
+ mDialog.dismiss();
+ }
finish();
}
}
@@ -135,4 +150,9 @@ public class MediaProjectionPermissionActivity extends AlertActivity
intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION, projection.asBinder());
return intent;
}
+
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ finish();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index f184ad2..4391bfc 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -43,6 +43,7 @@ import com.android.systemui.statusbar.phone.PhoneStatusBar;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import java.io.PrintWriter;
+import java.text.NumberFormat;
public class PowerNotificationWarnings implements PowerUI.WarningsUI {
private static final String TAG = PowerUI.TAG + ".Notification";
@@ -65,6 +66,7 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
private static final String ACTION_SHOW_BATTERY_SETTINGS = "PNW.batterySettings";
private static final String ACTION_START_SAVER = "PNW.startSaver";
private static final String ACTION_STOP_SAVER = "PNW.stopSaver";
+ private static final String ACTION_DISMISSED_WARNING = "PNW.dismissedWarning";
private static final AudioAttributes AUDIO_ATTRIBUTES = new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
@@ -143,7 +145,7 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
showSaverNotification();
mShowing = SHOWING_SAVER;
} else {
- mNoMan.cancel(TAG_NOTIFICATION, ID_NOTIFICATION);
+ mNoMan.cancelAsUser(TAG_NOTIFICATION, ID_NOTIFICATION, UserHandle.ALL);
mShowing = SHOWING_NOTHING;
}
}
@@ -157,7 +159,6 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
.setContentTitle(mContext.getString(R.string.invalid_charger_title))
.setContentText(mContext.getString(R.string.invalid_charger_text))
.setPriority(Notification.PRIORITY_MAX)
- .setCategory(Notification.CATEGORY_SYSTEM)
.setVisibility(Notification.VISIBILITY_PUBLIC)
.setColor(mContext.getResources().getColor(
com.android.internal.R.color.system_notification_accent_color));
@@ -165,22 +166,23 @@ 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.CURRENT);
+ mNoMan.notifyAsUser(TAG_NOTIFICATION, ID_NOTIFICATION, n, UserHandle.ALL);
}
private void showWarningNotification() {
final int textRes = mSaver ? R.string.battery_low_percent_format_saver_started
: R.string.battery_low_percent_format;
+ final String percentage = NumberFormat.getPercentInstance().format((double) mBatteryLevel / 100.0);
final Notification.Builder nb = new Notification.Builder(mContext)
.setSmallIcon(R.drawable.ic_power_low)
// Bump the notification when the bucket dropped.
.setWhen(mBucketDroppedNegativeTimeMs)
.setShowWhen(false)
.setContentTitle(mContext.getString(R.string.battery_low_title))
- .setContentText(mContext.getString(textRes, mBatteryLevel))
+ .setContentText(mContext.getString(textRes, percentage))
.setOnlyAlertOnce(true)
+ .setDeleteIntent(pendingBroadcast(ACTION_DISMISSED_WARNING))
.setPriority(Notification.PRIORITY_MAX)
- .setCategory(Notification.CATEGORY_SYSTEM)
.setVisibility(Notification.VISIBILITY_PUBLIC)
.setColor(mContext.getResources().getColor(
com.android.internal.R.color.battery_saver_mode_color));
@@ -202,7 +204,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.CURRENT);
+ mNoMan.notifyAsUser(TAG_NOTIFICATION, ID_NOTIFICATION, n, UserHandle.ALL);
}
private void showSaverNotification() {
@@ -212,7 +214,6 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
.setContentText(mContext.getString(R.string.battery_saver_notification_text))
.setOngoing(true)
.setShowWhen(false)
- .setCategory(Notification.CATEGORY_SYSTEM)
.setVisibility(Notification.VISIBILITY_PUBLIC)
.setColor(mContext.getResources().getColor(
com.android.internal.R.color.battery_saver_mode_color));
@@ -220,7 +221,7 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
if (hasSaverSettings()) {
nb.setContentIntent(pendingActivity(mOpenSaverSettings));
}
- mNoMan.notifyAsUser(TAG_NOTIFICATION, ID_NOTIFICATION, nb.build(), UserHandle.CURRENT);
+ mNoMan.notifyAsUser(TAG_NOTIFICATION, ID_NOTIFICATION, nb.build(), UserHandle.ALL);
}
private void addStopSaverAction(Notification.Builder nb) {
@@ -341,6 +342,11 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
updateNotification();
}
+ @Override
+ public void userSwitched() {
+ updateNotification();
+ }
+
private void showStartSaverConfirmation() {
if (mSaverConfirmation != null) return;
final SystemUIDialog d = new SystemUIDialog(mContext);
@@ -370,7 +376,8 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
filter.addAction(ACTION_SHOW_BATTERY_SETTINGS);
filter.addAction(ACTION_START_SAVER);
filter.addAction(ACTION_STOP_SAVER);
- mContext.registerReceiver(this, filter, null, mHandler);
+ filter.addAction(ACTION_DISMISSED_WARNING);
+ mContext.registerReceiverAsUser(this, UserHandle.ALL, filter, null, mHandler);
}
@Override
@@ -387,6 +394,8 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
dismissSaverNotification();
dismissLowBatteryNotification();
setSaverMode(false);
+ } else if (action.equals(ACTION_DISMISSED_WARNING)) {
+ dismissLowBatteryWarning();
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index d3c7dee..9459740 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -137,6 +137,7 @@ public class PowerUI extends SystemUI {
filter.addAction(Intent.ACTION_BATTERY_CHANGED);
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_SCREEN_ON);
+ filter.addAction(Intent.ACTION_USER_SWITCHED);
filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGING);
filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
mContext.registerReceiver(this, filter, null, mHandler);
@@ -207,6 +208,8 @@ public class PowerUI extends SystemUI {
mScreenOffTime = SystemClock.elapsedRealtime();
} else if (Intent.ACTION_SCREEN_ON.equals(action)) {
mScreenOffTime = -1;
+ } else if (Intent.ACTION_USER_SWITCHED.equals(action)) {
+ mWarnings.userSwitched();
} else if (PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals(action)) {
updateSaverMode();
} else if (PowerManager.ACTION_POWER_SAVE_MODE_CHANGING.equals(action)) {
@@ -256,6 +259,7 @@ public class PowerUI extends SystemUI {
void updateLowBatteryWarning();
boolean isInvalidChargerWarningShowing();
void dump(PrintWriter pw);
+ void userSwitched();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainer.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainer.java
new file mode 100644
index 0000000..cfe8d07
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainer.java
@@ -0,0 +1,76 @@
+/*
+ * 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.qs;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+import com.android.systemui.R;
+
+/**
+ * Wrapper view with background which contains {@link QSPanel}
+ */
+public class QSContainer extends FrameLayout {
+
+ private int mHeightOverride = -1;
+ private QSPanel mQSPanel;
+
+ public QSContainer(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mQSPanel = (QSPanel) findViewById(R.id.quick_settings_panel);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ updateBottom();
+ }
+
+ /**
+ * Overrides the height of this view (post-layout), so that the content is clipped to that
+ * height and the background is set to that height.
+ *
+ * @param heightOverride the overridden height
+ */
+ public void setHeightOverride(int heightOverride) {
+ mHeightOverride = heightOverride;
+ updateBottom();
+ }
+
+ /**
+ * The height this view wants to be. This is different from {@link #getMeasuredHeight} such that
+ * during closing the detail panel, this already returns the smaller height.
+ */
+ public int getDesiredHeight() {
+ if (mQSPanel.isClosingDetail()) {
+ return mQSPanel.getGridHeight() + getPaddingTop() + getPaddingBottom();
+ } else {
+ return getMeasuredHeight();
+ }
+ }
+
+ private void updateBottom() {
+ int height = mHeightOverride != -1 ? mHeightOverride : getMeasuredHeight();
+ setBottom(getTop() + height);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java
index eb4560d..111484b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java
@@ -55,7 +55,6 @@ public class QSDetailClipper {
if (listener != null) {
mAnimator.addListener(listener);
}
- mDetail.setLayerType(View.LAYER_TYPE_HARDWARE, null);
if (in) {
mBackground.startTransition((int)(mAnimator.getDuration() * 0.6));
mAnimator.addListener(mVisibleOnStart);
@@ -82,7 +81,6 @@ public class QSDetailClipper {
}
public void onAnimationEnd(Animator animation) {
- mDetail.setLayerType(View.LAYER_TYPE_NONE, null);
mAnimator = null;
}
};
@@ -90,7 +88,6 @@ public class QSDetailClipper {
private final AnimatorListenerAdapter mGoneOnEnd = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- mDetail.setLayerType(View.LAYER_TYPE_NONE, null);
mDetail.setVisibility(View.GONE);
mBackground.resetTransition();
mAnimator = null;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
index ce0d5f4..95ac558 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
@@ -18,13 +18,13 @@ package com.android.systemui.qs;
import android.content.Context;
import android.content.res.Configuration;
+import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
-import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -51,8 +51,10 @@ public class QSDetailItems extends FrameLayout {
private boolean mItemsVisible = true;
private LinearLayout mItems;
private View mEmpty;
+ private View mMinHeightSpacer;
private TextView mEmptyText;
private ImageView mEmptyIcon;
+ private int mMaxItems;
public QSDetailItems(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -77,6 +79,12 @@ public class QSDetailItems extends FrameLayout {
mEmpty.setVisibility(GONE);
mEmptyText = (TextView) mEmpty.findViewById(android.R.id.title);
mEmptyIcon = (ImageView) mEmpty.findViewById(android.R.id.icon);
+ mMinHeightSpacer = findViewById(R.id.min_height_spacer);
+
+ // By default, a detail item view has fixed size.
+ mMaxItems = getResources().getInteger(
+ R.integer.quick_settings_detail_max_item_count);
+ setMinHeightInItems(mMaxItems);
}
@Override
@@ -102,6 +110,16 @@ public class QSDetailItems extends FrameLayout {
mEmptyText.setText(text);
}
+ /**
+ * Set the minimum height of this detail view, in item count.
+ */
+ public void setMinHeightInItems(int minHeightInItems) {
+ ViewGroup.LayoutParams lp = mMinHeightSpacer.getLayoutParams();
+ lp.height = minHeightInItems * getResources().getDimensionPixelSize(
+ R.dimen.qs_detail_item_height);
+ mMinHeightSpacer.setLayoutParams(lp);
+ }
+
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
@@ -135,7 +153,7 @@ public class QSDetailItems extends FrameLayout {
}
private void handleSetItems(Item[] items) {
- final int itemCount = items != null ? items.length : 0;
+ final int itemCount = items != null ? Math.min(items.length, mMaxItems) : 0;
mEmpty.setVisibility(itemCount == 0 ? VISIBLE : GONE);
mItems.setVisibility(itemCount == 0 ? GONE : VISIBLE);
for (int i = mItems.getChildCount() - 1; i >= itemCount; i--) {
@@ -162,10 +180,17 @@ public class QSDetailItems extends FrameLayout {
view.setVisibility(mItemsVisible ? VISIBLE : INVISIBLE);
final ImageView iv = (ImageView) view.findViewById(android.R.id.icon);
iv.setImageResource(item.icon);
+ iv.getOverlay().clear();
+ if (item.overlay != null) {
+ item.overlay.setBounds(0, 0, item.overlay.getIntrinsicWidth(),
+ item.overlay.getIntrinsicHeight());
+ iv.getOverlay().add(item.overlay);
+ }
final TextView title = (TextView) view.findViewById(android.R.id.title);
title.setText(item.line1);
final TextView summary = (TextView) view.findViewById(android.R.id.summary);
final boolean twoLines = !TextUtils.isEmpty(item.line2);
+ title.setMaxLines(twoLines ? 1 : 2);
summary.setVisibility(twoLines ? VISIBLE : GONE);
summary.setText(twoLines ? item.line2 : null);
view.setMinimumHeight(mContext.getResources() .getDimensionPixelSize(
@@ -213,6 +238,7 @@ public class QSDetailItems extends FrameLayout {
public static class Item {
public int icon;
+ public Drawable overlay;
public String line1;
public String line2;
public Object tag;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDualTileLabel.java b/packages/SystemUI/src/com/android/systemui/qs/QSDualTileLabel.java
index a9fdc86..67cfc59 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDualTileLabel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDualTileLabel.java
@@ -100,9 +100,6 @@ public class QSDualTileLabel extends LinearLayout {
mFirstLineCaret.setImageDrawable(d);
if (d != null) {
final int h = d.getIntrinsicHeight();
- final LayoutParams lp = (LayoutParams) mSecondLine.getLayoutParams();
- lp.topMargin = h * 4 / 5;
- mSecondLine.setLayoutParams(lp);
mFirstLine.setMinHeight(h);
mFirstLine.setPadding(mHorizontalPaddingPx, 0, 0, 0);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index fdebdd3..4dacacf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -67,8 +67,10 @@ public class QSPanel extends ViewGroup {
private int mPanelPaddingBottom;
private int mDualTileUnderlap;
private int mBrightnessPaddingTop;
+ private int mGridHeight;
private boolean mExpanded;
private boolean mListening;
+ private boolean mClosingDetail;
private Record mDetailRecord;
private Callback mCallback;
@@ -203,7 +205,7 @@ public class QSPanel extends ViewGroup {
}
}
- private void refreshAllTiles() {
+ public void refreshAllTiles() {
for (TileRecord r : mRecords) {
r.tile.refreshState();
}
@@ -296,7 +298,14 @@ public class QSPanel extends ViewGroup {
r.tile.secondaryClick();
}
};
- r.tileView.init(click, clickSecondary);
+ final View.OnLongClickListener longClick = new View.OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View v) {
+ r.tile.longClick();
+ return true;
+ }
+ };
+ r.tileView.init(click, clickSecondary, longClick);
r.tile.setListening(mListening);
callback.onStateChanged(r.tile.getState());
r.tile.refreshState();
@@ -313,6 +322,14 @@ public class QSPanel extends ViewGroup {
showDetail(false, mDetailRecord);
}
+ public boolean isClosingDetail() {
+ return mClosingDetail;
+ }
+
+ public int getGridHeight() {
+ return mGridHeight;
+ }
+
private void handleShowDetail(Record r, boolean show) {
if (r instanceof TileRecord) {
handleShowDetailTile((TileRecord) r, show);
@@ -357,6 +374,7 @@ public class QSPanel extends ViewGroup {
setDetailRecord(r);
listener = mHideGridContentWhenDone;
} else {
+ mClosingDetail = true;
setGridContentVisibility(true);
listener = mTeardownDetailWhenDone;
fireScanStateChanged(false);
@@ -405,7 +423,9 @@ public class QSPanel extends ViewGroup {
}
for (TileRecord record : mRecords) {
- record.tileView.setDual(record.tile.supportsDualTargets());
+ if (record.tileView.setDual(record.tile.supportsDualTargets())) {
+ record.tileView.handleStateChanged(record.tile.getState());
+ }
if (record.tileView.getVisibility() == GONE) continue;
final int cw = record.row == 0 ? mLargeCellWidth : mCellWidth;
final int ch = record.row == 0 ? mLargeCellHeight : mCellHeight;
@@ -419,6 +439,7 @@ public class QSPanel extends ViewGroup {
if (mDetail.getMeasuredHeight() < h) {
mDetail.measure(exactly(width), exactly(h));
}
+ mGridHeight = h;
setMeasuredDimension(width, Math.max(h, mDetail.getMeasuredHeight()));
}
@@ -530,6 +551,7 @@ public class QSPanel extends ViewGroup {
public void onAnimationEnd(Animator animation) {
mDetailContent.removeAllViews();
setDetailRecord(null);
+ mClosingDetail = false;
};
};
@@ -542,7 +564,10 @@ public class QSPanel extends ViewGroup {
@Override
public void onAnimationEnd(Animator animation) {
- setGridContentVisibility(false);
+ // Only hide content if still in detail state.
+ if (mDetailRecord != null) {
+ setGridContentVisibility(false);
+ }
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
index 6ef6e9e..1790a4e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
@@ -18,11 +18,13 @@ package com.android.systemui.qs;
import android.content.Context;
import android.content.Intent;
+import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
+import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
@@ -110,6 +112,10 @@ public abstract class QSTile<TState extends State> implements Listenable {
mHandler.sendEmptyMessage(H.SECONDARY_CLICK);
}
+ public void longClick() {
+ mHandler.sendEmptyMessage(H.LONG_CLICK);
+ }
+
public void showDetail(boolean show) {
mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0).sendToTarget();
}
@@ -153,6 +159,10 @@ public abstract class QSTile<TState extends State> implements Listenable {
// optional
}
+ protected void handleLongClick() {
+ // optional
+ }
+
protected void handleRefreshState(Object arg) {
handleUpdateState(mTmpState, arg);
final boolean changed = mTmpState.copyTo(mState);
@@ -214,12 +224,13 @@ public abstract class QSTile<TState extends State> implements Listenable {
private static final int SET_CALLBACK = 1;
private static final int CLICK = 2;
private static final int SECONDARY_CLICK = 3;
- private static final int REFRESH_STATE = 4;
- private static final int SHOW_DETAIL = 5;
- private static final int USER_SWITCH = 6;
- private static final int TOGGLE_STATE_CHANGED = 7;
- private static final int SCAN_STATE_CHANGED = 8;
- private static final int DESTROY = 9;
+ private static final int LONG_CLICK = 4;
+ private static final int REFRESH_STATE = 5;
+ private static final int SHOW_DETAIL = 6;
+ private static final int USER_SWITCH = 7;
+ private static final int TOGGLE_STATE_CHANGED = 8;
+ private static final int SCAN_STATE_CHANGED = 9;
+ private static final int DESTROY = 10;
private H(Looper looper) {
super(looper);
@@ -239,6 +250,9 @@ public abstract class QSTile<TState extends State> implements Listenable {
} else if (msg.what == SECONDARY_CLICK) {
name = "handleSecondaryClick";
handleSecondaryClick();
+ } else if (msg.what == LONG_CLICK) {
+ name = "handleLongClick";
+ handleLongClick();
} else if (msg.what == REFRESH_STATE) {
name = "handleRefreshState";
handleRefreshState(msg.obj);
@@ -299,10 +313,91 @@ public abstract class QSTile<TState extends State> implements Listenable {
}
}
+ public static abstract class Icon {
+ abstract public Drawable getDrawable(Context context);
+
+ @Override
+ public int hashCode() {
+ return Icon.class.hashCode();
+ }
+ }
+
+ public static class ResourceIcon extends Icon {
+ private static final SparseArray<Icon> ICONS = new SparseArray<Icon>();
+
+ private final int mResId;
+
+ private ResourceIcon(int resId) {
+ mResId = resId;
+ }
+
+ public static Icon get(int resId) {
+ Icon icon = ICONS.get(resId);
+ if (icon == null) {
+ icon = new ResourceIcon(resId);
+ ICONS.put(resId, icon);
+ }
+ return icon;
+ }
+
+ @Override
+ public Drawable getDrawable(Context context) {
+ return context.getDrawable(mResId);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return o instanceof ResourceIcon && ((ResourceIcon) o).mResId == mResId;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("ResourceIcon[resId=0x%08x]", mResId);
+ }
+ }
+
+ protected class AnimationIcon extends ResourceIcon {
+ private boolean mAllowAnimation;
+
+ public AnimationIcon(int resId) {
+ super(resId);
+ }
+
+ public void setAllowAnimation(boolean allowAnimation) {
+ mAllowAnimation = allowAnimation;
+ }
+
+ @Override
+ public Drawable getDrawable(Context context) {
+ // workaround: get a clean state for every new AVD
+ final AnimatedVectorDrawable d = (AnimatedVectorDrawable) super.getDrawable(context)
+ .getConstantState().newDrawable();
+ d.start();
+ if (mAllowAnimation) {
+ mAllowAnimation = false;
+ } else {
+ d.stop(); // skip directly to end state
+ }
+ return d;
+ }
+ }
+
+ protected enum UserBoolean {
+ USER_TRUE(true, true),
+ USER_FALSE(true, false),
+ BACKGROUND_TRUE(false, true),
+ BACKGROUND_FALSE(false, false);
+ public final boolean value;
+ public final boolean userInitiated;
+ private UserBoolean(boolean userInitiated, boolean value) {
+ this.value = value;
+ this.userInitiated = userInitiated;
+ }
+ }
+
public static class State {
public boolean visible;
- public int iconId;
- public Drawable icon;
+ public Icon icon;
public String label;
public String contentDescription;
public String dualLabelContentDescription;
@@ -312,7 +407,6 @@ public abstract class QSTile<TState extends State> implements Listenable {
if (other == null) throw new IllegalArgumentException();
if (!other.getClass().equals(getClass())) throw new IllegalArgumentException();
final boolean changed = other.visible != visible
- || other.iconId != iconId
|| !Objects.equals(other.icon, icon)
|| !Objects.equals(other.label, label)
|| !Objects.equals(other.contentDescription, contentDescription)
@@ -320,7 +414,6 @@ public abstract class QSTile<TState extends State> implements Listenable {
|| !Objects.equals(other.dualLabelContentDescription,
dualLabelContentDescription);
other.visible = visible;
- other.iconId = iconId;
other.icon = icon;
other.label = label;
other.contentDescription = contentDescription;
@@ -335,9 +428,8 @@ public abstract class QSTile<TState extends State> implements Listenable {
}
protected StringBuilder toStringBuilder() {
- final StringBuilder sb = new StringBuilder( getClass().getSimpleName()).append('[');
+ final StringBuilder sb = new StringBuilder(getClass().getSimpleName()).append('[');
sb.append("visible=").append(visible);
- sb.append(",iconId=").append(iconId);
sb.append(",icon=").append(icon);
sb.append(",label=").append(label);
sb.append(",contentDescription=").append(contentDescription);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
index 3574877..16ae6b4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
@@ -21,6 +21,7 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Typeface;
+import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.RippleDrawable;
import android.os.Handler;
@@ -39,6 +40,8 @@ import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile.State;
+import java.util.Objects;
+
/** View that represents a standard quick settings tile. **/
public class QSTileView extends ViewGroup {
private static final Typeface CONDENSED = Typeface.create("sans-serif-condensed",
@@ -60,6 +63,8 @@ public class QSTileView extends ViewGroup {
private boolean mDual;
private OnClickListener mClickPrimary;
private OnClickListener mClickSecondary;
+ private OnLongClickListener mLongClick;
+ private Drawable mTileBackground;
private RippleDrawable mRipple;
public QSTileView(Context context) {
@@ -72,6 +77,7 @@ public class QSTileView extends ViewGroup {
mTilePaddingBelowIconPx = res.getDimensionPixelSize(R.dimen.qs_tile_padding_below_icon);
mDualTileVerticalPaddingPx =
res.getDimensionPixelSize(R.dimen.qs_dual_tile_padding_vertical);
+ mTileBackground = newTileBackground();
recreateLabel();
setClipChildren(false);
@@ -132,6 +138,7 @@ public class QSTileView extends ViewGroup {
mDualLabel = new QSDualTileLabel(mContext);
mDualLabel.setId(android.R.id.title);
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.setPadding(0, mDualTileVerticalPaddingPx, 0, mDualTileVerticalPaddingPx);
mDualLabel.setTypeface(CONDENSED);
@@ -165,33 +172,34 @@ public class QSTileView extends ViewGroup {
}
}
- public void setDual(boolean dual) {
+ public boolean setDual(boolean dual) {
final boolean changed = dual != mDual;
mDual = dual;
if (changed) {
recreateLabel();
}
- Drawable tileBackground = getTileBackground();
- if (tileBackground instanceof RippleDrawable) {
- setRipple((RippleDrawable) tileBackground);
+ if (mTileBackground instanceof RippleDrawable) {
+ setRipple((RippleDrawable) mTileBackground);
}
if (dual) {
mTopBackgroundView.setOnClickListener(mClickPrimary);
setOnClickListener(null);
setClickable(false);
setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
- mTopBackgroundView.setBackground(tileBackground);
+ mTopBackgroundView.setBackground(mTileBackground);
} else {
mTopBackgroundView.setOnClickListener(null);
mTopBackgroundView.setClickable(false);
setOnClickListener(mClickPrimary);
+ setOnLongClickListener(mLongClick);
setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
- setBackground(tileBackground);
+ setBackground(mTileBackground);
}
mTopBackgroundView.setFocusable(dual);
setFocusable(!dual);
mDivider.setVisibility(dual ? VISIBLE : GONE);
postInvalidate();
+ return changed;
}
private void setRipple(RippleDrawable tileBackground) {
@@ -201,9 +209,11 @@ public class QSTileView extends ViewGroup {
}
}
- public void init(OnClickListener clickPrimary, OnClickListener clickSecondary) {
+ public void init(OnClickListener clickPrimary, OnClickListener clickSecondary,
+ OnLongClickListener longClick) {
mClickPrimary = clickPrimary;
mClickSecondary = clickSecondary;
+ mLongClick = longClick;
}
protected View createIcon() {
@@ -213,7 +223,7 @@ public class QSTileView extends ViewGroup {
return icon;
}
- private Drawable getTileBackground() {
+ private Drawable newTileBackground() {
final int[] attrs = new int[] { android.R.attr.selectableItemBackgroundBorderless };
final TypedArray ta = mContext.obtainStyledAttributes(attrs);
final Drawable d = ta.getDrawable(0);
@@ -284,16 +294,7 @@ public class QSTileView extends ViewGroup {
protected void handleStateChanged(QSTile.State state) {
if (mIcon instanceof ImageView) {
- ImageView iv = (ImageView) mIcon;
- if (state.icon != null) {
- iv.setImageDrawable(state.icon);
- } else if (state.iconId > 0) {
- iv.setImageResource(state.iconId);
- }
- Drawable drawable = iv.getDrawable();
- if (state.autoMirrorDrawable && drawable != null) {
- drawable.setAutoMirrored(true);
- }
+ setIcon((ImageView) mIcon, state);
}
if (mDual) {
mDualLabel.setText(state.label);
@@ -305,6 +306,22 @@ public class QSTileView extends ViewGroup {
}
}
+ protected void setIcon(ImageView iv, QSTile.State state) {
+ if (!Objects.equals(state.icon, iv.getTag(R.id.qs_icon_tag))) {
+ Drawable d = state.icon != null ? state.icon.getDrawable(mContext) : null;
+ if (d != null && state.autoMirrorDrawable) {
+ d.setAutoMirrored(true);
+ }
+ 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
+ }
+ }
+ }
+ }
+
public void onStateChanged(QSTile.State state) {
mHandler.obtainMessage(H.STATE_CHANGED, state).sendToTarget();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java b/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java
index 0ab6626..4f812bc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java
@@ -26,24 +26,26 @@ import com.android.systemui.statusbar.policy.Listenable;
/** Helper for managing a secure setting. **/
public abstract class SecureSetting extends ContentObserver implements Listenable {
+ private static final int DEFAULT = 0;
+
private final Context mContext;
private final String mSettingName;
private boolean mListening;
private int mUserId;
+ private int mObservedValue = DEFAULT;
- protected abstract void handleValueChanged(int value);
+ protected abstract void handleValueChanged(int value, boolean observedChange);
public SecureSetting(Context context, Handler handler, String settingName) {
super(handler);
mContext = context;
mSettingName = settingName;
mUserId = ActivityManager.getCurrentUser();
- setListening(true);
}
public int getValue() {
- return Secure.getIntForUser(mContext.getContentResolver(), mSettingName, 0, mUserId);
+ return Secure.getIntForUser(mContext.getContentResolver(), mSettingName, DEFAULT, mUserId);
}
public void setValue(int value) {
@@ -52,18 +54,23 @@ public abstract class SecureSetting extends ContentObserver implements Listenabl
@Override
public void setListening(boolean listening) {
+ if (listening == mListening) return;
mListening = listening;
if (listening) {
+ mObservedValue = getValue();
mContext.getContentResolver().registerContentObserver(
Secure.getUriFor(mSettingName), false, this, mUserId);
} else {
mContext.getContentResolver().unregisterContentObserver(this);
+ mObservedValue = DEFAULT;
}
}
@Override
public void onChange(boolean selfChange) {
- handleValueChanged(getValue());
+ final int value = getValue();
+ handleValueChanged(value, value != mObservedValue);
+ mObservedValue = value;
}
public void setUserId(int userId) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/SignalTileView.java b/packages/SystemUI/src/com/android/systemui/qs/SignalTileView.java
index cfcd74e..9ac7944 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/SignalTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/SignalTileView.java
@@ -104,7 +104,7 @@ public final class SignalTileView extends QSTileView {
protected void handleStateChanged(QSTile.State state) {
super.handleStateChanged(state);
final SignalState s = (SignalState) state;
- mSignal.setImageResource(s.iconId);
+ setIcon(mSignal, s);
if (s.overlayIconId > 0) {
mOverlay.setVisibility(VISIBLE);
mOverlay.setImageResource(s.overlayIconId);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/UsageTracker.java b/packages/SystemUI/src/com/android/systemui/qs/UsageTracker.java
index ad79aba..e60aa53 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/UsageTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/UsageTracker.java
@@ -18,11 +18,13 @@ package com.android.systemui.qs;
import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.Listenable;
public class UsageTracker implements Listenable {
@@ -35,11 +37,10 @@ public class UsageTracker implements Listenable {
private boolean mRegistered;
- public UsageTracker(Context context, Class<?> tile) {
+ public UsageTracker(Context context, Class<?> tile, int timeoutResource) {
mContext = context;
mPrefKey = tile.getSimpleName() + "LastUsed";
- mTimeToShowTile = MILLIS_PER_DAY * mContext.getResources()
- .getInteger(R.integer.days_to_show_timeout_tiles);
+ mTimeToShowTile = MILLIS_PER_DAY * mContext.getResources().getInteger(timeoutResource);
mResetAction = "com.android.systemui.qs." + tile.getSimpleName() + ".usage_reset";
}
@@ -67,6 +68,25 @@ public class UsageTracker implements Listenable {
getSharedPrefs().edit().remove(mPrefKey).commit();
}
+ public void showResetConfirmation(String title, final Runnable onConfirmed) {
+ final SystemUIDialog d = new SystemUIDialog(mContext);
+ d.setTitle(title);
+ d.setMessage(mContext.getString(R.string.quick_settings_reset_confirmation_message));
+ d.setNegativeButton(android.R.string.cancel, null);
+ d.setPositiveButton(R.string.quick_settings_reset_confirmation_button,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ reset();
+ if (onConfirmed != null) {
+ onConfirmed.run();
+ }
+ }
+ });
+ d.setCanceledOnTouchOutside(true);
+ d.show();
+ }
+
private SharedPreferences getSharedPrefs() {
return mContext.getSharedPreferences(mContext.getPackageName(), 0);
}
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 51401c8..2dd02a5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
@@ -29,6 +29,10 @@ import com.android.systemui.qs.QSTile;
/** Quick settings tile: Airplane mode **/
public class AirplaneModeTile extends QSTile<QSTile.BooleanState> {
+ private final AnimationIcon mEnable =
+ new AnimationIcon(R.drawable.ic_signal_airplane_enable_animation);
+ private final AnimationIcon mDisable =
+ new AnimationIcon(R.drawable.ic_signal_airplane_disable_animation);
private final GlobalSetting mSetting;
private boolean mListening;
@@ -52,6 +56,8 @@ public class AirplaneModeTile extends QSTile<QSTile.BooleanState> {
@Override
public void handleClick() {
setEnabled(!mState.value);
+ mEnable.setAllowAnimation(true);
+ mDisable.setAllowAnimation(true);
}
private void setEnabled(boolean enabled) {
@@ -68,11 +74,11 @@ public class AirplaneModeTile extends QSTile<QSTile.BooleanState> {
state.visible = true;
state.label = mContext.getString(R.string.quick_settings_airplane_mode_label);
if (airplaneMode) {
- state.iconId = R.drawable.ic_qs_airplane_on;
+ state.icon = mEnable;
state.contentDescription = mContext.getString(
R.string.accessibility_quick_settings_airplane_on);
} else {
- state.iconId = R.drawable.ic_qs_airplane_off;
+ state.icon = mDisable;
state.contentDescription = mContext.getString(
R.string.accessibility_quick_settings_airplane_off);
}
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 e99b4c5..c15566f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -77,7 +77,11 @@ public class BluetoothTile extends QSTile<QSTile.BooleanState> {
@Override
protected void handleSecondaryClick() {
- mHost.startSettingsActivity(BLUETOOTH_SETTINGS);
+ if (!mState.value) {
+ mState.value = true;
+ mController.setBluetoothEnabled(true);
+ }
+ showDetail(true);
}
@Override
@@ -92,17 +96,17 @@ public class BluetoothTile extends QSTile<QSTile.BooleanState> {
if (enabled) {
state.label = null;
if (connected) {
- state.iconId = R.drawable.ic_qs_bluetooth_connected;
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_bluetooth_connected);
state.contentDescription = mContext.getString(
R.string.accessibility_quick_settings_bluetooth_connected);
state.label = mController.getLastDeviceName();
} else if (connecting) {
- state.iconId = R.drawable.ic_qs_bluetooth_connecting;
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_bluetooth_connecting);
state.contentDescription = mContext.getString(
R.string.accessibility_quick_settings_bluetooth_connecting);
state.label = mContext.getString(R.string.quick_settings_bluetooth_label);
} else {
- state.iconId = R.drawable.ic_qs_bluetooth_on;
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_bluetooth_on);
state.contentDescription = mContext.getString(
R.string.accessibility_quick_settings_bluetooth_on);
}
@@ -110,7 +114,7 @@ public class BluetoothTile extends QSTile<QSTile.BooleanState> {
state.label = mContext.getString(R.string.quick_settings_bluetooth_label);
}
} else {
- state.iconId = R.drawable.ic_qs_bluetooth_off;
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_bluetooth_off);
state.label = mContext.getString(R.string.quick_settings_bluetooth_label);
state.contentDescription = mContext.getString(
R.string.accessibility_quick_settings_bluetooth_off);
@@ -181,6 +185,7 @@ public class BluetoothTile extends QSTile<QSTile.BooleanState> {
mItems.setEmptyState(R.drawable.ic_qs_bluetooth_detail_empty,
R.string.quick_settings_bluetooth_detail_empty_text);
mItems.setCallback(this);
+ mItems.setMinHeightInItems(0);
updateItems();
setItemsVisible(mState.value);
return mItems;
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 8304291..5bf6fb5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -107,7 +107,8 @@ public class CastTile extends QSTile<QSTile.BooleanState> {
if (!state.value && connecting) {
state.label = mContext.getString(R.string.quick_settings_connecting);
}
- state.iconId = state.value ? R.drawable.ic_qs_cast_on : R.drawable.ic_qs_cast_off;
+ state.icon = ResourceIcon.get(state.value ? R.drawable.ic_qs_cast_on
+ : R.drawable.ic_qs_cast_off);
mDetailAdapter.updateItems(devices);
}
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 359a259..30f92b9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -29,7 +29,8 @@ 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.DataUsageInfo;
+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;
/** Quick settings tile: Cellular **/
@@ -38,11 +39,13 @@ public class CellularTile extends QSTile<QSTile.SignalState> {
"com.android.settings", "com.android.settings.Settings$DataUsageSummaryActivity"));
private final NetworkController mController;
+ private final MobileDataController mDataController;
private final CellularDetailAdapter mDetailAdapter;
public CellularTile(Host host) {
super(host);
mController = host.getNetworkController();
+ mDataController = mController.getMobileDataController();
mDetailAdapter = new CellularDetailAdapter();
}
@@ -72,7 +75,7 @@ public class CellularTile extends QSTile<QSTile.SignalState> {
@Override
protected void handleClick() {
- if (mController.isMobileDataSupported()) {
+ if (mDataController.isMobileDataSupported()) {
showDetail(true);
} else {
mHost.startSettingsActivity(CELLULAR_SETTINGS);
@@ -87,16 +90,15 @@ public class CellularTile extends QSTile<QSTile.SignalState> {
if (cb == null) return;
final Resources r = mContext.getResources();
- state.iconId = cb.noSim ? R.drawable.ic_qs_no_sim
+ final int iconId = cb.noSim ? R.drawable.ic_qs_no_sim
: !cb.enabled || cb.airplaneModeEnabled ? R.drawable.ic_qs_signal_disabled
: cb.mobileSignalIconId > 0 ? cb.mobileSignalIconId
: R.drawable.ic_qs_signal_no_signal;
+ state.icon = ResourceIcon.get(iconId);
state.isOverlayIconWide = cb.isDataTypeIconWide;
state.autoMirrorDrawable = !cb.noSim;
- state.overlayIconId = cb.enabled && (cb.dataTypeIconId > 0) && !cb.wifiConnected
- ? cb.dataTypeIconId
- : 0;
- state.filter = state.iconId != R.drawable.ic_qs_no_sim;
+ state.overlayIconId = cb.enabled && (cb.dataTypeIconId > 0) ? cb.dataTypeIconId : 0;
+ state.filter = iconId != R.drawable.ic_qs_no_sim;
state.activityIn = cb.enabled && cb.activityIn;
state.activityOut = cb.enabled && cb.activityOut;
@@ -143,16 +145,15 @@ public class CellularTile extends QSTile<QSTile.SignalState> {
}
private final NetworkSignalChangedCallback mCallback = new NetworkSignalChangedCallback() {
- private boolean mWifiEnabled;
- private boolean mWifiConnected;
- private boolean mAirplaneModeEnabled;
+ private final CallbackInfo mInfo = new CallbackInfo();
@Override
public void onWifiSignalChanged(boolean enabled, boolean connected, int wifiSignalIconId,
boolean activityIn, boolean activityOut,
String wifiSignalContentDescriptionId, String description) {
- mWifiEnabled = enabled;
- mWifiConnected = connected;
+ mInfo.wifiEnabled = enabled;
+ mInfo.wifiConnected = connected;
+ refreshState(mInfo);
}
@Override
@@ -160,28 +161,40 @@ public class CellularTile extends QSTile<QSTile.SignalState> {
int mobileSignalIconId,
String mobileSignalContentDescriptionId, int dataTypeIconId,
boolean activityIn, boolean activityOut,
- String dataTypeContentDescriptionId, String description, boolean noSim,
+ String dataTypeContentDescriptionId, String description,
boolean isDataTypeIconWide) {
- final CallbackInfo info = new CallbackInfo(); // TODO pool?
- info.enabled = enabled;
- info.wifiEnabled = mWifiEnabled;
- info.wifiConnected = mWifiConnected;
- info.airplaneModeEnabled = mAirplaneModeEnabled;
- info.mobileSignalIconId = mobileSignalIconId;
- info.signalContentDescription = mobileSignalContentDescriptionId;
- info.dataTypeIconId = dataTypeIconId;
- info.dataContentDescription = dataTypeContentDescriptionId;
- info.activityIn = activityIn;
- info.activityOut = activityOut;
- info.enabledDesc = description;
- info.noSim = noSim;
- info.isDataTypeIconWide = isDataTypeIconWide;
- refreshState(info);
+ mInfo.enabled = enabled;
+ mInfo.mobileSignalIconId = mobileSignalIconId;
+ mInfo.signalContentDescription = mobileSignalContentDescriptionId;
+ mInfo.dataTypeIconId = dataTypeIconId;
+ mInfo.dataContentDescription = dataTypeContentDescriptionId;
+ mInfo.activityIn = activityIn;
+ mInfo.activityOut = activityOut;
+ mInfo.enabledDesc = description;
+ mInfo.isDataTypeIconWide = isDataTypeIconWide;
+ refreshState(mInfo);
+ }
+
+ @Override
+ public void onNoSimVisibleChanged(boolean visible) {
+ mInfo.noSim = visible;
+ if (mInfo.noSim) {
+ // Make sure signal gets cleared out when no sims.
+ mInfo.mobileSignalIconId = 0;
+ mInfo.dataTypeIconId = 0;
+ // Show a No SIMs description to avoid emergency calls message.
+ mInfo.enabled = true;
+ mInfo.enabledDesc = mContext.getString(
+ R.string.keyguard_missing_sim_message_short);
+ mInfo.signalContentDescription = mInfo.enabledDesc;
+ }
+ refreshState(mInfo);
}
@Override
public void onAirplaneModeChanged(boolean enabled) {
- mAirplaneModeEnabled = enabled;
+ mInfo.airplaneModeEnabled = enabled;
+ refreshState(mInfo);
}
public void onMobileDataEnabled(boolean enabled) {
@@ -198,7 +211,9 @@ public class CellularTile extends QSTile<QSTile.SignalState> {
@Override
public Boolean getToggleState() {
- return mController.isMobileDataSupported() ? mController.isMobileDataEnabled() : null;
+ return mDataController.isMobileDataSupported()
+ ? mDataController.isMobileDataEnabled()
+ : null;
}
@Override
@@ -208,7 +223,7 @@ public class CellularTile extends QSTile<QSTile.SignalState> {
@Override
public void setToggleState(boolean state) {
- mController.setMobileDataEnabled(state);
+ mDataController.setMobileDataEnabled(state);
}
@Override
@@ -216,7 +231,7 @@ public class CellularTile extends QSTile<QSTile.SignalState> {
final DataUsageDetailView v = (DataUsageDetailView) (convertView != null
? convertView
: LayoutInflater.from(mContext).inflate(R.layout.data_usage, parent, false));
- final DataUsageInfo info = mController.getDataUsageInfo();
+ final DataUsageInfo info = mDataController.getDataUsageInfo();
if (info == null) return v;
v.bind(info);
return v;
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 01849c1..5963a45 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
@@ -26,6 +26,10 @@ import com.android.systemui.qs.UsageTracker;
/** Quick settings tile: Invert colors **/
public class ColorInversionTile extends QSTile<QSTile.BooleanState> {
+ private final AnimationIcon mEnable
+ = new AnimationIcon(R.drawable.ic_invert_colors_enable_animation);
+ private final AnimationIcon mDisable
+ = new AnimationIcon(R.drawable.ic_invert_colors_disable_animation);
private final SecureSetting mSetting;
private final UsageTracker mUsageTracker;
@@ -37,14 +41,17 @@ public class ColorInversionTile extends QSTile<QSTile.BooleanState> {
mSetting = new SecureSetting(mContext, mHandler,
Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED) {
@Override
- protected void handleValueChanged(int value) {
- mUsageTracker.trackUsage();
+ protected void handleValueChanged(int value, boolean observedChange) {
+ if (value != 0 || observedChange) {
+ mUsageTracker.trackUsage();
+ }
if (mListening) {
handleRefreshState(value);
}
}
};
- mUsageTracker = new UsageTracker(host.getContext(), ColorInversionTile.class);
+ mUsageTracker = new UsageTracker(host.getContext(), ColorInversionTile.class,
+ R.integer.days_to_show_color_inversion_tile);
if (mSetting.getValue() != 0 && !mUsageTracker.isRecentlyUsed()) {
mUsageTracker.trackUsage();
}
@@ -78,6 +85,21 @@ public class ColorInversionTile extends QSTile<QSTile.BooleanState> {
@Override
protected void handleClick() {
mSetting.setValue(mState.value ? 0 : 1);
+ mEnable.setAllowAnimation(true);
+ mDisable.setAllowAnimation(true);
+ }
+
+ @Override
+ protected void handleLongClick() {
+ if (mState.value) return; // don't allow usage reset if inversion is active
+ final String title = mContext.getString(R.string.quick_settings_reset_confirmation_title,
+ mState.label);
+ mUsageTracker.showResetConfirmation(title, new Runnable() {
+ @Override
+ public void run() {
+ refreshState();
+ }
+ });
}
@Override
@@ -87,7 +109,7 @@ public class ColorInversionTile extends QSTile<QSTile.BooleanState> {
state.visible = enabled || mUsageTracker.isRecentlyUsed();
state.value = enabled;
state.label = mContext.getString(R.string.quick_settings_inversion_label);
- state.iconId = enabled ? R.drawable.ic_qs_inversion_on : R.drawable.ic_qs_inversion_off;
+ state.icon = enabled ? mEnable : mDisable;
}
@Override
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 7bdb58f..eb816b7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataUsageDetailView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataUsageDetailView.java
@@ -20,7 +20,6 @@ import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.util.AttributeSet;
-import android.util.TypedValue;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
@@ -61,7 +60,7 @@ public class DataUsageDetailView extends LinearLayout {
R.dimen.qs_data_usage_text_size);
}
- public void bind(NetworkController.DataUsageInfo info) {
+ public void bind(NetworkController.MobileDataController.DataUsageInfo info) {
final Resources res = mContext.getResources();
final int titleId;
final long bytes;
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 e6b7f02..5c1a317 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
@@ -31,6 +31,10 @@ public class FlashlightTile extends QSTile<QSTile.BooleanState> implements
* 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;
@@ -66,7 +70,7 @@ public class FlashlightTile extends QSTile<QSTile.BooleanState> implements
}
boolean newState = !mState.value;
mFlashlightController.setFlashlight(newState);
- refreshState(newState);
+ refreshState(newState ? UserBoolean.USER_TRUE : UserBoolean.USER_FALSE);
}
@Override
@@ -75,8 +79,8 @@ public class FlashlightTile extends QSTile<QSTile.BooleanState> implements
mWasLastOn = SystemClock.uptimeMillis();
}
- if (arg instanceof Boolean) {
- state.value = (Boolean) arg;
+ if (arg instanceof UserBoolean) {
+ state.value = ((UserBoolean) arg).value;
}
if (!state.value && mWasLastOn != 0) {
@@ -92,8 +96,9 @@ public class FlashlightTile extends QSTile<QSTile.BooleanState> implements
// 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);
- state.iconId = state.value
- ? R.drawable.ic_qs_flashlight_on : R.drawable.ic_qs_flashlight_off;
+ final AnimationIcon icon = state.value ? mEnable : mDisable;
+ icon.setAllowAnimation(arg instanceof UserBoolean && ((UserBoolean) arg).userInitiated);
+ state.icon = icon;
int onOrOffId = state.value
? R.string.accessibility_quick_settings_flashlight_on
: R.string.accessibility_quick_settings_flashlight_off;
@@ -111,12 +116,12 @@ public class FlashlightTile extends QSTile<QSTile.BooleanState> implements
@Override
public void onFlashlightOff() {
- refreshState(false);
+ refreshState(UserBoolean.BACKGROUND_FALSE);
}
@Override
public void onFlashlightError() {
- refreshState(false);
+ refreshState(UserBoolean.BACKGROUND_FALSE);
}
@Override
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 ce99cc3..fcc517f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -24,18 +24,25 @@ import com.android.systemui.R;
import com.android.systemui.qs.UsageTracker;
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.policy.HotspotController;
+import com.android.systemui.statusbar.policy.KeyguardMonitor;
/** Quick settings tile: Hotspot **/
public class HotspotTile extends QSTile<QSTile.BooleanState> {
+ private final AnimationIcon mEnable =
+ new AnimationIcon(R.drawable.ic_hotspot_enable_animation);
+ private final AnimationIcon mDisable =
+ new AnimationIcon(R.drawable.ic_hotspot_disable_animation);
private final HotspotController mController;
private final Callback mCallback = new Callback();
private final UsageTracker mUsageTracker;
+ private final KeyguardMonitor mKeyguard;
public HotspotTile(Host host) {
super(host);
mController = host.getHotspotController();
- mUsageTracker = new UsageTracker(host.getContext(), HotspotTile.class);
+ mUsageTracker = newUsageTracker(host.getContext());
mUsageTracker.setListening(true);
+ mKeyguard = host.getKeyguardMonitor();
}
@Override
@@ -62,17 +69,30 @@ public class HotspotTile extends QSTile<QSTile.BooleanState> {
protected void handleClick() {
final boolean isEnabled = (Boolean) mState.value;
mController.setHotspotEnabled(!isEnabled);
+ mEnable.setAllowAnimation(true);
+ mDisable.setAllowAnimation(true);
+ }
+
+ @Override
+ protected void handleLongClick() {
+ if (mState.value) return; // don't allow usage reset if hotspot is active
+ final String title = mContext.getString(R.string.quick_settings_reset_confirmation_title,
+ mState.label);
+ mUsageTracker.showResetConfirmation(title, new Runnable() {
+ @Override
+ public void run() {
+ refreshState();
+ }
+ });
}
@Override
protected void handleUpdateState(BooleanState state, Object arg) {
- state.visible = mController.isHotspotSupported() && mUsageTracker.isRecentlyUsed()
- && !mController.isProvisioningNeeded();
+ state.visible = mController.isHotspotSupported() && mUsageTracker.isRecentlyUsed();
state.label = mContext.getString(R.string.quick_settings_hotspot_label);
state.value = mController.isHotspotEnabled();
- state.iconId = state.visible && state.value ? R.drawable.ic_qs_hotspot_on
- : R.drawable.ic_qs_hotspot_off;
+ state.icon = state.visible && state.value ? mEnable : mDisable;
}
@Override
@@ -84,6 +104,10 @@ 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);
+ }
+
private final class Callback implements HotspotController.Callback {
@Override
public void onHotspotChanged(boolean enabled) {
@@ -101,7 +125,7 @@ public class HotspotTile extends QSTile<QSTile.BooleanState> {
@Override
public void onReceive(Context context, Intent intent) {
if (mUsageTracker == null) {
- mUsageTracker = new UsageTracker(context, HotspotTile.class);
+ mUsageTracker = newUsageTracker(context);
}
mUsageTracker.trackUsage();
}
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 58587e6..2736530 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
@@ -31,11 +31,16 @@ import android.util.Log;
import com.android.systemui.qs.QSTile;
+import java.util.Arrays;
+import java.util.Objects;
+
public class IntentTile extends QSTile<QSTile.State> {
public static final String PREFIX = "intent(";
private PendingIntent mOnClick;
private String mOnClickUri;
+ private PendingIntent mOnLongClick;
+ private String mOnLongClickUri;
private int mCurrentUserId;
private IntentTile(Host host, String action) {
@@ -77,15 +82,24 @@ public class IntentTile extends QSTile<QSTile.State> {
@Override
protected void handleClick() {
+ sendIntent("click", mOnClick, mOnClickUri);
+ }
+
+ @Override
+ protected void handleLongClick() {
+ sendIntent("long-click", mOnLongClick, mOnLongClickUri);
+ }
+
+ private void sendIntent(String type, PendingIntent pi, String uri) {
try {
- if (mOnClick != null) {
- mOnClick.send();
- } else if (mOnClickUri != null) {
- final Intent intent = Intent.parseUri(mOnClickUri, Intent.URI_INTENT_SCHEME);
+ if (pi != null) {
+ pi.send();
+ } else if (uri != null) {
+ final Intent intent = Intent.parseUri(uri, Intent.URI_INTENT_SCHEME);
mContext.sendBroadcastAsUser(intent, new UserHandle(mCurrentUserId));
}
} catch (Throwable t) {
- Log.w(TAG, "Error sending click intent", t);
+ Log.w(TAG, "Error sending " + type + " intent", t);
}
}
@@ -96,13 +110,11 @@ public class IntentTile extends QSTile<QSTile.State> {
state.visible = intent.getBooleanExtra("visible", true);
state.contentDescription = intent.getStringExtra("contentDescription");
state.label = intent.getStringExtra("label");
- state.iconId = 0;
state.icon = null;
final byte[] iconBitmap = intent.getByteArrayExtra("iconBitmap");
if (iconBitmap != null) {
try {
- final Bitmap b = BitmapFactory.decodeByteArray(iconBitmap, 0, iconBitmap.length);
- state.icon = new BitmapDrawable(mContext.getResources(), b);
+ state.icon = new BytesIcon(iconBitmap);
} catch (Throwable t) {
Log.w(TAG, "Error loading icon bitmap, length " + iconBitmap.length, t);
}
@@ -111,23 +123,16 @@ public class IntentTile extends QSTile<QSTile.State> {
if (iconId != 0) {
final String iconPackage = intent.getStringExtra("iconPackage");
if (!TextUtils.isEmpty(iconPackage)) {
- state.icon = getPackageDrawable(iconPackage, iconId);
+ state.icon = new PackageDrawableIcon(iconPackage, iconId);
} else {
- state.iconId = iconId;
+ state.icon = ResourceIcon.get(iconId);
}
}
}
mOnClick = intent.getParcelableExtra("onClick");
mOnClickUri = intent.getStringExtra("onClickUri");
- }
-
- private Drawable getPackageDrawable(String pkg, int id) {
- try {
- return mContext.createPackageContext(pkg, 0).getDrawable(id);
- } catch (Throwable t) {
- Log.w(TAG, "Error loading package drawable pkg=" + pkg + " id=" + id, t);
- return null;
- }
+ mOnLongClick = intent.getParcelableExtra("onLongClick");
+ mOnLongClickUri = intent.getStringExtra("onLongClickUri");
}
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -136,4 +141,60 @@ public class IntentTile extends QSTile<QSTile.State> {
refreshState(intent);
}
};
+
+ private static class BytesIcon extends Icon {
+ private final byte[] mBytes;
+
+ public BytesIcon(byte[] bytes) {
+ mBytes = bytes;
+ }
+
+ @Override
+ public Drawable getDrawable(Context context) {
+ final Bitmap b = BitmapFactory.decodeByteArray(mBytes, 0, mBytes.length);
+ return new BitmapDrawable(context.getResources(), b);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return o instanceof BytesIcon && Arrays.equals(((BytesIcon) o).mBytes, mBytes);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("BytesIcon[len=%s]", mBytes.length);
+ }
+ }
+
+ private class PackageDrawableIcon extends Icon {
+ private final String mPackage;
+ private final int mResId;
+
+ public PackageDrawableIcon(String pkg, int resId) {
+ mPackage = pkg;
+ mResId = resId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof PackageDrawableIcon)) return false;
+ final PackageDrawableIcon other = (PackageDrawableIcon) o;
+ return Objects.equals(other.mPackage, mPackage) && other.mResId == mResId;
+ }
+
+ @Override
+ public Drawable getDrawable(Context context) {
+ try {
+ return context.createPackageContext(mPackage, 0).getDrawable(mResId);
+ } catch (Throwable t) {
+ Log.w(TAG, "Error loading package drawable pkg=" + mPackage + " id=" + mResId, t);
+ return null;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return String.format("PackageDrawableIcon[pkg=%s,id=0x%08x]", mPackage, mResId);
+ }
+ }
}
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 d1dc5d2..11ec722 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
@@ -25,6 +25,11 @@ import com.android.systemui.statusbar.policy.LocationController.LocationSettings
/** Quick settings tile: Location **/
public class LocationTile extends QSTile<QSTile.BooleanState> {
+ private final AnimationIcon mEnable =
+ new AnimationIcon(R.drawable.ic_signal_location_enable_animation);
+ private final AnimationIcon mDisable =
+ new AnimationIcon(R.drawable.ic_signal_location_disable_animation);
+
private final LocationController mController;
private final KeyguardMonitor mKeyguard;
private final Callback mCallback = new Callback();
@@ -55,6 +60,8 @@ public class LocationTile extends QSTile<QSTile.BooleanState> {
protected void handleClick() {
final boolean wasEnabled = (Boolean) mState.value;
mController.setLocationEnabled(!wasEnabled);
+ mEnable.setAllowAnimation(true);
+ mDisable.setAllowAnimation(true);
}
@Override
@@ -67,12 +74,12 @@ public class LocationTile extends QSTile<QSTile.BooleanState> {
state.visible = !mKeyguard.isShowing();
state.value = locationEnabled;
if (locationEnabled) {
- state.iconId = R.drawable.ic_qs_location_on;
+ state.icon = mEnable;
state.label = mContext.getString(R.string.quick_settings_location_label);
state.contentDescription = mContext.getString(
R.string.accessibility_quick_settings_location_on);
} else {
- state.iconId = R.drawable.ic_qs_location_off;
+ state.icon = mDisable;
state.label = mContext.getString(R.string.quick_settings_location_label);
state.contentDescription = mContext.getString(
R.string.accessibility_quick_settings_location_off);
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 ae40a4d..f46b9a6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
@@ -17,7 +17,6 @@
package com.android.systemui.qs.tiles;
import android.content.res.Configuration;
-import android.content.res.Resources;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
@@ -26,6 +25,15 @@ import com.android.systemui.statusbar.policy.RotationLockController.RotationLock
/** Quick settings tile: Rotation **/
public class RotationLockTile extends QSTile<QSTile.BooleanState> {
+ private final AnimationIcon mPortraitToAuto
+ = new AnimationIcon(R.drawable.ic_portrait_to_auto_rotate_animation);
+ private final AnimationIcon mAutoToPortrait
+ = new AnimationIcon(R.drawable.ic_portrait_from_auto_rotate_animation);
+
+ private final AnimationIcon mLandscapeToAuto
+ = new AnimationIcon(R.drawable.ic_landscape_to_auto_rotate_animation);
+ private final AnimationIcon mAutoToLandscape
+ = new AnimationIcon(R.drawable.ic_landscape_from_auto_rotate_animation);
private final RotationLockController mController;
@@ -51,30 +59,34 @@ public class RotationLockTile extends QSTile<QSTile.BooleanState> {
@Override
protected void handleClick() {
if (mController == null) return;
- mController.setRotationLocked(!mState.value);
+ final boolean newState = !mState.value;
+ mController.setRotationLocked(newState);
+ refreshState(newState ? UserBoolean.USER_TRUE : UserBoolean.USER_FALSE);
}
@Override
protected void handleUpdateState(BooleanState state, Object arg) {
if (mController == null) return;
- final boolean rotationLocked = mController.isRotationLocked();
+ final boolean rotationLocked = arg != null ? ((UserBoolean) arg).value
+ : mController.isRotationLocked();
+ final boolean userInitiated = arg != null ? ((UserBoolean) arg).userInitiated : false;
state.visible = mController.isRotationLockAffordanceVisible();
- final Resources res = mContext.getResources();
state.value = rotationLocked;
+ final boolean portrait = mContext.getResources().getConfiguration().orientation
+ != Configuration.ORIENTATION_LANDSCAPE;
+ final AnimationIcon icon;
if (rotationLocked) {
- final boolean portrait = res.getConfiguration().orientation
- != Configuration.ORIENTATION_LANDSCAPE;
final int label = portrait ? R.string.quick_settings_rotation_locked_portrait_label
: R.string.quick_settings_rotation_locked_landscape_label;
- final int icon = portrait ? R.drawable.ic_qs_rotation_portrait
- : R.drawable.ic_qs_rotation_landscape;
state.label = mContext.getString(label);
- state.icon = mContext.getDrawable(icon);
+ icon = portrait ? mAutoToPortrait : mAutoToLandscape;
} else {
state.label = mContext.getString(R.string.quick_settings_rotation_unlocked_label);
- state.icon = res.getDrawable(R.drawable.ic_qs_rotation_unlocked);
+ icon = portrait ? mPortraitToAuto : mLandscapeToAuto;
}
- state.contentDescription = getAccessibilityString(
+ icon.setAllowAnimation(userInitiated);
+ state.icon = icon;
+ state.contentDescription = getAccessibilityString(rotationLocked,
R.string.accessibility_rotation_lock_on_portrait,
R.string.accessibility_rotation_lock_on_landscape,
R.string.accessibility_rotation_lock_off);
@@ -83,14 +95,16 @@ public class RotationLockTile extends QSTile<QSTile.BooleanState> {
/**
* Get the correct accessibility string based on the state
*
+ * @param locked Whether or not rotation is locked.
* @param idWhenPortrait The id which should be used when locked in portrait.
* @param idWhenLandscape The id which should be used when locked in landscape.
* @param idWhenOff The id which should be used when the rotation lock is off.
* @return
*/
- private String getAccessibilityString(int idWhenPortrait, int idWhenLandscape, int idWhenOff) {
+ private String getAccessibilityString(boolean locked, int idWhenPortrait, int idWhenLandscape,
+ int idWhenOff) {
int stringID;
- if (mState.value) {
+ if (locked) {
final boolean portrait = mContext.getResources().getConfiguration().orientation
!= Configuration.ORIENTATION_LANDSCAPE;
stringID = portrait ? idWhenPortrait: idWhenLandscape;
@@ -102,7 +116,7 @@ public class RotationLockTile extends QSTile<QSTile.BooleanState> {
@Override
protected String composeChangeAnnouncement() {
- return getAccessibilityString(
+ return getAccessibilityString(mState.value,
R.string.accessibility_rotation_lock_on_portrait_changed,
R.string.accessibility_rotation_lock_on_landscape_changed,
R.string.accessibility_rotation_lock_off_changed);
@@ -111,7 +125,8 @@ public class RotationLockTile extends QSTile<QSTile.BooleanState> {
private final RotationLockControllerCallback mCallback = new RotationLockControllerCallback() {
@Override
public void onRotationLockStateChanged(boolean rotationLocked, boolean affordanceVisible) {
- refreshState();
+ refreshState(rotationLocked ? UserBoolean.BACKGROUND_TRUE
+ : UserBoolean.BACKGROUND_FALSE);
}
};
}
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 c524edc..6bad652 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
@@ -47,6 +47,10 @@ public class UserDetailView extends PseudoGridView {
ViewGroupAdapterBridge.link(this, mAdapter);
}
+ public void refreshAdapter() {
+ mAdapter.refresh();
+ }
+
public static class Adapter extends UserSwitcherController.BaseUserAdapter
implements OnClickListener {
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 0985812..e09024b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -30,8 +30,10 @@ 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.AccessPoint;
+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;
/** Quick settings tile: Wifi **/
@@ -39,12 +41,14 @@ public class WifiTile extends QSTile<QSTile.SignalState> {
private static final Intent WIFI_SETTINGS = new Intent(Settings.ACTION_WIFI_SETTINGS);
private final NetworkController mController;
+ private final AccessPointController mWifiController;
private final WifiDetailAdapter mDetailAdapter;
private final QSTile.SignalState mStateBeforeClick = newTileState();
public WifiTile(Host host) {
super(host);
mController = host.getNetworkController();
+ mWifiController = mController.getAccessPointController();
mDetailAdapter = new WifiDetailAdapter();
}
@@ -62,10 +66,10 @@ public class WifiTile extends QSTile<QSTile.SignalState> {
public void setListening(boolean listening) {
if (listening) {
mController.addNetworkSignalChangedCallback(mCallback);
- mController.addAccessPointCallback(mDetailAdapter);
+ mWifiController.addAccessPointCallback(mDetailAdapter);
} else {
mController.removeNetworkSignalChangedCallback(mCallback);
- mController.removeAccessPointCallback(mDetailAdapter);
+ mWifiController.removeAccessPointCallback(mDetailAdapter);
}
}
@@ -87,7 +91,15 @@ public class WifiTile extends QSTile<QSTile.SignalState> {
@Override
protected void handleSecondaryClick() {
- mHost.startSettingsActivity(WIFI_SETTINGS);
+ if (!mWifiController.canConfigWifi()) {
+ mHost.startSettingsActivity(new Intent(Settings.ACTION_WIFI_SETTINGS));
+ return;
+ }
+ if (!mState.enabled) {
+ mController.setWifiEnabled(true);
+ mState.enabled = true;
+ }
+ showDetail(true);
}
@Override
@@ -112,19 +124,19 @@ public class WifiTile extends QSTile<QSTile.SignalState> {
final String signalContentDescription;
final Resources r = mContext.getResources();
if (!state.enabled) {
- state.iconId = R.drawable.ic_qs_wifi_disabled;
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_wifi_disabled);
state.label = r.getString(R.string.quick_settings_wifi_label);
signalContentDescription = r.getString(R.string.accessibility_wifi_off);
} else if (wifiConnected) {
- state.iconId = cb.wifiSignalIconId;
+ state.icon = ResourceIcon.get(cb.wifiSignalIconId);
state.label = removeDoubleQuotes(cb.enabledDesc);
signalContentDescription = cb.wifiSignalContentDescription;
} else if (wifiNotConnected) {
- state.iconId = R.drawable.ic_qs_wifi_0;
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_wifi_0);
state.label = r.getString(R.string.quick_settings_wifi_label);
signalContentDescription = r.getString(R.string.accessibility_no_wifi);
} else {
- state.iconId = R.drawable.ic_qs_wifi_no_network;
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_wifi_no_network);
state.label = r.getString(R.string.quick_settings_wifi_label);
signalContentDescription = r.getString(R.string.accessibility_wifi_off);
}
@@ -206,11 +218,15 @@ public class WifiTile extends QSTile<QSTile.SignalState> {
int mobileSignalIconId,
String mobileSignalContentDescriptionId, int dataTypeIconId,
boolean activityIn, boolean activityOut,
- String dataTypeContentDescriptionId, String description, boolean noSim,
+ String dataTypeContentDescriptionId, String description,
boolean isDataTypeIconWide) {
// noop
}
+ public void onNoSimVisibleChanged(boolean noSims) {
+ // noop
+ }
+
@Override
public void onAirplaneModeChanged(boolean enabled) {
// noop
@@ -223,7 +239,7 @@ public class WifiTile extends QSTile<QSTile.SignalState> {
};
private final class WifiDetailAdapter implements DetailAdapter,
- NetworkController.AccessPointCallback, QSDetailItems.Callback {
+ NetworkController.AccessPointController.AccessPointCallback, QSDetailItems.Callback {
private QSDetailItems mItems;
private AccessPoint[] mAccessPoints;
@@ -253,7 +269,7 @@ public class WifiTile extends QSTile<QSTile.SignalState> {
public View createDetailView(Context context, View convertView, ViewGroup parent) {
if (DEBUG) Log.d(TAG, "createDetailView convertView=" + (convertView != null));
mAccessPoints = null;
- mController.scanForAccessPoints();
+ mWifiController.scanForAccessPoints();
fireScanStateChanged(true);
mItems = QSDetailItems.convertOrInflate(context, convertView, parent);
mItems.setTagSuffix("Wifi");
@@ -275,11 +291,18 @@ public class WifiTile extends QSTile<QSTile.SignalState> {
}
@Override
+ public void onSettingsActivityTriggered(Intent settingsIntent) {
+ mHost.startSettingsActivity(settingsIntent);
+ }
+
+ @Override
public void onDetailItemClick(Item item) {
if (item == null || item.tag == null) return;
final AccessPoint ap = (AccessPoint) item.tag;
if (!ap.isConnected) {
- mController.connect(ap);
+ if (mWifiController.connect(ap)) {
+ mHost.collapsePanels();
+ }
}
showDetail(false);
}
@@ -306,8 +329,15 @@ public class WifiTile extends QSTile<QSTile.SignalState> {
item.icon = ap.iconId;
item.line1 = ap.ssid;
if (ap.isConnected) {
- item.line2 = mContext.getString(R.string.quick_settings_connected);
+ 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
+ ? mContext.getDrawable(R.drawable.qs_ic_wifi_lock)
+ : null;
items[i] = item;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recent/Recents.java b/packages/SystemUI/src/com/android/systemui/recent/Recents.java
index 9a55590..e9f3cf9 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/Recents.java
@@ -18,6 +18,7 @@ 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;
@@ -44,16 +45,29 @@ public class Recents extends SystemUI implements RecentsComponent {
// Which recents to use
boolean mUseAlternateRecents = true;
- AlternateRecentsComponent mAlternateRecents;
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 (mAlternateRecents == null) {
- mAlternateRecents = new AlternateRecentsComponent(mContext);
+ if (sAlternateRecents == null) {
+ sAlternateRecents = getRecentsComponent(mContext, false);
}
- mAlternateRecents.onStart();
+ sAlternateRecents.onStart();
}
putComponent(RecentsComponent.class, this);
@@ -62,8 +76,8 @@ public class Recents extends SystemUI implements RecentsComponent {
@Override
protected void onBootCompleted() {
if (mUseAlternateRecents) {
- if (mAlternateRecents != null) {
- mAlternateRecents.onBootCompleted();
+ if (sAlternateRecents != null) {
+ sAlternateRecents.onBootCompleted();
}
}
mBootCompleted = true;
@@ -72,14 +86,14 @@ public class Recents extends SystemUI implements RecentsComponent {
@Override
public void showRecents(boolean triggeredFromAltTab, View statusBarView) {
if (mUseAlternateRecents) {
- mAlternateRecents.onShowRecents(triggeredFromAltTab, statusBarView);
+ sAlternateRecents.onShowRecents(triggeredFromAltTab);
}
}
@Override
public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
if (mUseAlternateRecents) {
- mAlternateRecents.onHideRecents(triggeredFromAltTab, triggeredFromHomeKey);
+ sAlternateRecents.onHideRecents(triggeredFromAltTab, triggeredFromHomeKey);
} else {
Intent intent = new Intent(RecentsActivity.CLOSE_RECENTS_INTENT);
intent.setPackage("com.android.systemui");
@@ -93,7 +107,7 @@ public class Recents extends SystemUI implements RecentsComponent {
public void toggleRecents(Display display, int layoutDirection, View statusBarView) {
if (mUseAlternateRecents) {
// Launch the alternate recents if required
- mAlternateRecents.onToggleRecents(statusBarView);
+ sAlternateRecents.onToggleRecents();
return;
}
@@ -241,14 +255,14 @@ public class Recents extends SystemUI implements RecentsComponent {
@Override
protected void onConfigurationChanged(Configuration newConfig) {
if (mUseAlternateRecents) {
- mAlternateRecents.onConfigurationChanged(newConfig);
+ sAlternateRecents.onConfigurationChanged(newConfig);
}
}
@Override
public void preloadRecents() {
if (mUseAlternateRecents) {
- mAlternateRecents.onPreloadRecents();
+ sAlternateRecents.onPreloadRecents();
} else {
Intent intent = new Intent(RecentsActivity.PRELOAD_INTENT);
intent.setClassName("com.android.systemui",
@@ -262,7 +276,7 @@ public class Recents extends SystemUI implements RecentsComponent {
@Override
public void cancelPreloadingRecents() {
if (mUseAlternateRecents) {
- mAlternateRecents.onCancelPreloadingRecents();
+ sAlternateRecents.onCancelPreloadingRecents();
} else {
Intent intent = new Intent(RecentsActivity.CANCEL_PRELOAD_INTENT);
intent.setClassName("com.android.systemui",
@@ -276,21 +290,21 @@ public class Recents extends SystemUI implements RecentsComponent {
@Override
public void showNextAffiliatedTask() {
if (mUseAlternateRecents) {
- mAlternateRecents.onShowNextAffiliatedTask();
+ sAlternateRecents.onShowNextAffiliatedTask();
}
}
@Override
public void showPrevAffiliatedTask() {
if (mUseAlternateRecents) {
- mAlternateRecents.onShowPrevAffiliatedTask();
+ sAlternateRecents.onShowPrevAffiliatedTask();
}
}
@Override
public void setCallback(Callbacks cb) {
if (mUseAlternateRecents) {
- mAlternateRecents.setRecentsComponentCallback(cb);
+ sAlternateRecents.setRecentsComponentCallback(cb);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
index 2a782cc..4c3460e 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
@@ -728,7 +728,7 @@ public class RecentsPanelView extends FrameLayout implements OnItemClickListener
final ActivityManager am = (ActivityManager)
getContext().getSystemService(Context.ACTIVITY_SERVICE);
if (am != null) {
- am.removeTask(ad.persistentTaskId, ActivityManager.REMOVE_TASK_KILL_PROCESS);
+ am.removeTask(ad.persistentTaskId);
// Accessibility feedback
setContentDescription(
diff --git a/packages/SystemUI/src/com/android/systemui/recent/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recent/ScreenPinningRequest.java
new file mode 100644
index 0000000..2fa0b58
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recent/ScreenPinningRequest.java
@@ -0,0 +1,283 @@
+/*
+ * 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.recent;
+
+import android.animation.ArgbEvaluator;
+import android.animation.ValueAnimator;
+import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Configuration;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.ColorDrawable;
+import android.os.RemoteException;
+import android.util.DisplayMetrics;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+import com.android.systemui.recents.model.RecentsTaskLoader;
+
+import java.util.ArrayList;
+
+public class ScreenPinningRequest implements View.OnClickListener {
+ private final Context mContext;
+
+ private final AccessibilityManager mAccessibilityService;
+ private final WindowManager mWindowManager;
+
+ private RequestWindowView mRequestWindow;
+
+ public ScreenPinningRequest(Context context) {
+ mContext = context;
+ mAccessibilityService = (AccessibilityManager)
+ mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
+ mWindowManager = (WindowManager)
+ mContext.getSystemService(Context.WINDOW_SERVICE);
+ }
+
+ public void clearPrompt() {
+ if (mRequestWindow != null) {
+ mWindowManager.removeView(mRequestWindow);
+ mRequestWindow = null;
+ }
+ }
+
+ public void showPrompt(boolean allowCancel) {
+ clearPrompt();
+
+ mRequestWindow = new RequestWindowView(mContext, allowCancel);
+
+ mRequestWindow.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+
+ // show the confirmation
+ WindowManager.LayoutParams lp = getWindowLayoutParams();
+ mWindowManager.addView(mRequestWindow, lp);
+ }
+
+ public void onConfigurationChanged() {
+ if (mRequestWindow != null) {
+ mRequestWindow.onConfigurationChanged();
+ }
+ }
+
+ private WindowManager.LayoutParams getWindowLayoutParams() {
+ final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
+ 0
+ | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
+ ,
+ PixelFormat.TRANSLUCENT);
+ lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ lp.setTitle("ScreenPinningConfirmation");
+ lp.gravity = Gravity.FILL;
+ return lp;
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (v.getId() == R.id.screen_pinning_ok_button || mRequestWindow == v) {
+ try {
+ ActivityManagerNative.getDefault().startLockTaskModeOnCurrent();
+ } catch (RemoteException e) {}
+ }
+ clearPrompt();
+ }
+
+ public FrameLayout.LayoutParams getRequestLayoutParams(boolean isLandscape) {
+ return new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ isLandscape ? (Gravity.CENTER_VERTICAL | Gravity.RIGHT)
+ : (Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM));
+ }
+
+ private class RequestWindowView extends FrameLayout {
+ private static final int OFFSET_DP = 96;
+
+ private final ColorDrawable mColor = new ColorDrawable(0);
+ private ValueAnimator mColorAnim;
+ private ViewGroup mLayout;
+ private boolean mShowCancel;
+
+ public RequestWindowView(Context context, boolean showCancel) {
+ super(context);
+ setClickable(true);
+ setOnClickListener(ScreenPinningRequest.this);
+ setBackground(mColor);
+ mShowCancel = showCancel;
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ DisplayMetrics metrics = new DisplayMetrics();
+ mWindowManager.getDefaultDisplay().getMetrics(metrics);
+ float density = metrics.density;
+ boolean isLandscape = isLandscapePhone(mContext);
+
+ inflateView(isLandscape);
+ int bgColor = mContext.getResources().getColor(
+ R.color.screen_pinning_request_window_bg);
+ if (ActivityManager.isHighEndGfx()) {
+ mLayout.setAlpha(0f);
+ if (isLandscape) {
+ mLayout.setTranslationX(OFFSET_DP * density);
+ } else {
+ mLayout.setTranslationY(OFFSET_DP * density);
+ }
+ mLayout.animate()
+ .alpha(1f)
+ .translationX(0)
+ .translationY(0)
+ .setDuration(300)
+ .setInterpolator(new DecelerateInterpolator())
+ .start();
+
+ mColorAnim = ValueAnimator.ofObject(new ArgbEvaluator(), 0, bgColor);
+ mColorAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ final int c = (Integer) animation.getAnimatedValue();
+ mColor.setColor(c);
+ }
+ });
+ mColorAnim.setDuration(1000);
+ mColorAnim.start();
+ } else {
+ mColor.setColor(bgColor);
+ }
+
+ IntentFilter filter = new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED);
+ filter.addAction(Intent.ACTION_USER_SWITCHED);
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
+ mContext.registerReceiver(mReceiver, filter);
+ }
+
+ private boolean isLandscapePhone(Context context) {
+ Configuration config = mContext.getResources().getConfiguration();
+ return config.orientation == Configuration.ORIENTATION_LANDSCAPE
+ && config.smallestScreenWidthDp < 600;
+ }
+
+ private void inflateView(boolean isLandscape) {
+ // We only want this landscape orientation on <600dp, so rather than handle
+ // resource overlay for -land and -sw600dp-land, just inflate this
+ // other view for this single case.
+ mLayout = (ViewGroup) View.inflate(getContext(), isLandscape
+ ? R.layout.screen_pinning_request_land_phone : R.layout.screen_pinning_request,
+ null);
+ // Catch touches so they don't trigger cancel/activate, like outside does.
+ mLayout.setClickable(true);
+ // Status bar is always on the right.
+ mLayout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR);
+ // Buttons and text do switch sides though.
+ View buttons = mLayout.findViewById(R.id.screen_pinning_buttons);
+ buttons.setLayoutDirection(View.LAYOUT_DIRECTION_LOCALE);
+ mLayout.findViewById(R.id.screen_pinning_text_area)
+ .setLayoutDirection(View.LAYOUT_DIRECTION_LOCALE);
+ swapChildrenIfRtlAndVertical(buttons);
+
+ ((Button) mLayout.findViewById(R.id.screen_pinning_ok_button))
+ .setOnClickListener(ScreenPinningRequest.this);
+ if (mShowCancel) {
+ ((Button) mLayout.findViewById(R.id.screen_pinning_cancel_button))
+ .setOnClickListener(ScreenPinningRequest.this);
+ } else {
+ ((Button) mLayout.findViewById(R.id.screen_pinning_cancel_button))
+ .setVisibility(View.INVISIBLE);
+ }
+
+ final int description = mAccessibilityService.isEnabled()
+ ? R.string.screen_pinning_description_accessible
+ : R.string.screen_pinning_description;
+ ((TextView) mLayout.findViewById(R.id.screen_pinning_description))
+ .setText(description);
+ final int backBgVisibility =
+ mAccessibilityService.isEnabled() ? View.INVISIBLE : View.VISIBLE;
+ mLayout.findViewById(R.id.screen_pinning_back_bg).setVisibility(backBgVisibility);
+ mLayout.findViewById(R.id.screen_pinning_back_bg_light).setVisibility(backBgVisibility);
+
+ addView(mLayout, getRequestLayoutParams(isLandscape));
+ }
+
+ private void swapChildrenIfRtlAndVertical(View group) {
+ if (mContext.getResources().getConfiguration().getLayoutDirection()
+ != View.LAYOUT_DIRECTION_RTL) {
+ return;
+ }
+ LinearLayout linearLayout = (LinearLayout) group;
+ if (linearLayout.getOrientation() == LinearLayout.VERTICAL) {
+ int childCount = linearLayout.getChildCount();
+ ArrayList<View> childList = new ArrayList<>(childCount);
+ for (int i = 0; i < childCount; i++) {
+ childList.add(linearLayout.getChildAt(i));
+ }
+ linearLayout.removeAllViews();
+ for (int i = childCount - 1; i >= 0; i--) {
+ linearLayout.addView(childList.get(i));
+ }
+ }
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ mContext.unregisterReceiver(mReceiver);
+ }
+
+ protected void onConfigurationChanged() {
+ removeAllViews();
+ inflateView(isLandscapePhone(mContext));
+ }
+
+ private final Runnable mUpdateLayoutRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (mLayout != null && mLayout.getParent() != null) {
+ mLayout.setLayoutParams(getRequestLayoutParams(isLandscapePhone(mContext)));
+ }
+ }
+ };
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
+ post(mUpdateLayoutRunnable);
+ } else if (intent.getAction().equals(Intent.ACTION_USER_SWITCHED)
+ || intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
+ clearPrompt();
+ }
+ }
+ };
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
index 1283dcd..910a57e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
@@ -19,6 +19,7 @@ package com.android.systemui.recents;
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;
@@ -26,12 +27,14 @@ import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.os.Handler;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.util.Pair;
import android.view.LayoutInflater;
@@ -40,6 +43,7 @@ import com.android.systemui.R;
import com.android.systemui.RecentsComponent;
import com.android.systemui.recents.misc.Console;
import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.recents.model.RecentsTaskLoadPlan;
import com.android.systemui.recents.model.RecentsTaskLoader;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskGrouping;
@@ -53,16 +57,27 @@ 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
+ * proxied to the current user.
+ */
+@interface ProxyFromPrimaryToCurrentUser {}
+/**
+ * Annotation for a method that may be called from any user's SystemUI process and will be proxied
+ * to the primary user.
+ */
+@interface ProxyFromAnyToPrimaryUser {}
+
/** A proxy implementation for the recents component */
public class AlternateRecentsComponent implements ActivityOptions.OnAnimationStartedListener {
- final public static String EXTRA_FROM_HOME = "recents.triggeredOverHome";
- final public static String EXTRA_FROM_SEARCH_HOME = "recents.triggeredOverSearchHome";
- final public static String EXTRA_FROM_APP_THUMBNAIL = "recents.animatingWithThumbnail";
- final public static String EXTRA_FROM_APP_FULL_SCREENSHOT = "recents.thumbnail";
- final public static String EXTRA_FROM_TASK_ID = "recents.activeTaskId";
- final public static String EXTRA_TRIGGERED_FROM_ALT_TAB = "recents.triggeredFromAltTab";
- final public static String EXTRA_TRIGGERED_FROM_HOME_KEY = "recents.triggeredFromHomeKey";
+ final public static String EXTRA_TRIGGERED_FROM_ALT_TAB = "triggeredFromAltTab";
+ final public static String EXTRA_TRIGGERED_FROM_HOME_KEY = "triggeredFromHomeKey";
+ final public static String EXTRA_RECENTS_VISIBILITY = "recentsVisibility";
+
+ // Owner proxy events
+ final public static String ACTION_PROXY_NOTIFY_RECENTS_VISIBLITY_TO_OWNER =
+ "action_notify_recents_visibility_change";
final public static String ACTION_START_ENTER_ANIMATION = "action_start_enter_animation";
final public static String ACTION_TOGGLE_RECENTS_ACTIVITY = "action_toggle_recents_activity";
@@ -71,18 +86,80 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
final static int sMinToggleDelay = 350;
final static String sToggleRecentsAction = "com.android.systemui.recents.SHOW_RECENTS";
- final static String sRecentsPackage = "com.android.systemui";
- final static String sRecentsActivity = "com.android.systemui.recents.RecentsActivity";
+ public final static String sRecentsPackage = "com.android.systemui";
+ public final static String sRecentsActivity = "com.android.systemui.recents.RecentsActivity";
+
+ /**
+ * An implementation of ITaskStackListener, that allows us to listen for changes to the system
+ * task stacks and update recents accordingly.
+ */
+ class TaskStackListenerImpl extends ITaskStackListener.Stub implements Runnable {
+ Handler mHandler;
+
+ public TaskStackListenerImpl(Handler handler) {
+ mHandler = handler;
+ }
+
+ @Override
+ public void onTaskStackChanged() {
+ // Debounce any task stack changes
+ mHandler.removeCallbacks(this);
+ mHandler.post(this);
+ }
+
+ /** Preloads the next task */
+ public void run() {
+ RecentsConfiguration config = RecentsConfiguration.getInstance();
+ if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
+ RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
+ SystemServicesProxy ssp = loader.getSystemServicesProxy();
+ ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getTopMostTask();
+
+ // Load the next task only if we aren't svelte
+ RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
+ loader.preloadTasks(plan, true /* isTopTaskHome */);
+ RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
+ // This callback is made when a new activity is launched and the old one is paused
+ // so ignore the current activity and try and preload the thumbnail for the
+ // previous one.
+ if (runningTaskInfo != null) {
+ launchOpts.runningTaskId = runningTaskInfo.id;
+ }
+ launchOpts.numVisibleTasks = 2;
+ launchOpts.numVisibleTaskThumbnails = 2;
+ launchOpts.onlyLoadForCache = true;
+ launchOpts.onlyLoadPausedActivities = true;
+ loader.loadTasks(mContext, plan, launchOpts);
+ }
+ }
+ }
+
+ /**
+ * A proxy for Recents events which happens strictly for the owner.
+ */
+ class RecentsOwnerEventProxyReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ switch (intent.getAction()) {
+ case ACTION_PROXY_NOTIFY_RECENTS_VISIBLITY_TO_OWNER:
+ visibilityChanged(intent.getBooleanExtra(EXTRA_RECENTS_VISIBILITY, false));
+ break;
+ }
+ }
+ }
- static Bitmap sLastScreenshot;
static RecentsComponent.Callbacks sRecentsComponentCallbacks;
+ static RecentsTaskLoadPlan sInstanceLoadPlan;
Context mContext;
LayoutInflater mInflater;
SystemServicesProxy mSystemServicesProxy;
Handler mHandler;
+ TaskStackListenerImpl mTaskStackListener;
+ RecentsOwnerEventProxyReceiver mProxyBroadcastReceiver;
boolean mBootCompleted;
boolean mStartAnimationTriggered;
+ boolean mCanReuseTaskStackViews = true;
// Task launching
RecentsConfiguration mConfig;
@@ -99,7 +176,6 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
TaskStackView mDummyStackView;
// Variables to keep track of if we need to start recents after binding
- View mStatusBarView;
boolean mTriggeredFromAltTab;
long mLastToggleTime;
@@ -110,37 +186,69 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
mSystemServicesProxy = new SystemServicesProxy(context);
mHandler = new Handler();
mTaskStackBounds = new Rect();
+
+ // Register the task stack listener
+ mTaskStackListener = new TaskStackListenerImpl(mHandler);
+ mSystemServicesProxy.registerTaskStackListener(mTaskStackListener);
+
+ // Only the owner has the callback to update the SysUI visibility flags, so all non-owner
+ // instances of AlternateRecentsComponent needs to notify the owner when the visibility
+ // changes.
+ if (mSystemServicesProxy.isForegroundUserOwner()) {
+ mProxyBroadcastReceiver = new RecentsOwnerEventProxyReceiver();
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(AlternateRecentsComponent.ACTION_PROXY_NOTIFY_RECENTS_VISIBLITY_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();
- // 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(mContext,
- Constants.Values.App.AppWidgetHostId);
- Pair<Integer, AppWidgetProviderInfo> widgetInfo =
- mSystemServicesProxy.bindSearchAppWidget(host);
- if (widgetInfo != null) {
- // Save the app widget id into the settings
- mConfig.updateSearchBarAppWidgetId(mContext, widgetInfo.first);
- }
- }
- }
+ reloadHeaderBarLayout(true);
+
+ // When we start, preload the data associated with the previous recent tasks.
+ // We can use a new plan since the caches will be the same.
+ RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
+ RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
+ loader.preloadTasks(plan, true /* isTopTaskHome */);
+ RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
+ launchOpts.numVisibleTasks = loader.getApplicationIconCacheSize();
+ launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize();
+ launchOpts.onlyLoadForCache = true;
+ loader.loadTasks(mContext, plan, launchOpts);
}
public void onBootCompleted() {
mBootCompleted = true;
}
- /** Shows the recents */
- public void onShowRecents(boolean triggeredFromAltTab, View statusBarView) {
- mStatusBarView = statusBarView;
+ /** Shows the Recents. */
+ @ProxyFromPrimaryToCurrentUser
+ public void onShowRecents(boolean triggeredFromAltTab) {
+ if (mSystemServicesProxy.isForegroundUserOwner()) {
+ showRecents(triggeredFromAltTab);
+ } else {
+ Intent intent = createLocalBroadcastIntent(mContext,
+ RecentsUserEventProxyReceiver.ACTION_PROXY_SHOW_RECENTS_TO_USER);
+ intent.putExtra(EXTRA_TRIGGERED_FROM_ALT_TAB, triggeredFromAltTab);
+ mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
+ }
+ }
+ void showRecents(boolean triggeredFromAltTab) {
mTriggeredFromAltTab = triggeredFromAltTab;
try {
@@ -150,15 +258,25 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
}
}
- /** Hides the recents */
+ /** Hides the Recents. */
+ @ProxyFromPrimaryToCurrentUser
public void onHideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
+ if (mSystemServicesProxy.isForegroundUserOwner()) {
+ hideRecents(triggeredFromAltTab, triggeredFromHomeKey);
+ } else {
+ Intent intent = createLocalBroadcastIntent(mContext,
+ RecentsUserEventProxyReceiver.ACTION_PROXY_HIDE_RECENTS_TO_USER);
+ intent.putExtra(EXTRA_TRIGGERED_FROM_ALT_TAB, triggeredFromAltTab);
+ intent.putExtra(EXTRA_TRIGGERED_FROM_HOME_KEY, triggeredFromHomeKey);
+ mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
+ }
+ }
+ void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
if (mBootCompleted) {
- if (isRecentsTopMost(getTopMostTask(), null)) {
+ ActivityManager.RunningTaskInfo topTask = mSystemServicesProxy.getTopMostTask();
+ if (topTask != null && mSystemServicesProxy.isRecentsTopMost(topTask, null)) {
// Notify recents to hide itself
- Intent intent = new Intent(ACTION_HIDE_RECENTS_ACTIVITY);
- intent.setPackage(mContext.getPackageName());
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT |
- Intent.FLAG_RECEIVER_FOREGROUND);
+ 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);
@@ -166,9 +284,18 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
}
}
- /** Toggles the alternate recents activity */
- public void onToggleRecents(View statusBarView) {
- mStatusBarView = statusBarView;
+ /** Toggles the Recents activity. */
+ @ProxyFromPrimaryToCurrentUser
+ public void onToggleRecents() {
+ if (mSystemServicesProxy.isForegroundUserOwner()) {
+ toggleRecents();
+ } else {
+ Intent intent = createLocalBroadcastIntent(mContext,
+ RecentsUserEventProxyReceiver.ACTION_PROXY_TOGGLE_RECENTS_TO_USER);
+ mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
+ }
+ }
+ void toggleRecents() {
mTriggeredFromAltTab = false;
try {
@@ -178,8 +305,23 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
}
}
+ /** Preloads info for the Recents activity. */
+ @ProxyFromPrimaryToCurrentUser
public void onPreloadRecents() {
- // Do nothing
+ if (mSystemServicesProxy.isForegroundUserOwner()) {
+ preloadRecents();
+ } else {
+ Intent intent = createLocalBroadcastIntent(mContext,
+ RecentsUserEventProxyReceiver.ACTION_PROXY_PRELOAD_RECENTS_TO_USER);
+ mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
+ }
+ }
+ void preloadRecents() {
+ // Preload only the raw task list into a new load plan (which will be consumed by the
+ // RecentsActivity)
+ RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
+ sInstanceLoadPlan = loader.createLoadPlan(mContext);
+ sInstanceLoadPlan.preloadRawTasks(true);
}
public void onCancelPreloadingRecents() {
@@ -188,12 +330,16 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
void showRelativeAffiliatedTask(boolean showNextTask) {
RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
- TaskStack stack = loader.getTaskStack(mSystemServicesProxy, mContext.getResources(),
- -1, -1, false, true, null, null);
+ RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
+ loader.preloadTasks(plan, true /* isTopTaskHome */);
+ TaskStack stack = plan.getTaskStack();
+
// Return early if there are no tasks
if (stack.getTaskCount() == 0) return;
- ActivityManager.RunningTaskInfo runningTask = getTopMostTask();
+ ActivityManager.RunningTaskInfo runningTask = mSystemServicesProxy.getTopMostTask();
+ // Return early if there is no running task (can't determine affiliated tasks in this case)
+ if (runningTask == null) return;
// Return early if the running task is in the home stack (optimization)
if (mSystemServicesProxy.isInHomeStack(runningTask.id)) return;
@@ -202,6 +348,7 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
Task toTask = null;
ActivityOptions launchOpts = null;
int taskCount = tasks.size();
+ int numAffiliatedTasks = 0;
for (int i = 0; i < taskCount; i++) {
Task task = tasks.get(i);
if (task.key.id == runningTask.id) {
@@ -221,16 +368,23 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
if (toTaskKey != null) {
toTask = stack.findTaskWithId(toTaskKey.id);
}
+ numAffiliatedTasks = group.getTaskCount();
break;
}
}
// Return early if there is no next task
if (toTask == null) {
- if (showNextTask) {
- // XXX: Show the next-task bounce animation
- } else {
- // XXX: Show the prev-task bounce animation
+ if (numAffiliatedTasks > 1) {
+ if (showNextTask) {
+ mSystemServicesProxy.startInPlaceAnimationOnFrontMostApplication(
+ ActivityOptions.makeCustomInPlaceAnimation(mContext,
+ R.anim.recents_launch_next_affiliated_task_bounce));
+ } else {
+ mSystemServicesProxy.startInPlaceAnimationOnFrontMostApplication(
+ ActivityOptions.makeCustomInPlaceAnimation(mContext,
+ R.anim.recents_launch_prev_affiliated_task_bounce));
+ }
}
return;
}
@@ -253,14 +407,26 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
showRelativeAffiliatedTask(false);
}
+ /** Updates on configuration change. */
+ @ProxyFromPrimaryToCurrentUser
public void onConfigurationChanged(Configuration newConfig) {
+ if (mSystemServicesProxy.isForegroundUserOwner()) {
+ configurationChanged();
+ } else {
+ Intent intent = createLocalBroadcastIntent(mContext,
+ RecentsUserEventProxyReceiver.ACTION_PROXY_CONFIG_CHANGE_TO_USER);
+ mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
+ }
+ }
+ void configurationChanged() {
+ // Don't reuse task stack views if the configuration changes
+ mCanReuseTaskStackViews = false;
// Reload the header bar layout
- reloadHeaderBarLayout();
- sLastScreenshot = null;
+ reloadHeaderBarLayout(false);
}
/** Prepares the header bar layout. */
- void reloadHeaderBarLayout() {
+ void reloadHeaderBarLayout(boolean reloadWidget) {
Resources res = mContext.getResources();
mWindowRect = mSystemServicesProxy.getWindowRect();
mStatusBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
@@ -268,6 +434,10 @@ 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);
if (mConfig.isLandscape && mConfig.hasTransposedNavBar) {
@@ -293,36 +463,22 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
mHeaderBar.layout(0, 0, taskViewSize.width(), taskBarHeight);
}
- /** Gets the top task. */
- ActivityManager.RunningTaskInfo getTopMostTask() {
- SystemServicesProxy ssp = mSystemServicesProxy;
- List<ActivityManager.RunningTaskInfo> tasks = ssp.getRunningTasks(1);
- if (!tasks.isEmpty()) {
- return tasks.get(0);
- }
- return null;
- }
-
- /** Returns whether the recents is currently running */
- boolean isRecentsTopMost(ActivityManager.RunningTaskInfo topTask, AtomicBoolean isHomeTopMost) {
- SystemServicesProxy ssp = mSystemServicesProxy;
- if (topTask != null) {
- ComponentName topActivity = topTask.topActivity;
-
- // Check if the front most activity is recents
- if (topActivity.getPackageName().equals(sRecentsPackage) &&
- topActivity.getClassName().equals(sRecentsActivity)) {
- if (isHomeTopMost != null) {
- isHomeTopMost.set(false);
+ /** 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);
}
- return true;
- }
-
- if (isHomeTopMost != null) {
- isHomeTopMost.set(ssp.isInHomeStack(topTask.id));
}
}
- return false;
}
/** Toggles the recents activity */
@@ -330,22 +486,19 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
// If the user has toggled it too quickly, then just eat up the event here (it's better than
// showing a janky screenshot).
// NOTE: Ideally, the screenshot mechanism would take the window transform into account
- if (System.currentTimeMillis() - mLastToggleTime < sMinToggleDelay) {
+ if ((SystemClock.elapsedRealtime() - mLastToggleTime) < sMinToggleDelay) {
return;
}
// 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 = getTopMostTask();
- AtomicBoolean isTopTaskHome = new AtomicBoolean();
- if (isRecentsTopMost(topTask, isTopTaskHome)) {
+ ActivityManager.RunningTaskInfo topTask = mSystemServicesProxy.getTopMostTask();
+ AtomicBoolean isTopTaskHome = new AtomicBoolean(true);
+ if (topTask != null && mSystemServicesProxy.isRecentsTopMost(topTask, isTopTaskHome)) {
// Notify recents to toggle itself
- Intent intent = new Intent(ACTION_TOGGLE_RECENTS_ACTIVITY);
- intent.setPackage(mContext.getPackageName());
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT |
- Intent.FLAG_RECEIVER_FOREGROUND);
+ Intent intent = createLocalBroadcastIntent(mContext, ACTION_TOGGLE_RECENTS_ACTIVITY);
mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
- mLastToggleTime = System.currentTimeMillis();
+ mLastToggleTime = SystemClock.elapsedRealtime();
return;
} else {
// Otherwise, start the recents activity
@@ -356,9 +509,9 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
/** Starts the recents activity if it is not already running */
void startRecentsActivity() {
// Check if the top task is in the home stack, and start the recents activity
- ActivityManager.RunningTaskInfo topTask = getTopMostTask();
- AtomicBoolean isTopTaskHome = new AtomicBoolean();
- if (!isRecentsTopMost(topTask, isTopTaskHome)) {
+ ActivityManager.RunningTaskInfo topTask = mSystemServicesProxy.getTopMostTask();
+ AtomicBoolean isTopTaskHome = new AtomicBoolean(true);
+ if (topTask == null || !mSystemServicesProxy.isRecentsTopMost(topTask, isTopTaskHome)) {
startRecentsActivity(topTask, isTopTaskHome.get());
}
}
@@ -370,7 +523,8 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
mStartAnimationTriggered = false;
return ActivityOptions.makeCustomAnimation(mContext,
R.anim.recents_from_unknown_enter,
- R.anim.recents_from_unknown_exit, mHandler, this);
+ R.anim.recents_from_unknown_exit,
+ mHandler, this);
}
/**
@@ -381,37 +535,24 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
if (fromSearchHome) {
return ActivityOptions.makeCustomAnimation(mContext,
R.anim.recents_from_search_launcher_enter,
- R.anim.recents_from_search_launcher_exit, mHandler, this);
+ R.anim.recents_from_search_launcher_exit,
+ mHandler, this);
}
return ActivityOptions.makeCustomAnimation(mContext,
R.anim.recents_from_launcher_enter,
- R.anim.recents_from_launcher_exit, mHandler, this);
+ R.anim.recents_from_launcher_exit,
+ mHandler, this);
}
/**
- * Creates the activity options for an app->recents transition. If this method sets the static
- * screenshot, then we will use that for the transition.
+ * Creates the activity options for an app->recents transition.
*/
ActivityOptions getThumbnailTransitionActivityOptions(ActivityManager.RunningTaskInfo topTask,
- boolean isTopTaskHome) {
- if (Constants.DebugFlags.App.EnableScreenshotAppTransition) {
- // Recycle the last screenshot
- consumeLastScreenshot();
-
- // Take the full screenshot
- sLastScreenshot = mSystemServicesProxy.takeAppScreenshot();
- if (sLastScreenshot != null) {
- mStartAnimationTriggered = false;
- return ActivityOptions.makeCustomAnimation(mContext,
- R.anim.recents_from_app_enter,
- R.anim.recents_from_app_exit, mHandler, this);
- }
- }
-
+ TaskStack stack, TaskStackView stackView) {
// Update the destination rect
Task toTask = new Task();
- TaskViewTransform toTransform = getThumbnailTransitionTransform(topTask.id, isTopTaskHome,
- toTask);
+ 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);
@@ -429,9 +570,9 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
}
mStartAnimationTriggered = false;
- return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mStatusBarView,
+ return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
thumbnail, toTaskRect.left, toTaskRect.top, toTaskRect.width(),
- toTaskRect.height(), this);
+ toTaskRect.height(), mHandler, this);
}
// If both the screenshot and thumbnail fails, then just fall back to the default transition
@@ -439,16 +580,8 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
}
/** Returns the transition rect for the given task id. */
- TaskViewTransform getThumbnailTransitionTransform(int runningTaskId, boolean isTopTaskHome,
- Task runningTaskOut) {
- // Get the stack of tasks that we are animating into
- RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
- TaskStack stack = loader.getTaskStack(mSystemServicesProxy, mContext.getResources(),
- runningTaskId, -1, false, isTopTaskHome, null, null);
- if (stack.getTaskCount() == 0) {
- return null;
- }
-
+ TaskViewTransform getThumbnailTransitionTransform(TaskStack stack, TaskStackView stackView,
+ int runningTaskId, Task runningTaskOut) {
// Find the running task in the TaskStack
Task task = null;
ArrayList<Task> tasks = stack.getTasks();
@@ -470,34 +603,45 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
}
// Get the transform for the running task
- mDummyStackView.updateMinMaxScrollForStack(stack, mTriggeredFromAltTab, isTopTaskHome);
- mDummyStackView.getScroller().setStackScrollToInitialState();
- mTmpTransform = mDummyStackView.getStackAlgorithm().getStackTransform(task,
- mDummyStackView.getScroller().getStackScroll(), mTmpTransform, null);
+ stackView.getScroller().setStackScrollToInitialState();
+ mTmpTransform = stackView.getStackAlgorithm().getStackTransform(task,
+ stackView.getScroller().getStackScroll(), mTmpTransform, null);
return mTmpTransform;
}
/** Starts the recents activity */
void startRecentsActivity(ActivityManager.RunningTaskInfo topTask, boolean isTopTaskHome) {
- // If Recents is not the front-most activity and we should animate into it. If
- // the activity at the root of the top task stack in the home stack, then we just do a
- // simple transition. Otherwise, we animate to the rects defined by the Recents service,
- // which can differ depending on the number of items in the list.
- SystemServicesProxy ssp = mSystemServicesProxy;
- List<ActivityManager.RecentTaskInfo> recentTasks =
- ssp.getRecentTasks(3, UserHandle.CURRENT.getIdentifier(), isTopTaskHome);
- boolean useThumbnailTransition = !isTopTaskHome;
- boolean hasRecentTasks = !recentTasks.isEmpty();
+ RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
+ RecentsConfiguration.reinitialize(mContext, mSystemServicesProxy);
+
+ if (sInstanceLoadPlan == null) {
+ // Create a new load plan if onPreloadRecents() was never triggered
+ sInstanceLoadPlan = loader.createLoadPlan(mContext);
+ }
+ loader.preloadTasks(sInstanceLoadPlan, isTopTaskHome);
+ TaskStack stack = sInstanceLoadPlan.getTaskStack();
+
+ // Prepare the dummy stack for the transition
+ mDummyStackView.updateMinMaxScrollForStack(stack, mTriggeredFromAltTab, isTopTaskHome);
+ TaskStackViewLayoutAlgorithm.VisibilityReport stackVr =
+ mDummyStackView.computeStackVisibilityReport();
+ boolean hasRecentTasks = stack.getTaskCount() > 0;
+ 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, isTopTaskHome);
+ ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask, stack,
+ mDummyStackView);
if (opts != null) {
- if (sLastScreenshot != null) {
- startAlternateRecentsActivity(topTask, opts, EXTRA_FROM_APP_FULL_SCREENSHOT);
- } else {
- startAlternateRecentsActivity(topTask, opts, EXTRA_FROM_APP_THUMBNAIL);
- }
+ startAlternateRecentsActivity(topTask, opts, false /* fromHome */,
+ false /* fromSearchHome */, true /* fromThumbnail */, stackVr);
} else {
// Fall through below to the non-thumbnail transition
useThumbnailTransition = false;
@@ -530,48 +674,44 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
}
ActivityOptions opts = getHomeTransitionActivityOptions(fromSearchHome);
- startAlternateRecentsActivity(topTask, opts,
- fromSearchHome ? EXTRA_FROM_SEARCH_HOME : EXTRA_FROM_HOME);
+ startAlternateRecentsActivity(topTask, opts, true /* fromHome */, fromSearchHome,
+ false /* fromThumbnail */, stackVr);
} else {
// Otherwise we do the normal fade from an unknown source
ActivityOptions opts = getUnknownTransitionActivityOptions();
- startAlternateRecentsActivity(topTask, opts, null);
+ startAlternateRecentsActivity(topTask, opts, true /* fromHome */,
+ false /* fromSearchHome */, false /* fromThumbnail */, stackVr);
}
}
- mLastToggleTime = System.currentTimeMillis();
+ mLastToggleTime = SystemClock.elapsedRealtime();
}
/** Starts the recents activity */
void startAlternateRecentsActivity(ActivityManager.RunningTaskInfo topTask,
- ActivityOptions opts, String extraFlag) {
+ ActivityOptions opts, boolean fromHome, boolean fromSearchHome, boolean fromThumbnail,
+ TaskStackViewLayoutAlgorithm.VisibilityReport vr) {
+ // Update the configuration based on the launch options
+ mConfig.launchedFromHome = fromSearchHome || fromHome;
+ mConfig.launchedFromSearchHome = fromSearchHome;
+ mConfig.launchedFromAppWithThumbnail = fromThumbnail;
+ mConfig.launchedToTaskId = (topTask != null) ? topTask.id : -1;
+ mConfig.launchedWithAltTab = mTriggeredFromAltTab;
+ mConfig.launchedReuseTaskStackViews = mCanReuseTaskStackViews;
+ mConfig.launchedNumVisibleTasks = vr.numVisibleTasks;
+ mConfig.launchedNumVisibleThumbnails = vr.numVisibleThumbnails;
+ mConfig.launchedHasConfigurationChanged = false;
+
Intent intent = new Intent(sToggleRecentsAction);
intent.setClassName(sRecentsPackage, sRecentsActivity);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
| Intent.FLAG_ACTIVITY_TASK_ON_HOME);
- if (extraFlag != null) {
- intent.putExtra(extraFlag, true);
- }
- intent.putExtra(EXTRA_TRIGGERED_FROM_ALT_TAB, mTriggeredFromAltTab);
- intent.putExtra(EXTRA_FROM_TASK_ID, (topTask != null) ? topTask.id : -1);
if (opts != null) {
mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT);
} else {
mContext.startActivityAsUser(intent, UserHandle.CURRENT);
}
- }
-
- /** Returns the last screenshot taken, this will be called by the RecentsActivity. */
- public static Bitmap getLastScreenshot() {
- return sLastScreenshot;
- }
-
- /** Recycles the last screenshot taken, this will be called by the RecentsActivity. */
- public static void consumeLastScreenshot() {
- if (sLastScreenshot != null) {
- sLastScreenshot.recycle();
- sLastScreenshot = null;
- }
+ mCanReuseTaskStackViews = true;
}
/** Sets the RecentsComponent callbacks. */
@@ -580,12 +720,33 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
}
/** Notifies the callbacks that the visibility of Recents has changed. */
- public static void notifyVisibilityChanged(boolean visible) {
+ @ProxyFromAnyToPrimaryUser
+ public static void notifyVisibilityChanged(Context context, SystemServicesProxy ssp,
+ boolean visible) {
+ if (ssp.isForegroundUserOwner()) {
+ visibilityChanged(visible);
+ } else {
+ Intent intent = createLocalBroadcastIntent(context,
+ ACTION_PROXY_NOTIFY_RECENTS_VISIBLITY_TO_OWNER);
+ intent.putExtra(EXTRA_RECENTS_VISIBILITY, visible);
+ context.sendBroadcastAsUser(intent, UserHandle.OWNER);
+ }
+ }
+ static void visibilityChanged(boolean visible) {
if (sRecentsComponentCallbacks != null) {
sRecentsComponentCallbacks.onVisibilityChanged(visible);
}
}
+ /**
+ * Returns the preloaded load plan and invalidates it.
+ */
+ public static RecentsTaskLoadPlan consumeInstanceLoadPlan() {
+ RecentsTaskLoadPlan plan = sInstanceLoadPlan;
+ sInstanceLoadPlan = null;
+ return plan;
+ }
+
/**** OnAnimationStartedListener Implementation ****/
@Override
@@ -610,15 +771,12 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
public void run() {
onAnimationStarted();
}
- }, 75);
+ }, 25);
}
};
// Send the broadcast to notify Recents that the animation has started
- Intent intent = new Intent(ACTION_START_ENTER_ANIMATION);
- intent.setPackage(mContext.getPackageName());
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT |
- Intent.FLAG_RECEIVER_FOREGROUND);
+ Intent intent = createLocalBroadcastIntent(mContext, ACTION_START_ENTER_ANIMATION);
mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, null,
fallbackReceiver, null, Activity.RESULT_CANCELED, null, null);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
index 85cf077..0a1718d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
@@ -25,8 +25,6 @@ public class Constants {
public static final boolean Verbose = false;
public static class App {
- // Enables the screenshot app->Recents transition
- public static final boolean EnableScreenshotAppTransition = false;
// Enables debug drawing for the transition thumbnail
public static final boolean EnableTransitionThumbnailDebugMode = false;
// Enables the filtering of tasks according to their grouping
@@ -66,13 +64,9 @@ public class Constants {
public static String DebugModeVersion = "A";
}
- public static class RecentsTaskLoader {
- // XXX: This should be calculated on the first load
- public static final int PreloadFirstTasksCount = 6;
- }
-
public static class TaskStackView {
- public static final int TaskStackOverscrollRange = 150;
+ public static final int TaskStackMinOverscrollRange = 32;
+ public static final int TaskStackMaxOverscrollRange = 128;
public static final int FilterStartDelay = 25;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 01ba5a2..cb1baeb 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -28,17 +28,20 @@ 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.R;
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;
@@ -47,6 +50,8 @@ 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;
@@ -60,7 +65,6 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
DebugOverlayView.DebugOverlayViewCallbacks {
RecentsConfiguration mConfig;
- boolean mVisible;
long mLastTabKeyEventTime;
// Top level views
@@ -79,6 +83,8 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
// Runnables to finish the Recents activity
FinishRecentsRunnable mFinishLaunchHomeRunnable;
+ private PhoneStatusBar mStatusBar;
+
/**
* A common Runnable to finish Recents either by calling finish() (with a custom animation) or
* launching Home with some ActivityOptions. Generally we always launch home when we exit
@@ -101,9 +107,6 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
@Override
public void run() {
- // Mark Recents as no longer visible
- AlternateRecentsComponent.notifyVisibilityChanged(false);
- mVisible = false;
// Finish Recents
if (mLaunchIntent != null) {
if (mLaunchOpts != null) {
@@ -127,8 +130,6 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(AlternateRecentsComponent.ACTION_HIDE_RECENTS_ACTIVITY)) {
- // Mark Recents as no longer visible
- AlternateRecentsComponent.notifyVisibilityChanged(false);
if (intent.getBooleanExtra(AlternateRecentsComponent.EXTRA_TRIGGERED_FROM_ALT_TAB, false)) {
// If we are hiding from releasing Alt-Tab, dismiss Recents to the focused app
dismissRecentsToFocusedTaskOrHome(false);
@@ -142,9 +143,7 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
// If we are toggling Recents, then first unfilter any filtered stacks first
dismissRecentsToFocusedTaskOrHome(true);
} else if (action.equals(AlternateRecentsComponent.ACTION_START_ENTER_ANIMATION)) {
- // Try and start the enter animation (or restart it on configuration changed)
- ReferenceCountedTrigger t = new ReferenceCountedTrigger(context, null, null, null);
- mRecentsView.startEnterRecentsAnimation(new ViewAnimation.TaskViewEnterContext(t));
+ // Trigger the enter animation
onEnterAnimationTriggered();
// Notify the fallback receiver that we have successfully got the broadcast
// See AlternateRecentsComponent.onAnimationStarted()
@@ -182,30 +181,31 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
/** Updates the set of recent tasks */
void updateRecentsTasks(Intent launchIntent) {
- // Update the configuration based on the launch intent
- boolean fromSearchHome = launchIntent.getBooleanExtra(
- AlternateRecentsComponent.EXTRA_FROM_SEARCH_HOME, false);
- mConfig.launchedFromHome = fromSearchHome || launchIntent.getBooleanExtra(
- AlternateRecentsComponent.EXTRA_FROM_HOME, false);
- mConfig.launchedFromAppWithThumbnail = launchIntent.getBooleanExtra(
- AlternateRecentsComponent.EXTRA_FROM_APP_THUMBNAIL, false);
- mConfig.launchedFromAppWithScreenshot = launchIntent.getBooleanExtra(
- AlternateRecentsComponent.EXTRA_FROM_APP_FULL_SCREENSHOT, false);
- mConfig.launchedToTaskId = launchIntent.getIntExtra(
- AlternateRecentsComponent.EXTRA_FROM_TASK_ID, -1);
- mConfig.launchedWithAltTab = launchIntent.getBooleanExtra(
- AlternateRecentsComponent.EXTRA_TRIGGERED_FROM_ALT_TAB, false);
-
- // Load all the tasks
+ // If AlternateRecentsComponent has preloaded a load plan, then use that to prevent
+ // reconstructing the task stack
RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
- SpaceNode root = loader.reload(this,
- Constants.Values.RecentsTaskLoader.PreloadFirstTasksCount,
- mConfig.launchedFromHome);
+ RecentsTaskLoadPlan plan = AlternateRecentsComponent.consumeInstanceLoadPlan();
+ if (plan == null) {
+ plan = loader.createLoadPlan(this);
+ }
+
+ // Start loading tasks according to the load plan
+ if (plan.getTaskStack() == null) {
+ loader.preloadTasks(plan, mConfig.launchedFromHome);
+ }
+ RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options();
+ loadOpts.runningTaskId = mConfig.launchedToTaskId;
+ loadOpts.numVisibleTasks = mConfig.launchedNumVisibleTasks;
+ loadOpts.numVisibleTaskThumbnails = mConfig.launchedNumVisibleThumbnails;
+ loader.loadTasks(this, plan, loadOpts);
+
+ SpaceNode root = plan.getSpaceNode();
ArrayList<TaskStack> stacks = root.getStacks();
- if (!stacks.isEmpty()) {
- mRecentsView.setTaskStacks(root.getStacks());
+ boolean hasTasks = root.hasTasks();
+ if (hasTasks) {
+ mRecentsView.setTaskStacks(stacks);
}
- mConfig.launchedWithNoRecentTasks = !root.hasTasks();
+ mConfig.launchedWithNoRecentTasks = !hasTasks;
// Create the home intent runnable
Intent homeIntent = new Intent(Intent.ACTION_MAIN, null);
@@ -214,9 +214,9 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
mFinishLaunchHomeRunnable = new FinishRecentsRunnable(homeIntent,
ActivityOptions.makeCustomAnimation(this,
- fromSearchHome ? R.anim.recents_to_search_launcher_enter :
+ mConfig.launchedFromSearchHome ? R.anim.recents_to_search_launcher_enter :
R.anim.recents_to_launcher_enter,
- fromSearchHome ? R.anim.recents_to_search_launcher_exit :
+ mConfig.launchedFromSearchHome ? R.anim.recents_to_search_launcher_exit :
R.anim.recents_to_launcher_exit));
// Mark the task that is the launch target
@@ -314,7 +314,8 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
/** Dismisses recents if we are already visible and the intent is to toggle the recents view */
boolean dismissRecentsToFocusedTaskOrHome(boolean checkFilteredStackState) {
- if (mVisible) {
+ SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
+ if (ssp.isRecentsTopMost(ssp.getTopMostTask(), null)) {
// If we currently have filtered stacks, then unfilter those first
if (checkFilteredStackState &&
mRecentsView.unfilterFilteredStacks()) return true;
@@ -348,7 +349,8 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
/** Dismisses Recents directly to Home if we currently aren't transitioning. */
boolean dismissRecentsToHome(boolean animated) {
- if (mVisible) {
+ SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
+ if (ssp.isRecentsTopMost(ssp.getTopMostTask(), null)) {
// Return to Home
dismissRecentsToHomeRaw(animated);
return true;
@@ -360,12 +362,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);
@@ -380,12 +381,12 @@ 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();
- // Update the recent tasks
- updateRecentsTasks(getIntent());
// Register the broadcast receiver to handle messages when the screen is turned off
IntentFilter filter = new IntentFilter();
@@ -401,32 +402,12 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
} catch (InvocationTargetException e) {
e.printStackTrace();
}
-
- // Update if we are getting a configuration change
- if (savedInstanceState != null) {
- mConfig.updateOnConfigurationChange();
- onConfigurationChange();
- }
-
- // Start listening for widget package changes if there is one bound, post it since we don't
- // want it stalling the startup
- if (mConfig.searchBarAppWidgetId >= 0) {
- final WeakReference<RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks> callback =
- new WeakReference<RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks>(this);
- mRecentsView.post(new Runnable() {
- @Override
- public void run() {
- RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks cb = callback.get();
- if (cb != null) {
- mAppWidgetHost.startListening(cb);
- }
- }
- });
- }
}
/** Inflates the debug overlay if debug mode is enabled. */
void inflateDebugOverlay() {
+ if (!Constants.DebugFlags.App.EnableDebugMode) return;
+
if (mConfig.debugModeEnabled && mDebugOverlay == null) {
// Inflate the overlay and seek bars
mDebugOverlay = (DebugOverlayView) mDebugOverlayStub.inflate();
@@ -435,37 +416,23 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
}
}
- void onConfigurationChange() {
- // Update RecentsConfiguration
- mConfig = RecentsConfiguration.reinitialize(this,
- RecentsTaskLoader.getInstance().getSystemServicesProxy());
-
- // Try and start the enter animation (or restart it on configuration changed)
- ReferenceCountedTrigger t = new ReferenceCountedTrigger(this, null, null, null);
- mRecentsView.startEnterRecentsAnimation(new ViewAnimation.TaskViewEnterContext(t));
- onEnterAnimationTriggered();
- }
-
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
- // Reinitialize the configuration
- RecentsConfiguration.reinitialize(this, RecentsTaskLoader.getInstance().getSystemServicesProxy());
-
// Clear any debug rects
if (mDebugOverlay != null) {
mDebugOverlay.clear();
}
-
- // Update the recent tasks
- updateRecentsTasks(intent);
}
@Override
protected void onStart() {
super.onStart();
+ RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
+ SystemServicesProxy ssp = loader.getSystemServicesProxy();
+ AlternateRecentsComponent.notifyVisibilityChanged(this, ssp, true);
// Register the broadcast receiver to handle messages from our service
IntentFilter filter = new IntentFilter();
@@ -475,29 +442,33 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
registerReceiver(mServiceBroadcastReceiver, filter);
// Register any broadcast receivers for the task loader
- RecentsTaskLoader.getInstance().registerReceivers(this, mRecentsView);
- }
+ loader.registerReceivers(this, mRecentsView);
- @Override
- protected void onResume() {
- super.onResume();
+ // Update the recent tasks
+ updateRecentsTasks(getIntent());
- // Mark Recents as visible
- mVisible = true;
+ // If this is a new instance from a configuration change, then we have to manually trigger
+ // the enter animation state
+ if (mConfig.launchedHasConfigurationChanged) {
+ onEnterAnimationTriggered();
+ }
}
@Override
protected void onStop() {
super.onStop();
+ RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
+ SystemServicesProxy ssp = loader.getSystemServicesProxy();
+ AlternateRecentsComponent.notifyVisibilityChanged(this, ssp, false);
- // Remove all the views
- mRecentsView.removeAllTaskStacks();
+ // Notify the views that we are no longer visible
+ mRecentsView.onRecentsHidden();
// Unregister the RecentsService receiver
unregisterReceiver(mServiceBroadcastReceiver);
// Unregister any broadcast receivers for the task loader
- RecentsTaskLoader.getInstance().unregisterReceivers();
+ loader.unregisterReceivers();
}
@Override
@@ -508,9 +479,32 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
unregisterReceiver(mSystemBroadcastReceiver);
// Stop listening for widget package changes if there was one bound
- if (mAppWidgetHost.isListening()) {
- mAppWidgetHost.stopListening();
+ mAppWidgetHost.stopListening();
+ }
+
+ public void onEnterAnimationTriggered() {
+ // Try and start the enter animation (or restart it on configuration changed)
+ ReferenceCountedTrigger t = new ReferenceCountedTrigger(this, null, null, null);
+ ViewAnimation.TaskViewEnterContext ctx = new ViewAnimation.TaskViewEnterContext(t);
+ mRecentsView.startEnterRecentsAnimation(ctx);
+ if (mConfig.searchBarAppWidgetId >= 0) {
+ final WeakReference<RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks> cbRef =
+ new WeakReference<RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks>(
+ RecentsActivity.this);
+ ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
+ @Override
+ public void run() {
+ // Start listening for widget package changes if there is one bound
+ RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks cb = cbRef.get();
+ if (cb != null) {
+ mAppWidgetHost.startListening(cb);
+ }
+ }
+ });
}
+
+ // Animate the SystemUI scrim views
+ mScrimViews.startEnterRecentsAnimation();
}
@Override
@@ -525,13 +519,13 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_TAB: {
- boolean hasRepKeyTimeElapsed = (System.currentTimeMillis() -
+ boolean hasRepKeyTimeElapsed = (SystemClock.elapsedRealtime() -
mLastTabKeyEventTime) > mConfig.altTabKeyDelay;
if (event.getRepeatCount() <= 0 || hasRepKeyTimeElapsed) {
// Focus the next task in the stack
final boolean backward = event.isShiftPressed();
mRecentsView.focusNextTask(!backward);
- mLastTabKeyEventTime = System.currentTimeMillis();
+ mLastTabKeyEventTime = SystemClock.elapsedRealtime();
}
return true;
}
@@ -572,7 +566,6 @@ 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)) {
@@ -580,13 +573,17 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
settings.edit().remove(Constants.Values.App.Key_DebugModeEnabled).apply();
mConfig.debugModeEnabled = false;
inflateDebugOverlay();
- mDebugOverlay.disable();
+ if (mDebugOverlay != null) {
+ mDebugOverlay.disable();
+ }
} else {
// Enable the debug mode
settings.edit().putBoolean(Constants.Values.App.Key_DebugModeEnabled, true).apply();
mConfig.debugModeEnabled = true;
inflateDebugOverlay();
- mDebugOverlay.enable();
+ if (mDebugOverlay != null) {
+ mDebugOverlay.enable();
+ }
}
Toast.makeText(this, "Debug mode (" + Constants.Values.App.DebugModeVersion + ") " +
(mConfig.debugModeEnabled ? "Enabled" : "Disabled") + ", please restart Recents now",
@@ -594,12 +591,6 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
}
}
- /** Called when the enter recents animation is triggered. */
- public void onEnterAnimationTriggered() {
- // Animate the SystemUI scrim views
- mScrimViews.startEnterRecentsAnimation();
- }
-
/**** RecentsView.RecentsViewCallbacks Implementation ****/
@Override
@@ -610,9 +601,6 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
@Override
public void onTaskViewClicked() {
- // Mark recents as no longer visible
- AlternateRecentsComponent.notifyVisibilityChanged(false);
- mVisible = false;
}
@Override
@@ -626,6 +614,13 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
mFinishLaunchHomeRunnable.run();
}
+ @Override
+ public void onScreenPinningRequest() {
+ if (mStatusBar != null) {
+ mStatusBar.showScreenPinningRequest(false);
+ }
+ }
+
/**** RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks Implementation ****/
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java
index a63e167..5bae37a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java
@@ -43,23 +43,23 @@ public class RecentsAppWidgetHost extends AppWidgetHost {
public void startListening(RecentsAppWidgetHostCallbacks cb) {
mCb = cb;
- mIsListening = true;
- super.startListening();
+ if (!mIsListening) {
+ mIsListening = true;
+ super.startListening();
+ }
}
@Override
public void stopListening() {
- super.stopListening();
+ if (mIsListening) {
+ super.stopListening();
+ }
// Ensure that we release any references to the callbacks
mCb = null;
mContext = null;
mIsListening = false;
}
- public boolean isListening() {
- return mIsListening;
- }
-
@Override
protected void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidgetInfo) {
if (mCb == null) return;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index 2aca576..52e7e7f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -38,6 +38,18 @@ public class RecentsConfiguration {
static RecentsConfiguration sInstance;
static int sPrevConfigurationHashCode;
+ /** Levels of svelte in increasing severity/austerity. */
+ // No svelting.
+ public static final int SVELTE_NONE = 0;
+ // Limit thumbnail cache to number of visible thumbnails when Recents was loaded, disable
+ // caching thumbnails as you scroll.
+ public static final int SVELTE_LIMIT_CACHE = 1;
+ // Disable the thumbnail cache, load thumbnails asynchronously when the activity loads and
+ // evict all thumbnails when hidden.
+ public static final int SVELTE_DISABLE_CACHE = 2;
+ // Disable all thumbnail loading.
+ public static final int SVELTE_DISABLE_LOADING = 3;
+
/** Animations */
public float animationPxMovementPerSecond;
@@ -74,11 +86,15 @@ public class RecentsConfiguration {
public float taskStackWidthPaddingPct;
public float taskStackOverscrollPct;
+ /** Transitions */
+ public int transitionEnterFromAppDelay;
+ public int transitionEnterFromHomeDelay;
+
/** Task view animation and styles */
- public int taskViewEnterFromHomeDelay;
+ public int taskViewEnterFromAppDuration;
public int taskViewEnterFromHomeDuration;
public int taskViewEnterFromHomeStaggerDelay;
- public int taskViewEnterFromHomeStaggerDuration;
+ public int taskViewExitToAppDuration;
public int taskViewExitToHomeDuration;
public int taskViewRemoveAnimDuration;
public int taskViewRemoveAnimTranslationXPx;
@@ -98,16 +114,8 @@ public class RecentsConfiguration {
/** Task bar size & animations */
public int taskBarHeight;
- public int taskBarEnterAnimDuration;
- public int taskBarEnterAnimDelay;
- public int taskBarExitAnimDuration;
public int taskBarDismissDozeDelaySeconds;
- /** Lock to app */
- public int taskViewLockToAppButtonHeight;
- public int taskViewLockToAppShortAnimDuration;
- public int taskViewLockToAppLongAnimDuration;
-
/** Nav bar scrim */
public int navBarScrimEnterDuration;
@@ -115,9 +123,13 @@ public class RecentsConfiguration {
public boolean launchedWithAltTab;
public boolean launchedWithNoRecentTasks;
public boolean launchedFromAppWithThumbnail;
- public boolean launchedFromAppWithScreenshot;
public boolean launchedFromHome;
+ public boolean launchedFromSearchHome;
+ public boolean launchedReuseTaskStackViews;
+ public boolean launchedHasConfigurationChanged;
public int launchedToTaskId;
+ public int launchedNumVisibleTasks;
+ public int launchedNumVisibleThumbnails;
/** Misc **/
public boolean useHardwareLayers;
@@ -128,6 +140,7 @@ public class RecentsConfiguration {
public boolean lockToAppEnabled;
public boolean developerOptionsEnabled;
public boolean debugModeEnabled;
+ public int svelteLevel;
/** Private constructor */
private RecentsConfiguration(Context context) {
@@ -213,15 +226,23 @@ public class RecentsConfiguration {
taskStackMaxDim = res.getInteger(R.integer.recents_max_task_stack_view_dim);
taskStackTopPaddingPx = res.getDimensionPixelSize(R.dimen.recents_stack_top_padding);
+ // Transition
+ transitionEnterFromAppDelay =
+ res.getInteger(R.integer.recents_enter_from_app_transition_duration);
+ transitionEnterFromHomeDelay =
+ res.getInteger(R.integer.recents_enter_from_home_transition_duration);
+
// Task view animation and styles
- taskViewEnterFromHomeDelay =
- res.getInteger(R.integer.recents_animate_task_enter_from_home_delay);
+ taskViewEnterFromAppDuration =
+ res.getInteger(R.integer.recents_task_enter_from_app_duration);
taskViewEnterFromHomeDuration =
- res.getInteger(R.integer.recents_animate_task_enter_from_home_duration);
+ res.getInteger(R.integer.recents_task_enter_from_home_duration);
taskViewEnterFromHomeStaggerDelay =
- res.getInteger(R.integer.recents_animate_task_enter_from_home_stagger_delay);
+ res.getInteger(R.integer.recents_task_enter_from_home_stagger_delay);
+ taskViewExitToAppDuration =
+ res.getInteger(R.integer.recents_task_exit_to_app_duration);
taskViewExitToHomeDuration =
- res.getInteger(R.integer.recents_animate_task_exit_to_home_duration);
+ res.getInteger(R.integer.recents_task_exit_to_home_duration);
taskViewRemoveAnimDuration =
res.getInteger(R.integer.recents_animate_task_view_remove_duration);
taskViewRemoveAnimTranslationXPx =
@@ -252,23 +273,9 @@ public class RecentsConfiguration {
// Task bar size & animations
taskBarHeight = res.getDimensionPixelSize(R.dimen.recents_task_bar_height);
- taskBarEnterAnimDuration =
- res.getInteger(R.integer.recents_animate_task_bar_enter_duration);
- taskBarEnterAnimDelay =
- res.getInteger(R.integer.recents_animate_task_bar_enter_delay);
- taskBarExitAnimDuration =
- res.getInteger(R.integer.recents_animate_task_bar_exit_duration);
taskBarDismissDozeDelaySeconds =
res.getInteger(R.integer.recents_task_bar_dismiss_delay_seconds);
- // Lock to app
- taskViewLockToAppButtonHeight =
- res.getDimensionPixelSize(R.dimen.recents_task_view_lock_to_app_button_height);
- taskViewLockToAppShortAnimDuration =
- res.getInteger(R.integer.recents_animate_lock_to_app_button_short_duration);
- taskViewLockToAppLongAnimDuration =
- res.getInteger(R.integer.recents_animate_lock_to_app_button_long_duration);
-
// Nav bar scrim
navBarScrimEnterDuration =
res.getInteger(R.integer.recents_nav_bar_scrim_enter_duration);
@@ -277,6 +284,7 @@ public class RecentsConfiguration {
useHardwareLayers = res.getBoolean(R.bool.config_recents_use_hardware_layers);
altTabKeyDelay = res.getInteger(R.integer.recents_alt_tab_key_delay);
fakeShadows = res.getBoolean(R.bool.config_recents_fake_shadows);
+ svelteLevel = res.getInteger(R.integer.recents_svelte_level);
}
/** Updates the system insets */
@@ -304,12 +312,10 @@ public class RecentsConfiguration {
/** Called when the configuration has changed, and we want to reset any configuration specific
* members. */
public void updateOnConfigurationChange() {
- launchedWithAltTab = false;
- launchedWithNoRecentTasks = false;
- launchedFromAppWithThumbnail = false;
- launchedFromAppWithScreenshot = false;
- launchedFromHome = false;
- launchedToTaskId = -1;
+ // Reset this flag on configuration change to ensure that we recreate new task views
+ launchedReuseTaskStackViews = false;
+ // Set this flag to indicate that the configuration has changed since Recents last launched
+ launchedHasConfigurationChanged = true;
}
/** Returns whether the search bar app widget exists. */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsUserEventProxyReceiver.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsUserEventProxyReceiver.java
new file mode 100644
index 0000000..236da5d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsUserEventProxyReceiver.java
@@ -0,0 +1,70 @@
+/*
+ * 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;
+
+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.
+ */
+public class RecentsUserEventProxyReceiver extends BroadcastReceiver {
+ final public static String ACTION_PROXY_SHOW_RECENTS_TO_USER =
+ "com.android.systemui.recents.action.SHOW_RECENTS_FOR_USER";
+ final public static String ACTION_PROXY_HIDE_RECENTS_TO_USER =
+ "com.android.systemui.recents.action.HIDE_RECENTS_FOR_USER";
+ final public static String ACTION_PROXY_TOGGLE_RECENTS_TO_USER =
+ "com.android.systemui.recents.action.TOGGLE_RECENTS_FOR_USER";
+ final public static String ACTION_PROXY_PRELOAD_RECENTS_TO_USER =
+ "com.android.systemui.recents.action.PRELOAD_RECENTS_FOR_USER";
+ final public static String ACTION_PROXY_CONFIG_CHANGE_TO_USER =
+ "com.android.systemui.recents.action.CONFIG_CHANGED_FOR_USER";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ AlternateRecentsComponent recents = Recents.getRecentsComponent(
+ context.getApplicationContext(), true);
+ switch (intent.getAction()) {
+ case ACTION_PROXY_SHOW_RECENTS_TO_USER: {
+ boolean triggeredFromAltTab = intent.getBooleanExtra(
+ AlternateRecentsComponent.EXTRA_TRIGGERED_FROM_ALT_TAB, false);
+ recents.showRecents(triggeredFromAltTab);
+ break;
+ }
+ case ACTION_PROXY_HIDE_RECENTS_TO_USER: {
+ boolean triggeredFromAltTab = intent.getBooleanExtra(
+ AlternateRecentsComponent.EXTRA_TRIGGERED_FROM_ALT_TAB, false);
+ boolean triggeredFromHome = intent.getBooleanExtra(
+ AlternateRecentsComponent.EXTRA_TRIGGERED_FROM_HOME_KEY, false);
+ recents.hideRecents(triggeredFromAltTab, triggeredFromHome);
+ break;
+ }
+ case ACTION_PROXY_TOGGLE_RECENTS_TO_USER:
+ recents.toggleRecents();
+ break;
+ case ACTION_PROXY_PRELOAD_RECENTS_TO_USER:
+ recents.preloadRecents();
+ break;
+ case ACTION_PROXY_CONFIG_CHANGE_TO_USER:
+ recents.configurationChanged();
+ break;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/DozeTrigger.java b/packages/SystemUI/src/com/android/systemui/recents/misc/DozeTrigger.java
index 4456066..735f79f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/DozeTrigger.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/DozeTrigger.java
@@ -82,4 +82,9 @@ public class DozeTrigger {
public boolean hasTriggered() {
return mHasTriggered;
}
+
+ /** Resets the doze trigger state. */
+ public void resetTrigger() {
+ mHasTriggered = false;
+ }
}
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 71a3ef1..90b099c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -21,6 +21,7 @@ import android.app.ActivityManagerNative;
import android.app.ActivityOptions;
import android.app.AppGlobals;
import android.app.IActivityManager;
+import android.app.ITaskStackListener;
import android.app.SearchManager;
import android.appwidget.AppWidgetHost;
import android.appwidget.AppWidgetManager;
@@ -58,6 +59,7 @@ import android.view.SurfaceControl;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
import com.android.systemui.R;
+import com.android.systemui.recents.AlternateRecentsComponent;
import com.android.systemui.recents.Constants;
import java.io.IOException;
@@ -65,6 +67,7 @@ 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
@@ -216,6 +219,37 @@ public class SystemServicesProxy {
return mAm.getRunningTasks(numTasks);
}
+ /** Returns the top task. */
+ public ActivityManager.RunningTaskInfo getTopMostTask() {
+ List<ActivityManager.RunningTaskInfo> tasks = getRunningTasks(1);
+ if (!tasks.isEmpty()) {
+ return tasks.get(0);
+ }
+ return null;
+ }
+
+ /** Returns whether the recents is currently running */
+ public boolean isRecentsTopMost(ActivityManager.RunningTaskInfo topTask,
+ AtomicBoolean 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 (isHomeTopMost != null) {
+ isHomeTopMost.set(false);
+ }
+ return true;
+ }
+
+ if (isHomeTopMost != null) {
+ isHomeTopMost.set(isInHomeStack(topTask.id));
+ }
+ }
+ return false;
+ }
+
/** Returns whether the specified task is in the home stack */
public boolean isInHomeStack(int taskId) {
if (mAm == null) return false;
@@ -242,6 +276,7 @@ public class SystemServicesProxy {
Bitmap thumbnail = SystemServicesProxy.getThumbnail(mAm, taskId);
if (thumbnail != null) {
+ thumbnail.setHasAlpha(false);
// We use a dumb heuristic for now, if the thumbnail is purely transparent in the top
// left pixel, then assume the whole thumbnail is transparent. Generally, proper
// screenshots are always composed onto a bitmap that has no alpha.
@@ -291,18 +326,18 @@ public class SystemServicesProxy {
}
}
- /** Removes the task and kills the process */
- public void removeTask(int taskId, boolean isDocument) {
+ /** Removes the task */
+ public void removeTask(int taskId) {
if (mAm == null) return;
if (Constants.DebugFlags.App.EnableSystemServicesProxy) return;
- // Remove the task, and only kill the process if it is not a document
- mAm.removeTask(taskId, isDocument ? 0 : ActivityManager.REMOVE_TASK_KILL_PROCESS);
+ // Remove the task.
+ mAm.removeTask(taskId);
}
/**
* Returns the activity info for a given component name.
- *
+ *
* @param cn The component name of the activity.
* @param userId The userId of the user that this is for.
*/
@@ -392,6 +427,15 @@ public class SystemServicesProxy {
}
/**
+ * Returns whether the foreground user is the owner.
+ */
+ public boolean isForegroundUserOwner() {
+ if (mAm == null) return false;
+
+ return mAm.getCurrentUser() == UserHandle.USER_OWNER;
+ }
+
+ /**
* Resolves and returns the first Recents widget from the same package as the global
* assist activity.
*/
@@ -429,6 +473,7 @@ public class SystemServicesProxy {
opts.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX);
if (!mAwm.bindAppWidgetIdIfAllowed(searchWidgetId, searchWidgetInfo.provider, opts)) {
+ host.deleteAppWidgetId(searchWidgetId);
return null;
}
return new Pair<Integer, AppWidgetProviderInfo>(searchWidgetId, searchWidgetInfo);
@@ -492,17 +537,6 @@ public class SystemServicesProxy {
}
/**
- * Locks the current task.
- */
- public void lockCurrentTask() {
- if (mIam == null) return;
-
- try {
- mIam.startLockTaskModeOnCurrent();
- } catch (RemoteException e) {}
- }
-
- /**
* Takes a screenshot of the current surface.
*/
public Bitmap takeScreenshot() {
@@ -532,4 +566,26 @@ public class SystemServicesProxy {
}
return false;
}
+
+ /** Starts an in-place animation on the front most application windows. */
+ public void startInPlaceAnimationOnFrontMostApplication(ActivityOptions opts) {
+ if (mIam == null) return;
+
+ try {
+ mIam.startInPlaceAnimationOnFrontMostApplication(opts);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ /** Registers a task stack listener with the system. */
+ public void registerTaskStackListener(ITaskStackListener listener) {
+ if (mIam == null) return;
+
+ try {
+ mIam.registerTaskStackListener(listener);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
}
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 f01d17c..e1179fa 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
@@ -16,7 +16,7 @@
package com.android.systemui.recents.misc;
-import android.content.Intent;
+import android.animation.Animator;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Rect;
@@ -183,9 +183,14 @@ public class Utilities {
sPropertyMethod.invoke(null, property, value);
}
- /** Returns whether the specified intent is a document. */
- public static boolean isDocument(Intent intent) {
- int flags = intent.getFlags();
- return (flags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) == Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
+ /**
+ * Cancels an animation ensuring that if it has listeners, onCancel and onEnd
+ * are not called.
+ */
+ public static void cancelAnimationWithoutCallbacks(Animator animator) {
+ if (animator != null) {
+ animator.removeAllListeners();
+ animator.cancel();
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/KeyStoreLruCache.java b/packages/SystemUI/src/com/android/systemui/recents/model/KeyStoreLruCache.java
index 7ccefc6..97e0916 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/KeyStoreLruCache.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/KeyStoreLruCache.java
@@ -21,15 +21,16 @@ import android.util.LruCache;
import java.util.HashMap;
/**
- * An LRU cache that support querying the keys as well as values. By using the Task's key, we can
- * prevent holding onto a reference to the Task resource data, while keeping the cache data in
- * memory where necessary.
+ * An LRU cache that internally support querying the keys as well as values. We use this to keep
+ * track of the task metadata to determine when to invalidate the cache when tasks have been
+ * updated. Generally, this cache will return the last known cache value for the requested task
+ * key.
*/
public class KeyStoreLruCache<V> {
// We keep a set of keys that are associated with the LRU cache, so that we can find out
// information about the Task that was previously in the cache.
HashMap<Integer, Task.TaskKey> mTaskKeys = new HashMap<Integer, Task.TaskKey>();
- // The cache implementation
+ // The cache implementation, mapping task id -> value
LruCache<Integer, V> mCache;
public KeyStoreLruCache(int cacheSize) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
index 60e89bf..e48e5f0 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
@@ -19,6 +19,7 @@ package com.android.systemui.recents.model;
import android.content.ComponentName;
import android.content.Context;
import android.os.Looper;
+import android.os.UserHandle;
import com.android.internal.content.PackageMonitor;
import com.android.systemui.recents.misc.SystemServicesProxy;
@@ -26,16 +27,16 @@ import java.util.HashSet;
import java.util.List;
/**
- * The package monitor listens for changes from PackageManager to update the contents of the Recents
- * list.
+ * The package monitor listens for changes from PackageManager to update the contents of the
+ * Recents list.
*/
public class RecentsPackageMonitor extends PackageMonitor {
public interface PackageCallbacks {
- public void onComponentRemoved(HashSet<ComponentName> cns);
+ public void onPackagesChanged(RecentsPackageMonitor monitor, String packageName,
+ int userId);
}
PackageCallbacks mCb;
- List<Task.TaskKey> mTasks;
SystemServicesProxy mSystemServicesProxy;
/** Registers the broadcast receivers with the specified callbacks. */
@@ -43,7 +44,9 @@ public class RecentsPackageMonitor extends PackageMonitor {
mSystemServicesProxy = new SystemServicesProxy(context);
mCb = cb;
try {
- register(context, Looper.getMainLooper(), true);
+ // We register for events from all users, but will cross-reference them with
+ // packages for the current user and any profiles they have
+ register(context, Looper.getMainLooper(), UserHandle.ALL, true);
} catch (IllegalStateException e) {
e.printStackTrace();
}
@@ -59,29 +62,15 @@ public class RecentsPackageMonitor extends PackageMonitor {
}
mSystemServicesProxy = null;
mCb = null;
- mTasks.clear();
- }
-
- /** Sets the list of tasks to match against package broadcast changes. */
- void setTasks(List<Task.TaskKey> tasks) {
- mTasks = tasks;
}
@Override
public void onPackageRemoved(String packageName, int uid) {
if (mCb == null) return;
- // Identify all the tasks that should be removed as a result of the package being removed.
- // Using a set to ensure that we callback once per unique component.
- HashSet<ComponentName> componentsToRemove = new HashSet<ComponentName>();
- for (Task.TaskKey t : mTasks) {
- ComponentName cn = t.baseIntent.getComponent();
- if (cn.getPackageName().equals(packageName)) {
- componentsToRemove.add(cn);
- }
- }
- // Notify our callbacks that the components no longer exist
- mCb.onComponentRemoved(componentsToRemove);
+ // Notify callbacks that a package has changed
+ final int eventUserId = getChangingUserId();
+ mCb.onPackagesChanged(this, packageName, eventUserId);
}
@Override
@@ -94,25 +83,38 @@ public class RecentsPackageMonitor extends PackageMonitor {
public void onPackageModified(String packageName) {
if (mCb == null) return;
+ // Notify callbacks that a package has changed
+ final int eventUserId = getChangingUserId();
+ mCb.onPackagesChanged(this, packageName, eventUserId);
+ }
+
+ /**
+ * Computes the components that have been removed as a result of a change in the specified
+ * package.
+ */
+ public HashSet<ComponentName> computeComponentsRemoved(List<Task.TaskKey> taskKeys,
+ String packageName, int userId) {
// Identify all the tasks that should be removed as a result of the package being removed.
// Using a set to ensure that we callback once per unique component.
- HashSet<ComponentName> componentsKnownToExist = new HashSet<ComponentName>();
- HashSet<ComponentName> componentsToRemove = new HashSet<ComponentName>();
- for (Task.TaskKey t : mTasks) {
+ HashSet<ComponentName> existingComponents = new HashSet<ComponentName>();
+ HashSet<ComponentName> removedComponents = new HashSet<ComponentName>();
+ for (Task.TaskKey t : taskKeys) {
+ // Skip if this doesn't apply to the current user
+ if (t.userId != userId) continue;
+
ComponentName cn = t.baseIntent.getComponent();
if (cn.getPackageName().equals(packageName)) {
- if (componentsKnownToExist.contains(cn)) {
+ if (existingComponents.contains(cn)) {
// If we know that the component still exists in the package, then skip
continue;
}
- if (mSystemServicesProxy.getActivityInfo(cn) != null) {
- componentsKnownToExist.add(cn);
+ if (mSystemServicesProxy.getActivityInfo(cn, userId) != null) {
+ existingComponents.add(cn);
} else {
- componentsToRemove.add(cn);
+ removedComponents.add(cn);
}
}
}
- // Notify our callbacks that the components no longer exist
- mCb.onComponentRemoved(componentsToRemove);
+ return removedComponents;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
new file mode 100644
index 0000000..0e1c01a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.model;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+import android.util.Log;
+import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.misc.SystemServicesProxy;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+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
+ */
+public class RecentsTaskLoadPlan {
+ static String TAG = "RecentsTaskLoadPlan";
+ static boolean DEBUG = false;
+
+ /** The set of conditions to load tasks. */
+ public static class Options {
+ public int runningTaskId = -1;
+ public boolean loadIcons = true;
+ public boolean loadThumbnails = true;
+ public boolean onlyLoadForCache = false;
+ public boolean onlyLoadPausedActivities = false;
+ public int numVisibleTasks = 0;
+ public int numVisibleTaskThumbnails = 0;
+ }
+
+ Context mContext;
+ RecentsConfiguration mConfig;
+ SystemServicesProxy mSystemServicesProxy;
+
+ List<ActivityManager.RecentTaskInfo> mRawTasks;
+ TaskStack mStack;
+ HashMap<Task.ComponentNameKey, ActivityInfoHandle> mActivityInfoCache =
+ new HashMap<Task.ComponentNameKey, ActivityInfoHandle>();
+
+ /** Package level ctor */
+ RecentsTaskLoadPlan(Context context, RecentsConfiguration config, SystemServicesProxy ssp) {
+ mContext = context;
+ mConfig = config;
+ mSystemServicesProxy = ssp;
+ }
+
+ /**
+ * An optimization to preload the raw list of tasks.
+ */
+ public synchronized void preloadRawTasks(boolean isTopTaskHome) {
+ mRawTasks = mSystemServicesProxy.getRecentTasks(mConfig.maxNumTasksToLoad,
+ UserHandle.CURRENT.getIdentifier(), isTopTaskHome);
+ Collections.reverse(mRawTasks);
+
+ if (DEBUG) Log.d(TAG, "preloadRawTasks, tasks: " + mRawTasks.size());
+ }
+
+ /**
+ * Preloads the list of recent tasks from the system. After this call, the TaskStack will
+ * have a list of all the recent tasks with their metadata, not including icons or
+ * thumbnails which were not cached and have to be loaded.
+ */
+ synchronized void preloadPlan(RecentsTaskLoader loader, boolean isTopTaskHome) {
+ if (DEBUG) Log.d(TAG, "preloadPlan");
+
+ mActivityInfoCache.clear();
+ mStack = new TaskStack();
+
+ Resources res = mContext.getResources();
+ ArrayList<Task> loadedTasks = new ArrayList<Task>();
+ if (mRawTasks == null) {
+ preloadRawTasks(isTopTaskHome);
+ }
+ int taskCount = mRawTasks.size();
+ for (int i = 0; i < taskCount; i++) {
+ ActivityManager.RecentTaskInfo t = mRawTasks.get(i);
+
+ // Compose the task key
+ Task.TaskKey taskKey = new Task.TaskKey(t.persistentId, t.baseIntent, t.userId,
+ t.firstActiveTime, t.lastActiveTime);
+
+ // 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();
+ }
+
+ // Load the label, icon, and color
+ String activityLabel = loader.getAndUpdateActivityLabel(taskKey, t.taskDescription,
+ mSystemServicesProxy, infoHandle);
+ Drawable activityIcon = loader.getAndUpdateActivityIcon(taskKey, t.taskDescription,
+ mSystemServicesProxy, res, infoHandle, false);
+ int activityColor = loader.getActivityPrimaryColor(t.taskDescription, mConfig);
+
+ // Update the activity info cache
+ if (!hadCachedActivityInfo && infoHandle.info != null) {
+ mActivityInfoCache.put(cnKey, infoHandle);
+ }
+
+ Bitmap icon = t.taskDescription != null
+ ? t.taskDescription.getInMemoryIcon()
+ : null;
+ String iconFilename = t.taskDescription != null
+ ? t.taskDescription.getIconFilename()
+ : null;
+
+ // 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);
+ task.thumbnail = loader.getAndUpdateThumbnail(taskKey, mSystemServicesProxy, false);
+ if (DEBUG) Log.d(TAG, "\tthumbnail: " + taskKey + ", " + task.thumbnail);
+ loadedTasks.add(task);
+ }
+ mStack.setTasks(loadedTasks);
+ mStack.createAffiliatedGroupings(mConfig);
+
+ // Assertion
+ if (mStack.getTaskCount() != mRawTasks.size()) {
+ throw new RuntimeException("Loading failed");
+ }
+ }
+
+ /**
+ * Called to apply the actual loading based on the specified conditions.
+ */
+ synchronized void executePlan(Options opts, RecentsTaskLoader loader,
+ TaskResourceLoadQueue loadQueue) {
+ if (DEBUG) Log.d(TAG, "executePlan, # tasks: " + opts.numVisibleTasks +
+ ", # thumbnails: " + opts.numVisibleTaskThumbnails +
+ ", running task id: " + opts.runningTaskId);
+
+ 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;
+
+ // 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);
+
+ // 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.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);
+ }
+ }
+ }
+
+ /**
+ * Composes and returns a TaskStack from the preloaded list of recent tasks.
+ */
+ public TaskStack getTaskStack() {
+ return mStack;
+ }
+
+ /**
+ * Composes and returns a SpaceNode from the preloaded list of recent tasks.
+ */
+ public SpaceNode getSpaceNode() {
+ SpaceNode node = new SpaceNode();
+ node.setStack(mStack);
+ return node;
+ }
+}
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 d40e847..ba2903a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -26,7 +26,6 @@ import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.HandlerThread;
-import android.os.UserHandle;
import android.util.Log;
import com.android.systemui.R;
@@ -34,11 +33,7 @@ import com.android.systemui.recents.Constants;
import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import java.util.ArrayList;
import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;
@@ -99,6 +94,9 @@ class TaskResourceLoadQueue {
/* Task resource loader */
class TaskResourceLoader implements Runnable {
+ static String TAG = "TaskResourceLoader";
+ static boolean DEBUG = false;
+
Context mContext;
HandlerThread mLoadThread;
Handler mLoadThreadHandler;
@@ -170,56 +168,67 @@ class TaskResourceLoader implements Runnable {
}
}
} else {
+ RecentsConfiguration config = RecentsConfiguration.getInstance();
SystemServicesProxy ssp = mSystemServicesProxy;
-
- // Load the next item from the queue
- final Task t = mLoadQueue.nextTask();
- if (t != null) {
- Drawable cachedIcon = mApplicationIconCache.get(t.key);
- Bitmap cachedThumbnail = mThumbnailCache.get(t.key);
-
- // Load the application icon if it is stale or we haven't cached one yet
- if (cachedIcon == null) {
- cachedIcon = getTaskDescriptionIcon(t.key, t.icon, t.iconFilename, ssp,
- mContext.getResources());
-
+ // If we've stopped the loader, then fall through to the above logic to wait on
+ // the load thread
+ if (ssp != null) {
+ // Load the next item from the queue
+ final Task t = mLoadQueue.nextTask();
+ if (t != null) {
+ Drawable cachedIcon = mApplicationIconCache.get(t.key);
+ Bitmap cachedThumbnail = mThumbnailCache.get(t.key);
+
+ // Load the application icon if it is stale or we haven't cached one yet
if (cachedIcon == null) {
- ActivityInfo info = ssp.getActivityInfo(t.key.baseIntent.getComponent(),
- t.key.userId);
- if (info != null) {
- cachedIcon = ssp.getActivityIcon(info, t.key.userId);
+ cachedIcon = getTaskDescriptionIcon(t.key, t.icon, t.iconFilename, ssp,
+ mContext.getResources());
+
+ if (cachedIcon == null) {
+ ActivityInfo info = ssp.getActivityInfo(
+ t.key.baseIntent.getComponent(), t.key.userId);
+ if (info != null) {
+ if (DEBUG) Log.d(TAG, "Loading icon: " + t.key);
+ cachedIcon = ssp.getActivityIcon(info, t.key.userId);
+ }
}
- }
- if (cachedIcon == null) {
- cachedIcon = mDefaultApplicationIcon;
- }
+ if (cachedIcon == null) {
+ cachedIcon = mDefaultApplicationIcon;
+ }
- // At this point, even if we can't load the icon, we will set the default
- // icon.
- mApplicationIconCache.put(t.key, cachedIcon);
- }
- // Load the thumbnail if it is stale or we haven't cached one yet
- if (cachedThumbnail == null) {
- cachedThumbnail = ssp.getTaskThumbnail(t.key.id);
- if (cachedThumbnail != null) {
- cachedThumbnail.setHasAlpha(false);
- } else {
- cachedThumbnail = mDefaultThumbnail;
+ // At this point, even if we can't load the icon, we will set the
+ // default icon.
+ mApplicationIconCache.put(t.key, cachedIcon);
}
- mThumbnailCache.put(t.key, cachedThumbnail);
- }
- if (!mCancelled) {
- // Notify that the task data has changed
- final Drawable newIcon = cachedIcon;
- final Bitmap newThumbnail = cachedThumbnail == mDefaultThumbnail
- ? null : cachedThumbnail;
- mMainThreadHandler.post(new Runnable() {
- @Override
- public void run() {
- t.notifyTaskDataLoaded(newThumbnail, newIcon);
+ // Load the thumbnail if it is stale or we haven't cached one yet
+ if (cachedThumbnail == null) {
+ if (config.svelteLevel < RecentsConfiguration.SVELTE_DISABLE_LOADING) {
+ if (DEBUG) Log.d(TAG, "Loading thumbnail: " + t.key);
+ cachedThumbnail = ssp.getTaskThumbnail(t.key.id);
+ }
+ if (cachedThumbnail == null) {
+ cachedThumbnail = mDefaultThumbnail;
+ }
+ // When svelte, we trim the memory to just the visible thumbnails when
+ // leaving, so don't thrash the cache as the user scrolls (just load
+ // them from scratch each time)
+ if (config.svelteLevel < RecentsConfiguration.SVELTE_LIMIT_CACHE) {
+ mThumbnailCache.put(t.key, cachedThumbnail);
}
- });
+ }
+ if (!mCancelled) {
+ // Notify that the task data has changed
+ final Drawable newIcon = cachedIcon;
+ final Bitmap newThumbnail = cachedThumbnail == mDefaultThumbnail
+ ? null : cachedThumbnail;
+ mMainThreadHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ t.notifyTaskDataLoaded(newThumbnail, newIcon);
+ }
+ });
+ }
}
}
@@ -257,6 +266,7 @@ public class RecentsTaskLoader {
private static final String TAG = "RecentsTaskLoader";
static RecentsTaskLoader sInstance;
+ static int INVALID_TASK_ID = -1;
SystemServicesProxy mSystemServicesProxy;
DrawableLruCache mApplicationIconCache;
@@ -269,6 +279,8 @@ public class RecentsTaskLoader {
int mMaxThumbnailCacheSize;
int mMaxIconCacheSize;
+ int mNumVisibleTasksLoaded;
+ int mNumVisibleThumbnailsLoaded;
BitmapDrawable mDefaultApplicationIcon;
Bitmap mDefaultThumbnail;
@@ -321,31 +333,45 @@ public class RecentsTaskLoader {
return mSystemServicesProxy;
}
- /** Gets the list of recent tasks, ordered from back to front. */
- private static List<ActivityManager.RecentTaskInfo> getRecentTasks(SystemServicesProxy ssp,
- boolean isTopTaskHome) {
- RecentsConfiguration config = RecentsConfiguration.getInstance();
- List<ActivityManager.RecentTaskInfo> tasks =
- ssp.getRecentTasks(config.maxNumTasksToLoad, UserHandle.CURRENT.getIdentifier(),
- isTopTaskHome);
- Collections.reverse(tasks);
- return tasks;
+ /** Returns the activity label using as many cached values as we can. */
+ public String getAndUpdateActivityLabel(Task.TaskKey taskKey,
+ ActivityManager.TaskDescription td, SystemServicesProxy ssp,
+ ActivityInfoHandle infoHandle) {
+ // Return the task description label if it exists
+ if (td != null && td.getLabel() != null) {
+ return td.getLabel();
+ }
+ // Return the cached activity label if it exists
+ String label = mActivityLabelCache.getAndInvalidateIfModified(taskKey);
+ if (label != null) {
+ return label;
+ }
+ // All short paths failed, load the label from the activity info and cache it
+ if (infoHandle.info == null) {
+ infoHandle.info = ssp.getActivityInfo(taskKey.baseIntent.getComponent(),
+ taskKey.userId);
+ }
+ if (infoHandle.info != null) {
+ label = ssp.getActivityLabel(infoHandle.info);
+ mActivityLabelCache.put(taskKey, label);
+ } else {
+ Log.w(TAG, "Missing ActivityInfo 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,
- Resources res, ActivityInfoHandle infoHandle, boolean preloadTask) {
+ ActivityManager.TaskDescription td, SystemServicesProxy ssp,
+ Resources res, ActivityInfoHandle infoHandle, boolean loadIfNotCached) {
// Return the cached activity icon if it exists
Drawable icon = mApplicationIconCache.getAndInvalidateIfModified(taskKey);
if (icon != null) {
return icon;
}
- // If we are preloading this task, continue to load the task description icon or the
- // activity icon
- if (preloadTask) {
-
+ if (loadIfNotCached) {
// Return and cache the task description icon if it exists
Drawable tdDrawable = mLoader.getTaskDescriptionIcon(taskKey, td.getInMemoryIcon(),
td.getIconFilename(), ssp, res);
@@ -367,36 +393,30 @@ public class RecentsTaskLoader {
}
}
}
- // If we couldn't load any icon, return null
+ // We couldn't load any icon
return null;
}
- /** Returns the activity label using as many cached values as we can. */
- public String getAndUpdateActivityLabel(Task.TaskKey taskKey,
- ActivityManager.TaskDescription td, SystemServicesProxy ssp,
- ActivityInfoHandle infoHandle) {
- // Return the task description label if it exists
- if (td != null && td.getLabel() != null) {
- return td.getLabel();
- }
- // Return the cached activity label if it exists
- String label = mActivityLabelCache.getAndInvalidateIfModified(taskKey);
- if (label != null) {
- return label;
+ /** Returns the bitmap using as many cached values as we can. */
+ public Bitmap getAndUpdateThumbnail(Task.TaskKey taskKey, SystemServicesProxy ssp,
+ boolean loadIfNotCached) {
+ // Return the cached thumbnail if it exists
+ Bitmap thumbnail = mThumbnailCache.getAndInvalidateIfModified(taskKey);
+ if (thumbnail != null) {
+ return thumbnail;
}
- // All short paths failed, load the label from the activity info and cache it
- if (infoHandle.info == null) {
- infoHandle.info = ssp.getActivityInfo(taskKey.baseIntent.getComponent(),
- taskKey.userId);
- }
- if (infoHandle.info != null) {
- label = ssp.getActivityLabel(infoHandle.info);
- mActivityLabelCache.put(taskKey, label);
- } else {
- Log.w(TAG, "Missing ActivityInfo for " + taskKey.baseIntent.getComponent()
- + " u=" + taskKey.userId);
+
+ RecentsConfiguration config = RecentsConfiguration.getInstance();
+ if (config.svelteLevel < RecentsConfiguration.SVELTE_DISABLE_LOADING && loadIfNotCached) {
+ // Load the thumbnail from the system
+ thumbnail = ssp.getTaskThumbnail(taskKey.id);
+ if (thumbnail != null) {
+ mThumbnailCache.put(taskKey, thumbnail);
+ return thumbnail;
+ }
}
- return label;
+ // We couldn't load any thumbnail
+ return null;
}
/** Returns the activity's primary color. */
@@ -408,116 +428,42 @@ public class RecentsTaskLoader {
return config.taskBarViewDefaultBackgroundColor;
}
- /** Reload the set of recent tasks */
- public SpaceNode reload(Context context, int preloadCount, boolean isTopTaskHome) {
- ArrayList<Task.TaskKey> taskKeys = new ArrayList<Task.TaskKey>();
- ArrayList<Task> tasksToLoad = new ArrayList<Task>();
- TaskStack stack = getTaskStack(mSystemServicesProxy, context.getResources(),
- -1, preloadCount, true, isTopTaskHome, taskKeys, tasksToLoad);
- SpaceNode root = new SpaceNode();
- root.setStack(stack);
-
- // Start the task loader and add all the tasks we need to load
- mLoader.start(context);
- mLoadQueue.addTasks(tasksToLoad);
-
- // Update the package monitor with the list of packages to listen for
- mPackageMonitor.setTasks(taskKeys);
+ /** Returns the size of the app icon cache. */
+ public int getApplicationIconCacheSize() {
+ return mMaxIconCacheSize;
+ }
- return root;
+ /** Returns the size of the thumbnail cache. */
+ public int getThumbnailCacheSize() {
+ return mMaxThumbnailCacheSize;
}
- /** Creates a lightweight stack of the current recent tasks, without thumbnails and icons. */
- public TaskStack getTaskStack(SystemServicesProxy ssp, Resources res,
- int preloadTaskId, int preloadTaskCount,
- boolean loadTaskThumbnails, boolean isTopTaskHome,
- List<Task.TaskKey> taskKeysOut, List<Task> tasksToLoadOut) {
+ /** Creates a new plan for loading the recent tasks. */
+ public RecentsTaskLoadPlan createLoadPlan(Context context) {
RecentsConfiguration config = RecentsConfiguration.getInstance();
- List<ActivityManager.RecentTaskInfo> tasks = getRecentTasks(ssp, isTopTaskHome);
- HashMap<Task.ComponentNameKey, ActivityInfoHandle> activityInfoCache =
- new HashMap<Task.ComponentNameKey, ActivityInfoHandle>();
- ArrayList<Task> tasksToAdd = new ArrayList<Task>();
- TaskStack stack = new TaskStack();
-
- int taskCount = tasks.size();
- for (int i = 0; i < taskCount; i++) {
- ActivityManager.RecentTaskInfo t = tasks.get(i);
-
- // Compose the task key
- Task.TaskKey taskKey = new Task.TaskKey(t.persistentId, t.baseIntent, t.userId,
- t.firstActiveTime, t.lastActiveTime);
-
- // Get an existing activity info handle if possible
- Task.ComponentNameKey cnKey = taskKey.getComponentNameKey();
- ActivityInfoHandle infoHandle;
- boolean hasCachedActivityInfo = false;
- if (activityInfoCache.containsKey(cnKey)) {
- infoHandle = activityInfoCache.get(cnKey);
- hasCachedActivityInfo = true;
- } else {
- infoHandle = new ActivityInfoHandle();
- }
-
- // Determine whether to preload this task
- boolean preloadTask = false;
- if (preloadTaskId > 0) {
- preloadTask = (t.id == preloadTaskId);
- } else if (preloadTaskCount > 0) {
- preloadTask = (i >= (taskCount - preloadTaskCount));
- }
-
- // Load the label, icon, and color
- String activityLabel = getAndUpdateActivityLabel(taskKey, t.taskDescription,
- ssp, infoHandle);
- Drawable activityIcon = getAndUpdateActivityIcon(taskKey, t.taskDescription,
- ssp, res, infoHandle, preloadTask);
- int activityColor = getActivityPrimaryColor(t.taskDescription, config);
+ RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(context, config, mSystemServicesProxy);
+ return plan;
+ }
- // Update the activity info cache
- if (!hasCachedActivityInfo && infoHandle.info != null) {
- activityInfoCache.put(cnKey, infoHandle);
- }
+ /** Preloads recents tasks using the specified plan to store the output. */
+ public void preloadTasks(RecentsTaskLoadPlan plan, boolean isTopTaskHome) {
+ plan.preloadPlan(this, isTopTaskHome);
+ }
- Bitmap icon = t.taskDescription != null
- ? t.taskDescription.getInMemoryIcon()
- : null;
- String iconFilename = t.taskDescription != null
- ? t.taskDescription.getIconFilename()
- : null;
-
- // Add the task to the stack
- Task task = new Task(taskKey, (t.id > -1), t.affiliatedTaskId, t.affiliatedTaskColor,
- activityLabel, activityIcon, activityColor, (i == (taskCount - 1)),
- config.lockToAppEnabled, icon, iconFilename);
-
- if (preloadTask && loadTaskThumbnails) {
- // Load the thumbnail from the cache if possible
- task.thumbnail = mThumbnailCache.getAndInvalidateIfModified(taskKey);
- if (task.thumbnail == null) {
- // Load the thumbnail from the system
- task.thumbnail = ssp.getTaskThumbnail(taskKey.id);
- if (task.thumbnail != null) {
- task.thumbnail.setHasAlpha(false);
- mThumbnailCache.put(taskKey, task.thumbnail);
- }
- }
- if (task.thumbnail == null && tasksToLoadOut != null) {
- // Either the task has changed since the last active time, or it was not
- // previously cached, so try and load the task anew.
- tasksToLoadOut.add(task);
- }
- }
+ /** Begins loading the heavy task data according to the specified options. */
+ public void loadTasks(Context context, RecentsTaskLoadPlan plan,
+ RecentsTaskLoadPlan.Options opts) {
+ if (opts == null) {
+ throw new RuntimeException("Requires load options");
+ }
+ plan.executePlan(opts, this, mLoadQueue);
+ if (!opts.onlyLoadForCache) {
+ mNumVisibleTasksLoaded = opts.numVisibleTasks;
+ mNumVisibleThumbnailsLoaded = opts.numVisibleTaskThumbnails;
- // Add to the list of task keys
- if (taskKeysOut != null) {
- taskKeysOut.add(taskKey);
- }
- // Add the task to the stack
- tasksToAdd.add(task);
+ // Start the loader
+ mLoader.start(context);
}
- stack.setTasks(tasksToAdd);
- stack.createAffiliatedGroupings(config);
- return stack;
}
/** Acquires the task resource data directly from the pool. */
@@ -573,28 +519,33 @@ public class RecentsTaskLoader {
* out of memory.
*/
public void onTrimMemory(int level) {
+ RecentsConfiguration config = RecentsConfiguration.getInstance();
switch (level) {
case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
// Stop the loader immediately when the UI is no longer visible
stopLoader();
- mThumbnailCache.trimToSize(Math.max(
- Constants.Values.RecentsTaskLoader.PreloadFirstTasksCount,
- mMaxThumbnailCacheSize / 2));
- mApplicationIconCache.trimToSize(Math.max(
- Constants.Values.RecentsTaskLoader.PreloadFirstTasksCount,
+ if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
+ mThumbnailCache.trimToSize(Math.max(mNumVisibleTasksLoaded,
+ mMaxThumbnailCacheSize / 2));
+ } else if (config.svelteLevel == RecentsConfiguration.SVELTE_LIMIT_CACHE) {
+ mThumbnailCache.trimToSize(mNumVisibleThumbnailsLoaded);
+ } else if (config.svelteLevel >= RecentsConfiguration.SVELTE_DISABLE_CACHE) {
+ mThumbnailCache.evictAll();
+ }
+ mApplicationIconCache.trimToSize(Math.max(mNumVisibleTasksLoaded,
mMaxIconCacheSize / 2));
break;
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
// We are leaving recents, so trim the data a bit
- mThumbnailCache.trimToSize(mMaxThumbnailCacheSize / 2);
- mApplicationIconCache.trimToSize(mMaxIconCacheSize / 2);
+ mThumbnailCache.trimToSize(Math.max(1, mMaxThumbnailCacheSize / 2));
+ mApplicationIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 2));
break;
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
// We are going to be low on memory
- mThumbnailCache.trimToSize(mMaxThumbnailCacheSize / 4);
- mApplicationIconCache.trimToSize(mMaxIconCacheSize / 4);
+ mThumbnailCache.trimToSize(Math.max(1, mMaxThumbnailCacheSize / 4));
+ mApplicationIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 4));
break;
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
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 a7e2b0b..55dfe45 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -129,7 +129,7 @@ public class Task {
TaskCallbacks mCb;
public Task() {
- // Only used by RecentsService for task rect calculations.
+ // Do nothing
}
public Task(TaskKey key, boolean isActive, int taskAffiliation, int taskAffiliationColor,
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 1e47b50..81f0cef 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -61,6 +61,14 @@ class FilteredTaskList {
}
}
+ /** Resets this FilteredTaskList. */
+ void reset() {
+ mTasks.clear();
+ mFilteredTasks.clear();
+ mTaskIndices.clear();
+ mFilter = null;
+ }
+
/** Removes the task filter and returns the previous touch state */
void removeFilter() {
mFilter = null;
@@ -190,6 +198,14 @@ public class TaskStack {
mCb = cb;
}
+ /** Resets this TaskStack. */
+ public void reset() {
+ mCb = null;
+ mTaskList.reset();
+ mGroups.clear();
+ mAffinitiesGroups.clear();
+ }
+
/** Adds a new task */
public void addTask(Task t) {
mTaskList.add(t);
@@ -236,6 +252,8 @@ public class TaskStack {
if (group.getTaskCount() == 0) {
removeGroup(group);
}
+ // Update the lock-to-app state
+ t.lockToThisTask = false;
if (mCb != null) {
// Notify that a task has been removed
mCb.onStackTaskRemoved(this, t, null);
@@ -255,6 +273,17 @@ public class TaskStack {
return mTaskList.getTasks().get(mTaskList.size() - 1);
}
+ /** Gets the task keys */
+ public ArrayList<Task.TaskKey> getTaskKeys() {
+ ArrayList<Task.TaskKey> taskKeys = new ArrayList<Task.TaskKey>();
+ ArrayList<Task> tasks = mTaskList.getTasks();
+ int taskCount = tasks.size();
+ for (int i = 0; i < taskCount; i++) {
+ taskKeys.add(tasks.get(i).key);
+ }
+ return taskKeys;
+ }
+
/** Gets the tasks */
public ArrayList<Task> getTasks() {
return mTaskList.getTasks();
@@ -453,4 +482,4 @@ public class TaskStack {
}
return str;
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java b/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java
index d2fdaff..fb05c01 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java
@@ -16,13 +16,10 @@
package com.android.systemui.recents.views;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
import android.graphics.Outline;
import android.graphics.Rect;
import android.view.View;
import android.view.ViewOutlineProvider;
-
import com.android.systemui.recents.RecentsConfiguration;
/* An outline provider that has a clip and outline that can be animated. */
@@ -31,35 +28,25 @@ public class AnimateableViewBounds extends ViewOutlineProvider {
RecentsConfiguration mConfig;
TaskView mSourceView;
- Rect mTmpRect = new Rect();
Rect mClipRect = new Rect();
Rect mClipBounds = new Rect();
- Rect mOutlineClipRect = new Rect();
int mCornerRadius;
float mAlpha = 1f;
final float mMinAlpha = 0.25f;
- ObjectAnimator mClipTopAnimator;
- ObjectAnimator mClipRightAnimator;
- ObjectAnimator mClipBottomAnimator;
-
public AnimateableViewBounds(TaskView source, int cornerRadius) {
mConfig = RecentsConfiguration.getInstance();
mSourceView = source;
mCornerRadius = cornerRadius;
- setClipTop(getClipTop());
- setClipRight(getClipRight());
setClipBottom(getClipBottom());
- setOutlineClipBottom(getOutlineClipBottom());
}
@Override
public void getOutline(View view, Outline outline) {
outline.setAlpha(mMinAlpha + mAlpha / (1f - mMinAlpha));
- outline.setRoundRect(Math.max(mClipRect.left, mOutlineClipRect.left),
- Math.max(mClipRect.top, mOutlineClipRect.top),
- mSourceView.getWidth() - Math.max(mClipRect.right, mOutlineClipRect.right),
- mSourceView.getHeight() - Math.max(mClipRect.bottom, mOutlineClipRect.bottom),
+ outline.setRoundRect(mClipRect.left, mClipRect.top,
+ mSourceView.getWidth() - mClipRect.right,
+ mSourceView.getHeight() - mClipRect.bottom,
mCornerRadius);
}
@@ -71,73 +58,6 @@ public class AnimateableViewBounds extends ViewOutlineProvider {
}
}
- /** Animates the top clip. */
- void animateClipTop(int top, int duration, ValueAnimator.AnimatorUpdateListener updateListener) {
- if (mClipTopAnimator != null) {
- mClipTopAnimator.removeAllListeners();
- mClipTopAnimator.cancel();
- }
- mClipTopAnimator = ObjectAnimator.ofInt(this, "clipTop", top);
- mClipTopAnimator.setDuration(duration);
- mClipTopAnimator.setInterpolator(mConfig.fastOutSlowInInterpolator);
- if (updateListener != null) {
- mClipTopAnimator.addUpdateListener(updateListener);
- }
- mClipTopAnimator.start();
- }
-
- /** Sets the top clip. */
- public void setClipTop(int top) {
- if (top != mClipRect.top) {
- mClipRect.top = top;
- mSourceView.invalidateOutline();
- updateClipBounds();
- }
- }
-
- /** Returns the top clip. */
- public int getClipTop() {
- return mClipRect.top;
- }
-
- /** Animates the right clip. */
- void animateClipRight(int right, int duration) {
- if (mClipRightAnimator != null) {
- mClipRightAnimator.removeAllListeners();
- mClipRightAnimator.cancel();
- }
- mClipRightAnimator = ObjectAnimator.ofInt(this, "clipRight", right);
- mClipRightAnimator.setDuration(duration);
- mClipRightAnimator.setInterpolator(mConfig.fastOutSlowInInterpolator);
- mClipRightAnimator.start();
- }
-
- /** Sets the right clip. */
- public void setClipRight(int right) {
- if (right != mClipRect.right) {
- mClipRect.right = right;
- mSourceView.invalidateOutline();
- updateClipBounds();
- }
- }
-
- /** Returns the right clip. */
- public int getClipRight() {
- return mClipRect.right;
- }
-
- /** Animates the bottom clip. */
- void animateClipBottom(int bottom, int duration) {
- if (mClipBottomAnimator != null) {
- mClipBottomAnimator.removeAllListeners();
- mClipBottomAnimator.cancel();
- }
- mClipBottomAnimator = ObjectAnimator.ofInt(this, "clipBottom", bottom);
- mClipBottomAnimator.setDuration(duration);
- mClipBottomAnimator.setInterpolator(mConfig.fastOutSlowInInterpolator);
- mClipBottomAnimator.start();
- }
-
/** Sets the bottom clip. */
public void setClipBottom(int bottom) {
if (bottom != mClipRect.bottom) {
@@ -145,7 +65,7 @@ public class AnimateableViewBounds extends ViewOutlineProvider {
mSourceView.invalidateOutline();
updateClipBounds();
if (!mConfig.useHardwareLayers) {
- mSourceView.mThumbnailView.updateVisibility(
+ mSourceView.mThumbnailView.updateThumbnailVisibility(
bottom - mSourceView.getPaddingBottom());
}
}
@@ -156,19 +76,6 @@ public class AnimateableViewBounds extends ViewOutlineProvider {
return mClipRect.bottom;
}
- /** Sets the outline bottom clip. */
- public void setOutlineClipBottom(int bottom) {
- if (bottom != mOutlineClipRect.bottom) {
- mOutlineClipRect.bottom = bottom;
- mSourceView.invalidateOutline();
- }
- }
-
- /** Gets the outline bottom clip. */
- public int getOutlineClipBottom() {
- return mOutlineClipRect.bottom;
- }
-
private void updateClipBounds() {
mClipBounds.set(mClipRect.left, mClipRect.top,
mSourceView.getWidth() - mClipRect.right,
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 6c22a3b..427ffe5 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -18,8 +18,6 @@ package com.android.systemui.recents.views;
import android.app.ActivityOptions;
import android.app.TaskStackBuilder;
-import android.content.ActivityNotFoundException;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
@@ -33,18 +31,16 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowInsets;
import android.widget.FrameLayout;
+
import com.android.systemui.recents.Constants;
import com.android.systemui.recents.RecentsConfiguration;
-import com.android.systemui.recents.misc.Console;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.model.RecentsPackageMonitor;
import com.android.systemui.recents.model.RecentsTaskLoader;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
import java.util.ArrayList;
-import java.util.HashSet;
/**
* This view is the the top level layout that contains TaskStacks (which are laid out according
@@ -59,6 +55,7 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
public void onTaskLaunchFailed();
public void onAllTaskViewsDismissed();
public void onExitToHomeAnimationTriggered();
+ public void onScreenPinningRequest();
}
RecentsConfiguration mConfig;
@@ -68,7 +65,6 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
ArrayList<TaskStack> mStacks;
View mSearchBar;
RecentsViewCallbacks mCb;
- boolean mAlreadyLaunchingTask;
public RecentsView(Context context) {
super(context);
@@ -100,42 +96,58 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
/** Set/get the bsp root node */
public void setTaskStacks(ArrayList<TaskStack> stacks) {
- // Remove all TaskStackViews (but leave the search bar)
+ 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 = childCount - 1; i >= 0; i--) {
- View v = getChildAt(i);
- if (v != mSearchBar) {
- removeViewAt(i);
+ for (int i = 0; i < childCount; i++) {
+ View child = getChildAt(i);
+ if (child != mSearchBar) {
+ stackViews.add((TaskStackView) child);
}
}
- // Create and add all the stacks for this partition of space.
+ // 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);
+ }
+ for (int i = stackViews.size() - 1; i >= numTaskStacksToKeep; i--) {
+ removeView(stackViews.get(i));
+ stackViews.remove(i);
+ }
+
+ // Update the stack views that we are keeping
+ for (int i = 0; i < numTaskStacksToKeep; i++) {
+ TaskStackView tsv = stackViews.get(i);
+ // If onRecentsHidden is not triggered, we need to the stack view again here
+ tsv.reset();
+ tsv.setStack(stacks.get(i));
+ }
+
+ // Add remaining/recreate stack views
mStacks = stacks;
- int numStacks = mStacks.size();
- for (int i = 0; i < numStacks; i++) {
- TaskStack stack = mStacks.get(i);
+ for (int i = stackViews.size(); i < numStacks; i++) {
+ TaskStack stack = stacks.get(i);
TaskStackView stackView = new TaskStackView(getContext(), stack);
stackView.setCallbacks(this);
- // Enable debug mode drawing
- if (mConfig.debugModeEnabled) {
- stackView.setDebugOverlay(mDebugOverlay);
- }
addView(stackView);
}
- // Reset the launched state
- mAlreadyLaunchingTask = false;
- }
-
- /** Removes all the task stack views from this recents view. */
- public void removeAllTaskStacks() {
- int childCount = getChildCount();
- for (int i = childCount - 1; i >= 0; i--) {
- View child = getChildAt(i);
- if (child != mSearchBar) {
- removeViewAt(i);
+ // 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);
+ }
}
}
+
+ // Trigger a new layout
+ requestLayout();
}
/** Launches the focused task from the first stack if possible */
@@ -192,6 +204,10 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
/** Requests all task stacks to start their enter-recents animation */
public void startEnterRecentsAnimation(ViewAnimation.TaskViewEnterContext ctx) {
+ // 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);
@@ -200,10 +216,14 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
stackView.startEnterRecentsAnimation(ctx);
}
}
+ ctx.postAnimationTrigger.decrement();
}
/** Requests all task stacks to start their exit-recents animation */
public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) {
+ // 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);
@@ -212,6 +232,7 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
stackView.startExitToHomeAnimation(ctx);
}
}
+ ctx.postAnimationTrigger.decrement();
// Notify of the exit animation
mCb.onExitToHomeAnimationTriggered();
@@ -339,7 +360,7 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
View child = getChildAt(i);
if (child != mSearchBar) {
TaskStackView stackView = (TaskStackView) child;
- stackView.focusNextTask(forward);
+ stackView.focusNextTask(forward, true);
break;
}
}
@@ -386,11 +407,6 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
if (mCb != null) {
mCb.onTaskViewClicked();
}
- // Skip if we are already launching tasks
- if (mAlreadyLaunchingTask) {
- return;
- }
- mAlreadyLaunchingTask = true;
// Upfront the processing of the thumbnail
TaskViewTransform transform = new TaskViewTransform();
@@ -451,7 +467,7 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
postDelayed(new Runnable() {
@Override
public void run() {
- ssp.lockCurrentTask();
+ mCb.onScreenPinningRequest();
}
}, 350);
mTriggered = true;
@@ -461,7 +477,7 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
}
opts = ActivityOptions.makeThumbnailAspectScaleUpAnimation(sourceView,
b, offsetX, offsetY, transform.rect.width(), transform.rect.height(),
- animStartedListener);
+ sourceView.getHandler(), animStartedListener);
}
final ActivityOptions launchOpts = opts;
@@ -475,7 +491,7 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
if (ssp.startActivityFromRecents(getContext(), task.key.id,
task.activityLabel, launchOpts)) {
if (launchOpts == null && lockToTask) {
- ssp.lockCurrentTask();
+ mCb.onScreenPinningRequest();
}
} else {
// Dismiss the task and return the user to home if we fail to
@@ -491,7 +507,7 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
// Launch the app right away if there is no task view, otherwise, animate the icon out first
if (tv == null) {
- post(launchRunnable);
+ launchRunnable.run();
} else {
if (!task.group.isFrontMostTask(task)) {
// For affiliated tasks that are behind other tasks, we must animate the front cards
@@ -500,7 +516,7 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
} else {
// Otherwise, we can start the task transition immediately
stackView.startLaunchTaskAnimation(tv, null, lockToTask);
- postDelayed(launchRunnable, 17);
+ launchRunnable.run();
}
}
}
@@ -526,8 +542,7 @@ 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,
- Utilities.isDocument(t.key.baseIntent));
+ loader.getSystemServicesProxy().removeTask(t.key.id);
}
@Override
@@ -535,6 +550,19 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
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();
+ }
+ }
+ }
+
@Override
public void onTaskStackFilterTriggered() {
// Hide the search bar
@@ -566,14 +594,14 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
/**** RecentsPackageMonitor.PackageCallbacks Implementation ****/
@Override
- public void onComponentRemoved(HashSet<ComponentName> cns) {
+ 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.onComponentRemoved(cns);
+ stackView.onPackagesChanged(monitor, packageName, userId);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java
index fa44551..0428b48 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java
@@ -50,7 +50,6 @@ public class SwipeHelper {
private float SWIPE_ESCAPE_VELOCITY = 100f; // dp/sec
private int DEFAULT_ESCAPE_ANIMATION_DURATION = 75; // ms
private int MAX_ESCAPE_ANIMATION_DURATION = 150; // ms
- private int MAX_DISMISS_VELOCITY = 2000; // dp/sec
private static final int SNAP_ANIM_LEN = SLOW_ANIMATIONS ? 1000 : 250; // ms
public static float ALPHA_FADE_START = 0.15f; // fraction of thumbnail width
@@ -350,8 +349,7 @@ public class SwipeHelper {
}
private void endSwipe(VelocityTracker velocityTracker) {
- float maxVelocity = MAX_DISMISS_VELOCITY * mDensityScale;
- velocityTracker.computeCurrentVelocity(1000 /* px/sec */, maxVelocity);
+ velocityTracker.computeCurrentVelocity(1000 /* px/sec */);
float velocity = getVelocity(velocityTracker);
float perpendicularVelocity = getPerpendicularVelocity(velocityTracker);
float escapeVelocity = SWIPE_ESCAPE_VELOCITY * mDensityScale;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java b/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java
index 162897e..1086160 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java
@@ -64,7 +64,9 @@ public class SystemBarScrimViews {
mStatusBarScrimView.setTranslationY(-mStatusBarScrimView.getMeasuredHeight());
mStatusBarScrimView.animate()
.translationY(0)
- .setStartDelay(mConfig.taskBarEnterAnimDelay)
+ .setStartDelay(mConfig.launchedFromHome ?
+ mConfig.transitionEnterFromHomeDelay :
+ mConfig.transitionEnterFromAppDelay)
.setDuration(mConfig.navBarScrimEnterDuration)
.setInterpolator(mConfig.quintOutInterpolator)
.withStartAction(new Runnable() {
@@ -79,7 +81,9 @@ public class SystemBarScrimViews {
mNavBarScrimView.setTranslationY(mNavBarScrimView.getMeasuredHeight());
mNavBarScrimView.animate()
.translationY(0)
- .setStartDelay(mConfig.taskBarEnterAnimDelay)
+ .setStartDelay(mConfig.launchedFromHome ?
+ mConfig.transitionEnterFromHomeDelay :
+ mConfig.transitionEnterFromAppDelay)
.setDuration(mConfig.navBarScrimEnterDuration)
.setInterpolator(mConfig.quintOutInterpolator)
.withStartAction(new Runnable() {
@@ -101,7 +105,7 @@ public class SystemBarScrimViews {
mStatusBarScrimView.animate()
.translationY(-mStatusBarScrimView.getMeasuredHeight())
.setStartDelay(0)
- .setDuration(mConfig.taskBarExitAnimDuration)
+ .setDuration(mConfig.taskViewExitToAppDuration)
.setInterpolator(mConfig.fastOutSlowInInterpolator)
.start();
}
@@ -109,7 +113,7 @@ public class SystemBarScrimViews {
mNavBarScrimView.animate()
.translationY(mNavBarScrimView.getMeasuredHeight())
.setStartDelay(0)
- .setDuration(mConfig.taskBarExitAnimDuration)
+ .setDuration(mConfig.taskViewExitToAppDuration)
.setInterpolator(mConfig.fastOutSlowInInterpolator)
.start();
}
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 40134da..169683f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -24,7 +24,6 @@ import android.graphics.Rect;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityEvent;
import android.widget.FrameLayout;
import com.android.systemui.R;
@@ -41,6 +40,7 @@ import com.android.systemui.recents.model.TaskStack;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Iterator;
/* The visual representation of a task stack view */
@@ -100,25 +100,11 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
};
- // A convenience runnable to return all views to the pool
- Runnable mReturnAllViewsToPoolRunnable = new Runnable() {
- @Override
- public void run() {
- int childCount = getChildCount();
- for (int i = childCount - 1; i >= 0; i--) {
- TaskView tv = (TaskView) getChildAt(i);
- mViewPool.returnViewToPool(tv);
- // Also hide the view since we don't need it anymore
- tv.setVisibility(View.INVISIBLE);
- }
- }
- };
-
public TaskStackView(Context context, TaskStack stack) {
super(context);
+ // Set the stack first
+ setStack(stack);
mConfig = RecentsConfiguration.getInstance();
- mStack = stack;
- mStack.setCallbacks(this);
mViewPool = new ViewPool<TaskView, Task>(context, this);
mInflater = LayoutInflater.from(context);
mLayoutAlgorithm = new TaskStackViewLayoutAlgorithm(mConfig);
@@ -144,11 +130,58 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
mCb = cb;
}
+ /** Sets the task stack */
+ void setStack(TaskStack stack) {
+ // Set the new stack
+ mStack = stack;
+ if (mStack != null) {
+ mStack.setCallbacks(this);
+ }
+ // Layout again with the new stack
+ requestLayout();
+ }
+
/** Sets the debug overlay */
public void setDebugOverlay(DebugOverlayView overlay) {
mDebugOverlay = overlay;
}
+ /** 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);
+ }
+
+ // Mark each task view for relayout
+ if (mViewPool != null) {
+ Iterator<TaskView> iter = mViewPool.poolViewIterator();
+ if (iter != null) {
+ while (iter.hasNext()) {
+ TaskView tv = iter.next();
+ tv.reset();
+ }
+ }
+ }
+
+ // Reset the stack state
+ mStack.reset();
+ mStackViewsDirty = true;
+ mStackViewsClipDirty = true;
+ mAwaitingFirstLayout = true;
+ mPrevAccessibilityFocusedIndex = -1;
+ if (mUIDozeTrigger != null) {
+ mUIDozeTrigger.stopDozing();
+ mUIDozeTrigger.resetTrigger();
+ }
+ mStackScroller.reset();
+ }
+
/** Requests that the views be synchronized with the model */
void requestSynchronizeStackViewsWithModel() {
requestSynchronizeStackViewsWithModel(0);
@@ -199,11 +232,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
float stackScroll,
int[] visibleRangeOut,
boolean boundTranslationsToRect) {
- // XXX: We should be intelligent about where to look for the visible stack range using the
- // current stack scroll.
- // XXX: We should log extra cases like the ones below where we don't expect to hit very often
- // XXX: Print out approximately how many indices we have to go through to find the first visible transform
-
int taskTransformCount = taskTransforms.size();
int taskCount = tasks.size();
int frontMostVisibleIndex = -1;
@@ -255,20 +283,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
return frontMostVisibleIndex != -1 && backMostVisibleIndex != -1;
}
- /**
- * Gets the stack transforms of a list of tasks, and returns the visible range of tasks. This
- * call is less optimal than calling updateStackTransforms directly.
- */
- private ArrayList<TaskViewTransform> getStackTransforms(ArrayList<Task> tasks,
- float stackScroll,
- int[] visibleRangeOut,
- boolean boundTranslationsToRect) {
- ArrayList<TaskViewTransform> taskTransforms = new ArrayList<TaskViewTransform>();
- updateStackTransforms(taskTransforms, tasks, stackScroll, visibleRangeOut,
- boundTranslationsToRect);
- return taskTransforms;
- }
-
/** Synchronizes the views with the model */
boolean synchronizeStackViewsWithModel() {
if (mStackViewsDirty) {
@@ -415,7 +429,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
/** Focuses the task at the specified index in the stack */
- void focusTask(int taskIndex, boolean scrollToNewPosition) {
+ void focusTask(int taskIndex, boolean scrollToNewPosition, final boolean animateFocusedState) {
// Return early if the task is already focused
if (taskIndex == mFocusedTaskIndex) return;
@@ -427,7 +441,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
TaskView tv = getChildViewForTask(t);
Runnable postScrollRunnable = null;
if (tv != null) {
- tv.setFocusedTask();
+ tv.setFocusedTask(animateFocusedState);
} else {
postScrollRunnable = new Runnable() {
@Override
@@ -435,7 +449,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
Task t = mStack.getTasks().get(mFocusedTaskIndex);
TaskView tv = getChildViewForTask(t);
if (tv != null) {
- tv.setFocusedTask();
+ tv.setFocusedTask(animateFocusedState);
}
}
};
@@ -455,30 +469,77 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
}
- /** Focuses the next task in the stack */
- void focusNextTask(boolean forward) {
+ /**
+ * 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() {
+ 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;
+ }
+ }
+ // If we can't find the center task, then use the front most index
+ if (mFocusedTaskIndex < 0 && childCount > 0) {
+ mFocusedTaskIndex = childCount - 1;
+ }
+ }
+ return mFocusedTaskIndex >= 0;
+ }
+
+ /**
+ * Focuses the next task in the stack.
+ * @param animateFocusedState determines whether to actually draw the highlight along with
+ * the change in focus, as well as whether to scroll to fit the
+ * task into view.
+ */
+ public void focusNextTask(boolean forward, boolean animateFocusedState) {
// Find the next index to focus
int numTasks = mStack.getTaskCount();
if (numTasks == 0) return;
- int nextFocusIndex = numTasks - 1;
- if (0 <= mFocusedTaskIndex && mFocusedTaskIndex < numTasks) {
- nextFocusIndex = Math.max(0, Math.min(numTasks - 1,
- mFocusedTaskIndex + (forward ? -1 : 1)));
+ int direction = (forward ? -1 : 1);
+ int newIndex = mFocusedTaskIndex + direction;
+ if (newIndex >= 0 && newIndex <= (numTasks - 1)) {
+ newIndex = Math.max(0, Math.min(numTasks - 1, newIndex));
+ focusTask(newIndex, true, animateFocusedState);
}
- focusTask(nextFocusIndex, true);
}
/** Dismisses the focused task. */
public void dismissFocusedTask() {
- // Return early if there is no focused task index
- if (mFocusedTaskIndex < 0) return;
+ // Return early if the focused task index is invalid
+ if (mFocusedTaskIndex < 0 || mFocusedTaskIndex >= mStack.getTaskCount()) {
+ mFocusedTaskIndex = -1;
+ return;
+ }
Task t = mStack.getTasks().get(mFocusedTaskIndex);
TaskView tv = getChildViewForTask(t);
tv.dismissTask();
}
+ /** Resets the focused task. */
+ void resetFocusedTask() {
+ if ((0 <= mFocusedTaskIndex) && (mFocusedTaskIndex < mStack.getTaskCount())) {
+ Task t = mStack.getTasks().get(mFocusedTaskIndex);
+ TaskView tv = getChildViewForTask(t);
+ if (tv != null) {
+ tv.unsetFocusedTask();
+ }
+ }
+ mFocusedTaskIndex = -1;
+ }
+
@Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event);
@@ -506,6 +567,11 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
@Override
+ public boolean onGenericMotionEvent(MotionEvent ev) {
+ return mTouchHandler.onGenericMotionEvent(ev);
+ }
+
+ @Override
public void computeScroll() {
mStackScroller.computeScroll();
// Synchronize the views
@@ -536,6 +602,14 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
/**
+ * Computes the maximum number of visible tasks and thumbnails. Requires that
+ * updateMinMaxScrollForStack() is called first.
+ */
+ public TaskStackViewLayoutAlgorithm.VisibilityReport computeStackVisibilityReport() {
+ return mLayoutAlgorithm.computeStackVisibilityReport(mStack.getTasks());
+ }
+
+ /**
* This is called with the full window width and height to allow stack view children to
* perform the full screen transition down.
*/
@@ -562,22 +636,18 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
TaskView tv = (TaskView) getChildAt(i);
- if (tv.isFullScreenView()) {
- tv.measure(widthMeasureSpec, heightMeasureSpec);
+ if (tv.getBackground() != null) {
+ tv.getBackground().getPadding(mTmpRect);
} else {
- if (tv.getBackground() != null) {
- tv.getBackground().getPadding(mTmpRect);
- } else {
- mTmpRect.setEmpty();
- }
- tv.measure(
- MeasureSpec.makeMeasureSpec(
- mLayoutAlgorithm.mTaskRect.width() + mTmpRect.left + mTmpRect.right,
- MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(
- mLayoutAlgorithm.mTaskRect.height() + mTmpRect.top + mTmpRect.bottom +
- tv.getMaxFooterHeight(), MeasureSpec.EXACTLY));
+ mTmpRect.setEmpty();
}
+ tv.measure(
+ MeasureSpec.makeMeasureSpec(
+ mLayoutAlgorithm.mTaskRect.width() + mTmpRect.left + mTmpRect.right,
+ MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(
+ mLayoutAlgorithm.mTaskRect.height() + mTmpRect.top + mTmpRect.bottom,
+ MeasureSpec.EXACTLY));
}
setMeasuredDimension(width, height);
@@ -594,20 +664,15 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
TaskView tv = (TaskView) getChildAt(i);
- if (tv.isFullScreenView()) {
- tv.layout(left, top, left + tv.getMeasuredWidth(), top + tv.getMeasuredHeight());
+ if (tv.getBackground() != null) {
+ tv.getBackground().getPadding(mTmpRect);
} else {
- if (tv.getBackground() != null) {
- tv.getBackground().getPadding(mTmpRect);
- } else {
- mTmpRect.setEmpty();
- }
- tv.layout(mLayoutAlgorithm.mTaskRect.left - mTmpRect.left,
- mLayoutAlgorithm.mTaskRect.top - mTmpRect.top,
- mLayoutAlgorithm.mTaskRect.right + mTmpRect.right,
- mLayoutAlgorithm.mTaskRect.bottom + mTmpRect.bottom +
- tv.getMaxFooterHeight());
+ mTmpRect.setEmpty();
}
+ tv.layout(mLayoutAlgorithm.mTaskRect.left - mTmpRect.left,
+ mLayoutAlgorithm.mTaskRect.top - mTmpRect.top,
+ mLayoutAlgorithm.mTaskRect.right + mTmpRect.right,
+ mLayoutAlgorithm.mTaskRect.bottom + mTmpRect.bottom);
}
if (mAwaitingFirstLayout) {
@@ -650,14 +715,20 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
mStartEnterAnimationContext = null;
}
- // When Alt-Tabbing, we scroll to and focus the previous task
+ // When Alt-Tabbing, focus the previous task (but leave the animation until we finish the
+ // enter animation).
if (mConfig.launchedWithAltTab) {
- if (mConfig.launchedFromHome) {
- focusTask(Math.max(0, mStack.getTaskCount() - 1), false);
+ if (mConfig.launchedFromAppWithThumbnail) {
+ focusTask(Math.max(0, mStack.getTaskCount() - 2), false,
+ mConfig.launchedHasConfigurationChanged);
} else {
- focusTask(Math.max(0, mStack.getTaskCount() - 2), false);
+ focusTask(Math.max(0, mStack.getTaskCount() - 1), false,
+ mConfig.launchedHasConfigurationChanged);
}
}
+
+ // Start dozing
+ mUIDozeTrigger.startDozing();
}
/** Requests this task stacks to start it's enter-recents animation */
@@ -702,16 +773,27 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
@Override
public void run() {
mStartEnterAnimationCompleted = true;
- // Start dozing
- mUIDozeTrigger.startDozing();
- // Focus the first view if accessibility is enabled
+ // Poke the dozer to restart the trigger after the animation completes
+ mUIDozeTrigger.poke();
+
RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
SystemServicesProxy ssp = loader.getSystemServicesProxy();
int childCount = getChildCount();
- if (childCount > 0 && ssp.isTouchExplorationEnabled()) {
- TaskView tv = ((TaskView) getChildAt(childCount - 1));
- tv.requestAccessibilityFocus();
- mPrevAccessibilityFocusedIndex = mStack.indexOfTask(tv.getTask());
+ if (childCount > 0) {
+ // Focus the first view if accessibility is enabled
+ if (ssp.isTouchExplorationEnabled()) {
+ TaskView tv = ((TaskView) getChildAt(childCount - 1));
+ tv.requestAccessibilityFocus();
+ mPrevAccessibilityFocusedIndex = mStack.indexOfTask(tv.getTask());
+ }
+ }
+
+ // Start the focus animation when alt-tabbing
+ if (mConfig.launchedWithAltTab && !mConfig.launchedHasConfigurationChanged) {
+ View tv = getChildAt(mFocusedTaskIndex);
+ if (tv != null) {
+ ((TaskView) tv).setFocusedTask(true);
+ }
}
}
});
@@ -731,9 +813,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
TaskView tv = (TaskView) getChildAt(i);
tv.startExitToHomeAnimation(ctx);
}
-
- // Add a runnable to the post animation ref counter to clear all the views
- ctx.postAnimationTrigger.addLastDecrementRunnable(mReturnAllViewsToPoolRunnable);
}
/** Animates a task view in this stack as it launches. */
@@ -753,6 +832,11 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
}
+ /** Final callback after Recents is finally hidden. */
+ void onRecentsHidden() {
+ reset();
+ }
+
public boolean isTransformedTouchPointInView(float x, float y, View child) {
return isTransformedTouchPointInView(x, y, child, null);
}
@@ -811,6 +895,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
TaskView frontTv = getChildViewForTask(newFrontMostTask);
if (frontTv != null) {
frontTv.onTaskBound(newFrontMostTask);
+ frontTv.fadeInActionButton(0, mConfig.taskViewEnterFromAppDuration);
}
}
@@ -916,27 +1001,23 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// Reset the view properties
tv.resetViewProperties();
+
+ // Reset the clip state of the task view
+ tv.setClipViewInStack(false);
}
@Override
public void prepareViewToLeavePool(TaskView tv, Task task, boolean isNewView) {
+ // It is possible for a view to be returned to the view pool before it is laid out,
+ // which means that we will need to relayout the view when it is first used next.
+ boolean requiresRelayout = tv.getWidth() <= 0 && !isNewView;
+
// Rebind the task and request that this task's data be filled into the TaskView
tv.onTaskBound(task);
- // Mark the launch task as fullscreen
- if (Constants.DebugFlags.App.EnableScreenshotAppTransition && mAwaitingFirstLayout) {
- if (task.isLaunchTarget) {
- tv.setIsFullScreen(true);
- }
- }
-
// Load the task data
RecentsTaskLoader.getInstance().loadTaskData(task);
- // Sanity check, the task view should always be clipping against the stack at this point,
- // but just in case, re-enable it here
- tv.setClipViewInStack(true);
-
// If the doze trigger has already fired, then update the state for this task view
if (mUIDozeTrigger.hasTriggered()) {
tv.setNoUserInteractionState();
@@ -964,13 +1045,17 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// Add/attach the view to the hierarchy
if (isNewView) {
addView(tv, insertIndex);
-
- // Set the callbacks and listeners for this new view
- tv.setTouchEnabled(true);
- tv.setCallbacks(this);
} else {
attachViewToParent(tv, insertIndex, tv.getLayoutParams());
+ if (requiresRelayout) {
+ tv.requestLayout();
+ }
}
+
+ // Set the new state for this view, including the callbacks and view clipping
+ tv.setCallbacks(this);
+ tv.setTouchEnabled(true);
+ tv.setClipViewInStack(true);
}
@Override
@@ -1018,14 +1103,18 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
tv.getTask().activityLabel));
// Remove the task from the view
mStack.removeTask(task);
- // If the dismissed task was focused, then we should focus the next task in front
+ // If the dismissed task was focused, then we should focus the new task in the same index
if (taskWasFocused) {
ArrayList<Task> tasks = mStack.getTasks();
- int nextTaskIndex = Math.min(tasks.size() - 1, taskIndex);
+ int nextTaskIndex = Math.min(tasks.size() - 1, taskIndex - 1);
if (nextTaskIndex >= 0) {
Task nextTask = tasks.get(nextTaskIndex);
TaskView nextTv = getChildViewForTask(nextTask);
- nextTv.setFocusedTask();
+ if (nextTv != null) {
+ // Focus the next task, and only animate the visible state if we are launched
+ // from Alt-Tab
+ nextTv.setFocusedTask(mConfig.launchedWithAltTab);
+ }
}
}
}
@@ -1038,11 +1127,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
@Override
- public void onTaskViewFullScreenTransitionCompleted() {
- requestSynchronizeStackViewsWithModel();
- }
-
- @Override
public void onTaskViewFocusChanged(TaskView tv, boolean focused) {
if (focused) {
mFocusedTaskIndex = mStack.indexOfTask(tv.getTask());
@@ -1061,12 +1145,16 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
/**** RecentsPackageMonitor.PackageCallbacks Implementation ****/
@Override
- public void onComponentRemoved(HashSet<ComponentName> cns) {
+ public void onPackagesChanged(RecentsPackageMonitor monitor, String packageName, int userId) {
+ // Compute which components need to be removed
+ HashSet<ComponentName> removedComponents = monitor.computeComponentsRemoved(
+ mStack.getTaskKeys(), packageName, userId);
+
// For other tasks, just remove them directly if they no longer exist
ArrayList<Task> tasks = mStack.getTasks();
for (int i = tasks.size() - 1; i >= 0; i--) {
final Task t = tasks.get(i);
- if (cns.contains(t.key.baseIntent.getComponent())) {
+ if (removedComponents.contains(t.key.baseIntent.getComponent())) {
TaskView tv = getChildViewForTask(t);
if (tv != null) {
// For visible children, defer removing the task until after the animation
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 31fc701..49b9129 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
@@ -35,6 +35,18 @@ public class TaskStackViewLayoutAlgorithm {
// These are all going to change
static final float StackPeekMinScale = 0.8f; // The min scale of the last card in the peek area
+ // A report of the visibility state of the stack
+ public class VisibilityReport {
+ public int numVisibleTasks;
+ public int numVisibleThumbnails;
+
+ /** Package level ctor */
+ VisibilityReport(int tasks, int thumbnails) {
+ numVisibleTasks = tasks;
+ numVisibleThumbnails = thumbnails;
+ }
+ }
+
RecentsConfiguration mConfig;
// The various rects that define the stack view
@@ -117,12 +129,12 @@ public class TaskStackViewLayoutAlgorithm {
float pTaskHeightOffset = pAtBottomOfStackRect -
screenYToCurveProgress(mStackVisibleRect.bottom - taskHeight);
float pNavBarOffset = pAtBottomOfStackRect -
- screenYToCurveProgress(mStackVisibleRect.bottom - (mStackVisibleRect.bottom - mStackRect.bottom));
+ screenYToCurveProgress(mStackVisibleRect.bottom - (mStackVisibleRect.bottom -
+ mStackRect.bottom));
// Update the task offsets
float pAtBackMostCardTop = 0.5f;
float pAtFrontMostCardTop = pAtBackMostCardTop;
- float pAtSecondFrontMostCardTop = pAtBackMostCardTop;
int taskCount = tasks.size();
for (int i = 0; i < taskCount; i++) {
Task task = tasks.get(i);
@@ -130,42 +142,89 @@ public class TaskStackViewLayoutAlgorithm {
if (i < (taskCount - 1)) {
// Increment the peek height
- float pPeek = task.group.isFrontMostTask(task) ? pBetweenAffiliateOffset :
- pWithinAffiliateOffset;
- pAtSecondFrontMostCardTop = pAtFrontMostCardTop;
+ float pPeek = task.group.isFrontMostTask(task) ?
+ pBetweenAffiliateOffset : pWithinAffiliateOffset;
pAtFrontMostCardTop += pPeek;
}
}
mMaxScrollP = pAtFrontMostCardTop - ((1f - pTaskHeightOffset - pNavBarOffset));
mMinScrollP = tasks.size() == 1 ? Math.max(mMaxScrollP, 0f) : 0f;
- if (launchedWithAltTab) {
- if (launchedFromHome) {
- // Center the top most task, since that will be focused first
- mInitialScrollP = pAtSecondFrontMostCardTop - 0.5f;
- } else {
- // Center the second top most task, since that will be focused first
- mInitialScrollP = pAtSecondFrontMostCardTop - 0.5f;
- }
+ if (launchedWithAltTab && launchedFromHome) {
+ // Center the top most task, since that will be focused first
+ mInitialScrollP = mMaxScrollP;
} else {
mInitialScrollP = pAtFrontMostCardTop - 0.825f;
}
- mInitialScrollP = Math.max(0, mInitialScrollP);
+ mInitialScrollP = Math.min(mMaxScrollP, Math.max(0, mInitialScrollP));
+ }
+
+ /**
+ * Computes the maximum number of visible tasks and thumbnails. Requires that
+ * computeMinMaxScroll() is called first.
+ */
+ public VisibilityReport computeStackVisibilityReport(ArrayList<Task> tasks) {
+ if (tasks.size() <= 1) {
+ return new VisibilityReport(1, 1);
+ }
+
+ // Walk backwards in the task stack and count the number of tasks and visible thumbnails
+ int taskHeight = mTaskRect.height();
+ int numVisibleTasks = 1;
+ int numVisibleThumbnails = 1;
+ float progress = mTaskProgressMap.get(tasks.get(tasks.size() - 1).key) - mInitialScrollP;
+ int prevScreenY = curveProgressToScreenY(progress);
+ for (int i = tasks.size() - 2; i >= 0; i--) {
+ Task task = tasks.get(i);
+ progress = mTaskProgressMap.get(task.key) - mInitialScrollP;
+ if (progress < 0) {
+ break;
+ }
+ boolean isFrontMostTaskInGroup = task.group.isFrontMostTask(task);
+ if (isFrontMostTaskInGroup) {
+ float scaleAtP = curveProgressToScale(progress);
+ int scaleYOffsetAtP = (int) (((1f - scaleAtP) * taskHeight) / 2);
+ int screenY = curveProgressToScreenY(progress) + scaleYOffsetAtP;
+ boolean hasVisibleThumbnail = (prevScreenY - screenY) > mConfig.taskBarHeight;
+ if (hasVisibleThumbnail) {
+ numVisibleThumbnails++;
+ numVisibleTasks++;
+ prevScreenY = screenY;
+ } else {
+ // Once we hit the next front most task that does not have a visible thumbnail,
+ // walk through remaining visible set
+ for (int j = i; j >= 0; j--) {
+ numVisibleTasks++;
+ progress = mTaskProgressMap.get(tasks.get(j).key) - mInitialScrollP;
+ if (progress < 0) {
+ break;
+ }
+ }
+ break;
+ }
+ } else if (!isFrontMostTaskInGroup) {
+ // Affiliated task, no thumbnail
+ numVisibleTasks++;
+ }
+ }
+ return new VisibilityReport(numVisibleTasks, numVisibleThumbnails);
}
/** Update/get the transform */
- public TaskViewTransform getStackTransform(Task task, float stackScroll, TaskViewTransform transformOut,
- TaskViewTransform prevTransform) {
+ public TaskViewTransform getStackTransform(Task task, float stackScroll,
+ TaskViewTransform transformOut, TaskViewTransform prevTransform) {
// Return early if we have an invalid index
if (task == null || !mTaskProgressMap.containsKey(task.key)) {
transformOut.reset();
return transformOut;
}
- return getStackTransform(mTaskProgressMap.get(task.key), stackScroll, transformOut, prevTransform);
+ return getStackTransform(mTaskProgressMap.get(task.key), stackScroll, transformOut,
+ prevTransform);
}
/** Update/get the transform */
- public TaskViewTransform getStackTransform(float taskProgress, float stackScroll, TaskViewTransform transformOut, TaskViewTransform prevTransform) {
+ public TaskViewTransform getStackTransform(float taskProgress, float stackScroll,
+ TaskViewTransform transformOut, TaskViewTransform prevTransform) {
float pTaskRelative = taskProgress - stackScroll;
float pBounded = Math.max(0, Math.min(pTaskRelative, 1f));
// If the task top is outside of the bounds below the screen, then immediately reset it
@@ -199,19 +258,16 @@ public class TaskStackViewLayoutAlgorithm {
return transformOut;
}
- /**
- * Returns the untransformed task view size.
- */
+ /** Returns the untransformed task view size. */
public Rect getUntransformedTaskViewSize() {
Rect tvSize = new Rect(mTaskRect);
tvSize.offsetTo(0, 0);
return tvSize;
}
- /**
- * Returns the scroll to such task top = 1f;
- */
+ /** Returns the scroll to such task top = 1f; */
float getStackScrollForTask(Task t) {
+ if (!mTaskProgressMap.containsKey(t.key)) return 0f;
return mTaskProgressMap.get(t.key);
}
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 5852b88..f7067be 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
@@ -23,6 +23,7 @@ import android.animation.ValueAnimator;
import android.content.Context;
import android.widget.OverScroller;
import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.misc.Utilities;
/* The scrolling logic for a TaskStackView */
public class TaskStackViewScroller {
@@ -38,6 +39,7 @@ public class TaskStackViewScroller {
OverScroller mScroller;
ObjectAnimator mScrollAnimator;
+ float mFinalAnimatedScroll;
public TaskStackViewScroller(Context context, RecentsConfiguration config, TaskStackViewLayoutAlgorithm layoutAlgorithm) {
mConfig = config;
@@ -46,6 +48,11 @@ public class TaskStackViewScroller {
setStackScroll(getStackScroll());
}
+ /** Resets the task scroller. */
+ void reset() {
+ mStackScrollP = 0f;
+ }
+
/** Sets the callbacks */
void setCallbacks(TaskStackViewScrollerCallbacks cb) {
mCb = cb;
@@ -69,9 +76,14 @@ public class TaskStackViewScroller {
mStackScrollP = s;
}
- /** Sets the current stack scroll to the initial state when you first enter recents */
- public void setStackScrollToInitialState() {
+ /**
+ * Sets the current stack scroll to the initial state when you first enter recents.
+ * @return whether the stack progress changed.
+ */
+ public boolean setStackScrollToInitialState() {
+ float prevStackScrollP = mStackScrollP;
setStackScroll(getBoundedStackScroll(mLayoutAlgorithm.mInitialScrollP));
+ return Float.compare(prevStackScrollP, mStackScrollP) != 0;
}
/** Bounds the current scroll if necessary */
@@ -128,10 +140,15 @@ public class TaskStackViewScroller {
/** Animates the stack scroll */
void animateScroll(float curScroll, float newScroll, final Runnable postRunnable) {
- // Abort any current animations
+ // Finish any current scrolling animations
+ if (mScrollAnimator != null && mScrollAnimator.isRunning()) {
+ setStackScroll(mFinalAnimatedScroll);
+ mScroller.startScroll(0, progressToScrollRange(mFinalAnimatedScroll), 0, 0, 0);
+ }
stopScroller();
stopBoundScrollAnimation();
+ mFinalAnimatedScroll = newScroll;
mScrollAnimator = ObjectAnimator.ofFloat(this, "stackScroll", curScroll, newScroll);
mScrollAnimator.setDuration(mConfig.taskStackScrollDuration);
mScrollAnimator.setInterpolator(mConfig.linearOutSlowInInterpolator);
@@ -155,10 +172,7 @@ public class TaskStackViewScroller {
/** Aborts any current stack scrolls */
void stopBoundScrollAnimation() {
- if (mScrollAnimator != null) {
- mScrollAnimator.removeAllListeners();
- mScrollAnimator.cancel();
- }
+ Utilities.cancelAnimationWithoutCallbacks(mScrollAnimator);
}
/**** OverScroller ****/
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 8f9b4c2..59e38f4 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -17,6 +17,7 @@
package com.android.systemui.recents.views;
import android.content.Context;
+import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
@@ -142,22 +143,21 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
// Initialize the velocity tracker
initOrResetVelocityTracker();
mVelocityTracker.addMovement(createMotionEventForStackScroll(ev));
- // Check if the scroller is finished yet
- mIsScrolling = mScroller.isScrolling();
break;
}
case MotionEvent.ACTION_MOVE: {
if (mActivePointerId == INACTIVE_POINTER_ID) break;
+ // Initialize the velocity tracker if necessary
+ initVelocityTrackerIfNotExists();
+ mVelocityTracker.addMovement(createMotionEventForStackScroll(ev));
+
int activePointerIndex = ev.findPointerIndex(mActivePointerId);
int y = (int) ev.getY(activePointerIndex);
int x = (int) ev.getX(activePointerIndex);
if (Math.abs(y - mInitialMotionY) > mScrollTouchSlop) {
// Save the touch move info
mIsScrolling = true;
- // Initialize the velocity tracker if necessary
- initVelocityTrackerIfNotExists();
- mVelocityTracker.addMovement(createMotionEventForStackScroll(ev));
// Disallow parents from intercepting touch events
final ViewParent parent = mSv.getParent();
if (parent != null) {
@@ -189,7 +189,6 @@ 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) {
@@ -237,6 +236,8 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
case MotionEvent.ACTION_MOVE: {
if (mActivePointerId == INACTIVE_POINTER_ID) break;
+ mVelocityTracker.addMovement(createMotionEventForStackScroll(ev));
+
int activePointerIndex = ev.findPointerIndex(mActivePointerId);
int x = (int) ev.getX(activePointerIndex);
int y = (int) ev.getY(activePointerIndex);
@@ -246,9 +247,6 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
if (!mIsScrolling) {
if (yTotal > mScrollTouchSlop) {
mIsScrolling = true;
- // Initialize the velocity tracker
- initOrResetVelocityTracker();
- mVelocityTracker.addMovement(createMotionEventForStackScroll(ev));
// Disallow parents from intercepting touch events
final ViewParent parent = mSv.getParent();
if (parent != null) {
@@ -267,11 +265,6 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
/ maxOverScroll));
}
mScroller.setStackScroll(curStackScroll + deltaP);
- if (mScroller.isScrollOutOfBounds()) {
- mVelocityTracker.clear();
- } else {
- mVelocityTracker.addMovement(createMotionEventForStackScroll(ev));
- }
}
mLastMotionX = x;
mLastMotionY = y;
@@ -280,20 +273,21 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
break;
}
case MotionEvent.ACTION_UP: {
- final VelocityTracker velocityTracker = mVelocityTracker;
- velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
- int velocity = (int) velocityTracker.getYVelocity(mActivePointerId);
+ mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+ int velocity = (int) mVelocityTracker.getYVelocity(mActivePointerId);
if (mIsScrolling && (Math.abs(velocity) > mMinimumVelocity)) {
- int overscrollRange = (int) (Math.min(1f,
- Math.abs((float) velocity / mMaximumVelocity)) *
- Constants.Values.TaskStackView.TaskStackOverscrollRange);
- // Fling scroll
- mScroller.mScroller.fling(0, mScroller.progressToScrollRange(mScroller.getStackScroll()),
+ float overscrollRangePct = Math.abs((float) velocity / mMaximumVelocity);
+ int overscrollRange = (int) (Math.min(1f, overscrollRangePct) *
+ (Constants.Values.TaskStackView.TaskStackMaxOverscrollRange -
+ Constants.Values.TaskStackView.TaskStackMinOverscrollRange));
+ mScroller.mScroller.fling(0,
+ mScroller.progressToScrollRange(mScroller.getStackScroll()),
0, velocity,
0, 0,
mScroller.progressToScrollRange(mSv.mLayoutAlgorithm.mMinScrollP),
mScroller.progressToScrollRange(mSv.mLayoutAlgorithm.mMaxScrollP),
- 0, overscrollRange);
+ 0, Constants.Values.TaskStackView.TaskStackMinOverscrollRange +
+ overscrollRange);
// Invalidate to kick off computeScroll
mSv.invalidate();
} else if (mScroller.isScrollOutOfBounds()) {
@@ -336,6 +330,30 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
return true;
}
+ /** Handles generic motion events */
+ public boolean onGenericMotionEvent(MotionEvent ev) {
+ if ((ev.getSource() & InputDevice.SOURCE_CLASS_POINTER) ==
+ InputDevice.SOURCE_CLASS_POINTER) {
+ int action = ev.getAction();
+ switch (action & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_SCROLL:
+ // 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()) {
+ mSv.focusNextTask(true, false);
+ }
+ } else {
+ if (mSv.ensureFocusedTask()) {
+ mSv.focusNextTask(false, false);
+ }
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
/**** SwipeHelper Implementation ****/
@Override
@@ -355,8 +373,6 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
tv.setClipViewInStack(false);
// Disallow touch events from this task view
tv.setTouchEnabled(false);
- // Hide the footer
- tv.animateFooterVisibility(false, mSv.mConfig.taskViewLockToAppShortAnimDuration);
// Disallow parents from intercepting touch events
final ViewParent parent = mSv.getParent();
if (parent != null) {
@@ -387,8 +403,6 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
tv.setClipViewInStack(true);
// Re-enable touch events from this task view
tv.setTouchEnabled(true);
- // Restore the footer
- tv.animateFooterVisibility(true, mSv.mConfig.taskViewLockToAppShortAnimDuration);
}
@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 2658176..faa728d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -21,23 +21,21 @@ import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.*;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.LayerDrawable;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewOutlineProvider;
import android.view.animation.AccelerateInterpolator;
import android.widget.FrameLayout;
import com.android.systemui.R;
-import com.android.systemui.recents.AlternateRecentsComponent;
import com.android.systemui.recents.Constants;
import com.android.systemui.recents.RecentsConfiguration;
-import com.android.systemui.recents.model.RecentsTaskLoader;
+import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.model.Task;
+import com.android.systemui.statusbar.phone.PhoneStatusBar;
/* A task view */
public class TaskView extends FrameLayout implements Task.TaskCallbacks,
- TaskViewFooter.TaskFooterViewCallbacks, View.OnClickListener, View.OnLongClickListener {
+ View.OnClickListener, View.OnLongClickListener {
/** The TaskView callbacks */
interface TaskViewCallbacks {
@@ -46,7 +44,6 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
public void onTaskViewClicked(TaskView tv, Task task, boolean lockToTask);
public void onTaskViewDismissed(TaskView tv);
public void onTaskViewClipStateChanged(TaskView tv);
- public void onTaskViewFullScreenTransitionCompleted();
public void onTaskViewFocusChanged(TaskView tv, boolean focused);
}
@@ -54,25 +51,23 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
float mTaskProgress;
ObjectAnimator mTaskProgressAnimator;
- ObjectAnimator mDimAnimator;
float mMaxDimScale;
- int mDim;
+ int mDimAlpha;
AccelerateInterpolator mDimInterpolator = new AccelerateInterpolator(1f);
- PorterDuffColorFilter mDimColorFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.MULTIPLY);
+ PorterDuffColorFilter mDimColorFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_ATOP);
+ Paint mDimLayerPaint = new Paint();
+ float mActionButtonTranslationZ;
Task mTask;
boolean mTaskDataLoaded;
boolean mIsFocused;
boolean mFocusAnimationsEnabled;
- boolean mIsFullScreenView;
boolean mClipViewInStack;
AnimateableViewBounds mViewBounds;
- Paint mLayerPaint = new Paint();
View mContent;
TaskViewThumbnail mThumbnailView;
TaskViewHeader mHeaderView;
- TaskViewFooter mFooterView;
View mActionButtonView;
TaskViewCallbacks mCb;
@@ -117,6 +112,14 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
mCb = cb;
}
+ /** Resets this TaskView for reuse. */
+ void reset() {
+ resetViewProperties();
+ resetNoUserInteractionState();
+ setClipViewInStack(false);
+ setCallbacks(null);
+ }
+
/** Gets the task */
Task getTask() {
return mTask;
@@ -133,7 +136,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
mContent = findViewById(R.id.task_view_content);
mHeaderView = (TaskViewHeader) findViewById(R.id.task_view_bar);
mThumbnailView = (TaskViewThumbnail) findViewById(R.id.task_view_thumbnail);
- mThumbnailView.enableTaskBarClip(mHeaderView);
+ mThumbnailView.updateClipToTaskBar(mHeaderView);
mActionButtonView = findViewById(R.id.lock_to_app_fab);
mActionButtonView.setOutlineProvider(new ViewOutlineProvider() {
@Override
@@ -142,9 +145,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
outline.setOval(0, 0, mActionButtonView.getWidth(), mActionButtonView.getHeight());
}
});
- if (mFooterView != null) {
- mFooterView.setCallbacks(this);
- }
+ mActionButtonTranslationZ = mActionButtonView.getTranslationZ();
}
@Override
@@ -159,29 +160,16 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
mContent.measure(MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY));
- // Measure the bar view, thumbnail, and footer
+ // Measure the bar view, and action button
mHeaderView.measure(MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(mConfig.taskBarHeight, MeasureSpec.EXACTLY));
- if (mFooterView != null) {
- mFooterView.measure(
- MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(mConfig.taskViewLockToAppButtonHeight,
- MeasureSpec.EXACTLY));
- }
mActionButtonView.measure(
MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(heightWithoutPadding, MeasureSpec.AT_MOST));
- if (mIsFullScreenView) {
- // Measure the thumbnail height to be the full dimensions
- mThumbnailView.measure(
- MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(heightWithoutPadding, MeasureSpec.EXACTLY));
- } else {
- // Measure the thumbnail to be square
- mThumbnailView.measure(
- MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY));
- }
+ // Measure the thumbnail to be square
+ mThumbnailView.measure(
+ MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY));
setMeasuredDimension(width, height);
invalidateOutline();
}
@@ -193,25 +181,12 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform, int duration,
ValueAnimator.AnimatorUpdateListener updateCallback) {
- // If we are a full screen view, then only update the Z to keep it in order
- // XXX: Also update/animate the dim as well
- if (mIsFullScreenView) {
- if (!mConfig.fakeShadows &&
- toTransform.hasTranslationZChangedFrom(getTranslationZ())) {
- setTranslationZ(toTransform.translationZ);
- }
- return;
- }
-
// Apply the transform
toTransform.applyToTaskView(this, duration, mConfig.fastOutSlowInInterpolator, false,
!mConfig.fakeShadows, updateCallback);
// Update the task progress
- if (mTaskProgressAnimator != null) {
- mTaskProgressAnimator.removeAllListeners();
- mTaskProgressAnimator.cancel();
- }
+ Utilities.cancelAnimationWithoutCallbacks(mTaskProgressAnimator);
if (duration <= 0) {
setTaskProgress(toTransform.p);
} else {
@@ -225,7 +200,14 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
/** Resets this view's properties */
void resetViewProperties() {
setDim(0);
+ setLayerType(View.LAYER_TYPE_NONE, null);
TaskViewTransform.reset(this);
+ if (mActionButtonView != null) {
+ mActionButtonView.setScaleX(1f);
+ mActionButtonView.setScaleY(1f);
+ mActionButtonView.setAlpha(1f);
+ mActionButtonView.setTranslationZ(mActionButtonTranslationZ);
+ }
}
/**
@@ -253,22 +235,14 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask,
boolean occludesLaunchTarget, int offscreenY) {
int initialDim = getDim();
- if (mConfig.launchedFromAppWithScreenshot) {
- if (isTaskViewLaunchTargetTask) {
- // Hide the footer during the transition in, and animate it out afterwards?
- if (mFooterView != null) {
- mFooterView.animateFooterVisibility(false, 0);
- }
- } else {
- // Don't do anything for the side views when animating in
- }
-
+ if (mConfig.launchedHasConfigurationChanged) {
+ // Just load the views as-is
} else if (mConfig.launchedFromAppWithThumbnail) {
if (isTaskViewLaunchTargetTask) {
- // Hide the action button if it exists
- mActionButtonView.setAlpha(0f);
// Set the dim to 0 so we can animate it in
initialDim = 0;
+ // Hide the action button
+ mActionButtonView.setAlpha(0f);
} else if (occludesLaunchTarget) {
// Move the task view off screen (below) so we can animate it in
setTranslationY(offscreenY);
@@ -292,105 +266,31 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
final TaskViewTransform transform = ctx.currentTaskTransform;
int startDelay = 0;
- if (mConfig.launchedFromAppWithScreenshot) {
- if (mTask.isLaunchTarget) {
- Rect taskRect = ctx.currentTaskRect;
- int duration = mConfig.taskViewEnterFromHomeDuration * 10;
- int windowInsetTop = mConfig.systemInsets.top; // XXX: Should be for the window
- float taskScale = ((float) taskRect.width() / getMeasuredWidth()) * transform.scale;
- float scaledYOffset = ((1f - taskScale) * getMeasuredHeight()) / 2;
- float scaledWindowInsetTop = (int) (taskScale * windowInsetTop);
- float scaledTranslationY = taskRect.top + transform.translationY -
- (scaledWindowInsetTop + scaledYOffset);
- startDelay = mConfig.taskViewEnterFromHomeStaggerDelay;
-
- // Animate the top clip
- mViewBounds.animateClipTop(windowInsetTop, duration,
- new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- int y = (Integer) animation.getAnimatedValue();
- mHeaderView.setTranslationY(y);
- }
- });
- // Animate the bottom or right clip
- int size = Math.round((taskRect.width() / taskScale));
- if (mConfig.hasHorizontalLayout()) {
- mViewBounds.animateClipRight(getMeasuredWidth() - size, duration);
- } else {
- mViewBounds.animateClipBottom(getMeasuredHeight() - (windowInsetTop + size), duration);
- }
- // Animate the task bar of the first task view
- animate()
- .scaleX(taskScale)
- .scaleY(taskScale)
- .translationY(scaledTranslationY)
- .setDuration(duration)
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- setIsFullScreen(false);
- requestLayout();
-
- // Reset the clip
- mViewBounds.setClipTop(0);
- mViewBounds.setClipBottom(0);
- mViewBounds.setClipRight(0);
- // Reset the bar translation
- mHeaderView.setTranslationY(0);
- // Animate the footer into view (if it is the front most task)
- animateFooterVisibility(true, mConfig.taskBarEnterAnimDuration);
-
- // Unbind the thumbnail from the screenshot
- RecentsTaskLoader.getInstance().loadTaskData(mTask);
- // Recycle the full screen screenshot
- AlternateRecentsComponent.consumeLastScreenshot();
-
- mCb.onTaskViewFullScreenTransitionCompleted();
-
- // Decrement the post animation trigger
- ctx.postAnimationTrigger.decrement();
- }
- })
- .start();
- } else {
- // Animate the footer into view
- animateFooterVisibility(true, 0);
- }
- ctx.postAnimationTrigger.increment();
-
- } else if (mConfig.launchedFromAppWithThumbnail) {
+ if (mConfig.launchedFromAppWithThumbnail) {
if (mTask.isLaunchTarget) {
// Animate the dim/overlay
if (Constants.DebugFlags.App.EnableThumbnailAlphaOnFrontmost) {
// Animate the thumbnail alpha before the dim animation (to prevent updating the
// hardware layer)
- mThumbnailView.startEnterRecentsAnimation(mConfig.taskBarEnterAnimDelay,
+ mThumbnailView.startEnterRecentsAnimation(mConfig.transitionEnterFromAppDelay,
new Runnable() {
@Override
public void run() {
- animateDimToProgress(0, mConfig.taskBarEnterAnimDuration,
+ animateDimToProgress(0, mConfig.taskViewEnterFromAppDuration,
ctx.postAnimationTrigger.decrementOnAnimationEnd());
}
});
} else {
// Immediately start the dim animation
- animateDimToProgress(mConfig.taskBarEnterAnimDelay,
- mConfig.taskBarEnterAnimDuration,
+ animateDimToProgress(mConfig.transitionEnterFromAppDelay,
+ mConfig.taskViewEnterFromAppDuration,
ctx.postAnimationTrigger.decrementOnAnimationEnd());
}
ctx.postAnimationTrigger.increment();
- // Animate the footer into view
- animateFooterVisibility(true, mConfig.taskBarEnterAnimDuration);
-
// Animate the action button in
- mActionButtonView.animate().alpha(1f)
- .setStartDelay(mConfig.taskBarEnterAnimDelay)
- .setDuration(mConfig.taskBarEnterAnimDuration)
- .setInterpolator(mConfig.fastOutLinearInInterpolator)
- .withLayer()
- .start();
+ fadeInActionButton(mConfig.transitionEnterFromAppDelay,
+ mConfig.taskViewEnterFromAppDuration);
} else {
// Animate the task up if it was occluding the launch target
if (ctx.currentTaskOccludesLaunchTarget) {
@@ -398,7 +298,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
setAlpha(0f);
animate().alpha(1f)
.translationY(transform.translationY)
- .setStartDelay(mConfig.taskBarEnterAnimDelay)
+ .setStartDelay(mConfig.transitionEnterFromAppDelay)
.setUpdateListener(null)
.setInterpolator(mConfig.fastOutSlowInInterpolator)
.setDuration(mConfig.taskViewEnterFromHomeDuration)
@@ -413,12 +313,12 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
ctx.postAnimationTrigger.increment();
}
}
- startDelay = mConfig.taskBarEnterAnimDelay;
+ startDelay = mConfig.transitionEnterFromAppDelay;
} else if (mConfig.launchedFromHome) {
// Animate the tasks up
int frontIndex = (ctx.currentStackViewCount - ctx.currentStackViewIndex - 1);
- int delay = mConfig.taskViewEnterFromHomeDelay +
+ int delay = mConfig.transitionEnterFromHomeDelay +
frontIndex * mConfig.taskViewEnterFromHomeStaggerDelay;
setScaleX(transform.scale);
@@ -442,14 +342,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
})
.start();
ctx.postAnimationTrigger.increment();
-
- // Animate the footer into view
- animateFooterVisibility(true, mConfig.taskViewEnterFromHomeDuration);
startDelay = delay;
-
- } else {
- // Animate the footer into view
- animateFooterVisibility(true, 0);
}
// Enable the focus animations from this point onwards so that they aren't affected by the
@@ -459,7 +352,20 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
public void run() {
enableFocusAnimations();
}
- }, (startDelay / 2));
+ }, startDelay);
+ }
+
+ public void fadeInActionButton(int delay, int duration) {
+ // Hide the action button
+ mActionButtonView.setAlpha(0f);
+
+ // Animate the action button in
+ mActionButtonView.animate().alpha(1f)
+ .setStartDelay(delay)
+ .setDuration(duration)
+ .setInterpolator(PhoneStatusBar.ALPHA_IN)
+ .withLayer()
+ .start();
}
/** Animates this task view as it leaves recents by pressing home. */
@@ -483,9 +389,9 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
mThumbnailView.startLaunchTaskAnimation(postAnimRunnable);
// Animate the dim
- if (mDim > 0) {
+ if (mDimAlpha > 0) {
ObjectAnimator anim = ObjectAnimator.ofInt(this, "dim", 0);
- anim.setDuration(mConfig.taskBarExitAnimDuration);
+ anim.setDuration(mConfig.taskViewExitToAppDuration);
anim.setInterpolator(mConfig.fastOutLinearInInterpolator);
anim.start();
}
@@ -500,7 +406,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
mActionButtonView.animate()
.alpha(0f)
.setStartDelay(0)
- .setDuration(mConfig.taskBarExitAnimDuration)
+ .setDuration(mConfig.taskViewExitToAppDuration)
.setInterpolator(mConfig.fastOutLinearInInterpolator)
.withLayer()
.start();
@@ -515,7 +421,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
.setStartDelay(0)
.setUpdateListener(null)
.setInterpolator(mConfig.fastOutLinearInInterpolator)
- .setDuration(mConfig.taskBarExitAnimDuration)
+ .setDuration(mConfig.taskViewExitToAppDuration)
.start();
}
}
@@ -559,6 +465,11 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
mHeaderView.setNoUserInteractionState();
}
+ /** Resets the state tracking that the user has not interacted with the stack after a certain time. */
+ void resetNoUserInteractionState() {
+ mHeaderView.resetNoUserInteractionState();
+ }
+
/** Dismisses this task. */
void dismissTask() {
// Animate out the view and call the callback
@@ -566,26 +477,11 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
startDeleteTaskAnimation(new Runnable() {
@Override
public void run() {
- mCb.onTaskViewDismissed(tv);
+ if (mCb != null) {
+ mCb.onTaskViewDismissed(tv);
+ }
}
});
- // Hide the footer
- animateFooterVisibility(false, mConfig.taskViewRemoveAnimDuration);
- }
-
- /** Sets whether this task view is full screen or not. */
- void setIsFullScreen(boolean isFullscreen) {
- mIsFullScreenView = isFullscreen;
- mHeaderView.setIsFullscreen(isFullscreen);
- if (isFullscreen) {
- // If we are full screen, then disable the bottom outline clip for the footer
- mViewBounds.setOutlineClipBottom(0);
- }
- }
-
- /** Returns whether this task view should currently be drawn as a full screen view. */
- boolean isFullScreenView() {
- return mIsFullScreenView;
}
/**
@@ -593,35 +489,16 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
* view.
*/
boolean shouldClipViewInStack() {
- return mClipViewInStack && !mIsFullScreenView && (getVisibility() == View.VISIBLE);
+ return mClipViewInStack && (getVisibility() == View.VISIBLE);
}
/** Sets whether this view should be clipped, or clipped against. */
void setClipViewInStack(boolean clip) {
if (clip != mClipViewInStack) {
mClipViewInStack = clip;
- mCb.onTaskViewClipStateChanged(this);
- }
- }
-
- /** Gets the max footer height. */
- public int getMaxFooterHeight() {
- if (mFooterView != null) {
- return mFooterView.mMaxFooterHeight;
- } else {
- return 0;
- }
- }
-
- /** Animates the footer into and out of view. */
- void animateFooterVisibility(boolean visible, int duration) {
- // Hide the footer if we are a full screen view
- if (mIsFullScreenView) return;
- // Hide the footer if the current task can not be locked to
- if (!mTask.lockToTaskEnabled || !mTask.lockToThisTask) return;
- // Otherwise, animate the visibility
- if (mFooterView != null) {
- mFooterView.animateFooterVisibility(visible, duration);
+ if (mCb != null) {
+ mCb.onTaskViewClipStateChanged(this);
+ }
}
}
@@ -639,26 +516,16 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
/** Returns the current dim. */
public void setDim(int dim) {
- mDim = dim;
- if (mDimAnimator != null) {
- mDimAnimator.removeAllListeners();
- mDimAnimator.cancel();
- }
+ mDimAlpha = dim;
if (mConfig.useHardwareLayers) {
// Defer setting hardware layers if we have not yet measured, or there is no dim to draw
if (getMeasuredWidth() > 0 && getMeasuredHeight() > 0) {
- if (mDimAnimator != null) {
- mDimAnimator.removeAllListeners();
- mDimAnimator.cancel();
- }
-
- int inverse = 255 - mDim;
- mDimColorFilter.setColor(Color.argb(0xFF, inverse, inverse, inverse));
- mLayerPaint.setColorFilter(mDimColorFilter);
- mContent.setLayerType(LAYER_TYPE_HARDWARE, mLayerPaint);
+ mDimColorFilter.setColor(Color.argb(mDimAlpha, 0, 0, 0));
+ mDimLayerPaint.setColorFilter(mDimColorFilter);
+ mContent.setLayerType(LAYER_TYPE_HARDWARE, mDimLayerPaint);
}
} else {
- float dimAlpha = mDim / 255.0f;
+ float dimAlpha = mDimAlpha / 255.0f;
if (mThumbnailView != null) {
mThumbnailView.setDimAlpha(dimAlpha);
}
@@ -670,7 +537,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
/** Returns the current dim. */
public int getDim() {
- return mDim;
+ return mDimAlpha;
}
/** Animates the dim to the task progress. */
@@ -706,16 +573,18 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
* if the view is not currently visible, or we are in touch state (where we still want to keep
* track of focus).
*/
- public void setFocusedTask() {
+ public void setFocusedTask(boolean animateFocusedState) {
mIsFocused = true;
if (mFocusAnimationsEnabled) {
// Focus the header bar
- mHeaderView.onTaskViewFocusChanged(true);
+ mHeaderView.onTaskViewFocusChanged(true, animateFocusedState);
}
// Update the thumbnail alpha with the focus
mThumbnailView.onFocusChanged(true);
// Call the callback
- mCb.onTaskViewFocusChanged(this, true);
+ if (mCb != null) {
+ mCb.onTaskViewFocusChanged(this, true);
+ }
// Workaround, we don't always want it focusable in touch mode, but we want the first task
// to be focused after the enter-recents animation, which can be triggered from either touch
// or keyboard
@@ -732,13 +601,15 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
mIsFocused = false;
if (mFocusAnimationsEnabled) {
// Un-focus the header bar
- mHeaderView.onTaskViewFocusChanged(false);
+ mHeaderView.onTaskViewFocusChanged(false, true);
}
// Update the thumbnail alpha with the focus
mThumbnailView.onFocusChanged(false);
// Call the callback
- mCb.onTaskViewFocusChanged(this, false);
+ if (mCb != null) {
+ mCb.onTaskViewFocusChanged(this, false);
+ }
invalidate();
}
@@ -766,7 +637,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
mFocusAnimationsEnabled = true;
if (mIsFocused && !wasFocusAnimationsEnabled) {
// Re-notify the header if we were focused and animations were not previously enabled
- mHeaderView.onTaskViewFocusChanged(true);
+ mHeaderView.onTaskViewFocusChanged(true, true);
}
}
@@ -776,15 +647,12 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
public void onTaskBound(Task t) {
mTask = t;
mTask.setCallbacks(this);
- if (getMeasuredWidth() == 0) {
- // If we haven't yet measured, we should just set the footer height with any animation
- animateFooterVisibility(t.lockToThisTask, 0);
- } else {
- animateFooterVisibility(t.lockToThisTask, mConfig.taskViewLockToAppLongAnimDuration);
- }
- // Hide the action button if lock to app is disabled
- if (!t.lockToTaskEnabled && mActionButtonView.getVisibility() != View.GONE) {
- mActionButtonView.setVisibility(View.GONE);
+
+ // Hide the action button if lock to app is disabled for this view
+ int lockButtonVisibility = (!t.lockToTaskEnabled || !t.lockToThisTask) ? GONE : VISIBLE;
+ if (mActionButtonView.getVisibility() != lockButtonVisibility) {
+ mActionButtonView.setVisibility(lockButtonVisibility);
+ requestLayout();
}
}
@@ -792,18 +660,11 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
public void onTaskDataLoaded() {
if (mThumbnailView != null && mHeaderView != null) {
// Bind each of the views to the new task data
- if (mIsFullScreenView) {
- mThumbnailView.bindToScreenshot(AlternateRecentsComponent.getLastScreenshot());
- } else {
- mThumbnailView.rebindToTask(mTask);
- }
+ mThumbnailView.rebindToTask(mTask);
mHeaderView.rebindToTask(mTask);
// Rebind any listeners
mHeaderView.mApplicationIcon.setOnClickListener(this);
mHeaderView.mDismissButton.setOnClickListener(this);
- if (mFooterView != null) {
- mFooterView.setOnClickListener(this);
- }
mActionButtonView.setOnClickListener(this);
if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) {
if (mConfig.developerOptionsEnabled) {
@@ -824,9 +685,6 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
// Unbind any listeners
mHeaderView.mApplicationIcon.setOnClickListener(null);
mHeaderView.mDismissButton.setOnClickListener(null);
- if (mFooterView != null) {
- mFooterView.setOnClickListener(null);
- }
mActionButtonView.setOnClickListener(null);
if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) {
mHeaderView.mApplicationIcon.setOnLongClickListener(null);
@@ -840,19 +698,6 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
setOnClickListener(enabled ? this : null);
}
- /**** TaskViewFooter.TaskFooterViewCallbacks ****/
-
- @Override
- public void onTaskFooterHeightChanged(int height, int maxHeight) {
- if (mIsFullScreenView) {
- // Disable the bottom outline clip when fullscreen
- mViewBounds.setOutlineClipBottom(0);
- } else {
- // Update the bottom clip in our outline provider
- mViewBounds.setOutlineClipBottom(maxHeight - height);
- }
- }
-
/**** View.OnClickListener Implementation ****/
@Override
@@ -865,7 +710,9 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
@Override
public void run() {
if (Constants.DebugFlags.App.EnableTaskFiltering && v == mHeaderView.mApplicationIcon) {
- mCb.onTaskViewAppIconClicked(tv);
+ if (mCb != null) {
+ mCb.onTaskViewAppIconClicked(tv);
+ }
} else if (v == mHeaderView.mDismissButton) {
dismissTask();
}
@@ -876,8 +723,9 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
// Reset the translation of the action button before we animate it out
mActionButtonView.setTranslationZ(0f);
}
- mCb.onTaskViewClicked(tv, tv.getTask(),
- (v == mFooterView || v == mActionButtonView));
+ if (mCb != null) {
+ mCb.onTaskViewClicked(tv, tv.getTask(), (v == mActionButtonView));
+ }
}
}
@@ -886,8 +734,10 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
@Override
public boolean onLongClick(View v) {
if (v == mHeaderView.mApplicationIcon) {
- mCb.onTaskViewAppInfoClicked(this);
- return true;
+ if (mCb != null) {
+ mCb.onTaskViewAppInfoClicked(this);
+ return true;
+ }
}
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewFooter.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewFooter.java
deleted file mode 100644
index 324169e..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewFooter.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.views;
-
-import android.animation.ObjectAnimator;
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.FrameLayout;
-import com.android.systemui.recents.RecentsConfiguration;
-
-
-/** The task footer view */
-public class TaskViewFooter extends FrameLayout {
-
- interface TaskFooterViewCallbacks {
- public void onTaskFooterHeightChanged(int height, int maxHeight);
- }
-
- RecentsConfiguration mConfig;
-
- TaskFooterViewCallbacks mCb;
- int mFooterHeight;
- int mMaxFooterHeight;
- ObjectAnimator mFooterAnimator;
-
- public TaskViewFooter(Context context) {
- this(context, null);
- }
-
- public TaskViewFooter(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public TaskViewFooter(Context context, AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
- }
-
- public TaskViewFooter(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- mConfig = RecentsConfiguration.getInstance();
- mMaxFooterHeight = mConfig.taskViewLockToAppButtonHeight;
- setFooterHeight(getFooterHeight());
- }
-
- /** Sets the callbacks for when the footer height changes. */
- void setCallbacks(TaskFooterViewCallbacks cb) {
- mCb = cb;
- mCb.onTaskFooterHeightChanged(mFooterHeight, mMaxFooterHeight);
- }
-
- /** Sets the footer height. */
- public void setFooterHeight(int footerHeight) {
- if (footerHeight != mFooterHeight) {
- mFooterHeight = footerHeight;
- mCb.onTaskFooterHeightChanged(footerHeight, mMaxFooterHeight);
- }
- }
-
- /** Gets the footer height. */
- public int getFooterHeight() {
- return mFooterHeight;
- }
-
- /** Animates the footer into and out of view. */
- void animateFooterVisibility(final boolean visible, int duration) {
- // Return early if there is no footer
- if (mMaxFooterHeight <= 0) return;
-
- // Cancel the previous animation
- if (mFooterAnimator != null) {
- mFooterAnimator.removeAllListeners();
- mFooterAnimator.cancel();
- }
- int finalHeight = visible ? mMaxFooterHeight : 0;
- if (duration > 0) {
- mFooterAnimator = ObjectAnimator.ofInt(this, "footerHeight", finalHeight);
- mFooterAnimator.setDuration(duration);
- mFooterAnimator.setInterpolator(mConfig.fastOutSlowInInterpolator);
- mFooterAnimator.start();
- } else {
- setFooterHeight(finalHeight);
- }
- }
-}
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 396d441..05f6f40 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
@@ -36,7 +36,6 @@ import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.RippleDrawable;
-import android.graphics.drawable.ShapeDrawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
@@ -56,25 +55,28 @@ public class TaskViewHeader extends FrameLayout {
RecentsConfiguration mConfig;
+ // Header views
ImageView mDismissButton;
ImageView mApplicationIcon;
TextView mActivityDescription;
- RippleDrawable mBackground;
- GradientDrawable mBackgroundColorDrawable;
+ // Header drawables
+ boolean mCurrentPrimaryColorIsDark;
+ int mCurrentPrimaryColor;
int mBackgroundColor;
Drawable mLightDismissDrawable;
Drawable mDarkDismissDrawable;
+ RippleDrawable mBackground;
+ GradientDrawable mBackgroundColorDrawable;
AnimatorSet mFocusAnimator;
- ValueAnimator backgroundColorAnimator;
- PorterDuffColorFilter mDimFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_ATOP);
-
- boolean mIsFullscreen;
- boolean mCurrentPrimaryColorIsDark;
- int mCurrentPrimaryColor;
+ String mDismissContentDescription;
+ // Static highlight that we draw at the top of each view
static Paint sHighlightPaint;
- private Paint mDimPaint = new Paint();
+
+ // Header dim, which is only used when task view hardware layers are not used
+ Paint mDimLayerPaint = new Paint();
+ PorterDuffColorFilter mDimColorFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_ATOP);
public TaskViewHeader(Context context) {
this(context, null);
@@ -104,6 +106,8 @@ public class TaskViewHeader extends FrameLayout {
Resources res = context.getResources();
mLightDismissDrawable = res.getDrawable(R.drawable.recents_dismiss_light);
mDarkDismissDrawable = res.getDrawable(R.drawable.recents_dismiss_dark);
+ mDismissContentDescription =
+ res.getString(R.string.accessibility_recents_item_will_be_dismissed);
// Configure the highlight paint
if (sHighlightPaint == null) {
@@ -126,14 +130,6 @@ public class TaskViewHeader extends FrameLayout {
@Override
protected void onFinishInflate() {
- // Set the outline provider
- setOutlineProvider(new ViewOutlineProvider() {
- @Override
- public void getOutline(View view, Outline outline) {
- outline.setRect(0, 0, getMeasuredWidth(), getMeasuredHeight());
- }
- });
-
// Initialize the icon and description views
mApplicationIcon = (ImageView) findViewById(R.id.application_icon);
mActivityDescription = (TextView) findViewById(R.id.activity_description);
@@ -159,21 +155,14 @@ public class TaskViewHeader extends FrameLayout {
@Override
protected void onDraw(Canvas canvas) {
- if (!mIsFullscreen) {
- // Draw the highlight at the top edge (but put the bottom edge just out of view)
- float offset = (float) Math.ceil(mConfig.taskViewHighlightPx / 2f);
- float radius = mConfig.taskViewRoundedCornerRadiusPx;
- int count = canvas.save(Canvas.CLIP_SAVE_FLAG);
- canvas.clipRect(0, 0, getMeasuredWidth(), getMeasuredHeight());
- canvas.drawRoundRect(-offset, 0f, (float) getMeasuredWidth() + offset,
- getMeasuredHeight() + radius, radius, radius, sHighlightPaint);
- canvas.restoreToCount(count);
- }
- }
-
- /** Sets whether the current task is full screen or not. */
- void setIsFullscreen(boolean isFullscreen) {
- mIsFullscreen = isFullscreen;
+ // Draw the highlight at the top edge (but put the bottom edge just out of view)
+ float offset = (float) Math.ceil(mConfig.taskViewHighlightPx / 2f);
+ float radius = mConfig.taskViewRoundedCornerRadiusPx;
+ int count = canvas.save(Canvas.CLIP_SAVE_FLAG);
+ canvas.clipRect(0, 0, getMeasuredWidth(), getMeasuredHeight());
+ canvas.drawRoundRect(-offset, 0f, (float) getMeasuredWidth() + offset,
+ getMeasuredHeight() + radius, radius, radius, sHighlightPaint);
+ canvas.restoreToCount(count);
}
@Override
@@ -181,6 +170,16 @@ public class TaskViewHeader extends FrameLayout {
return false;
}
+ /**
+ * Sets the dim alpha, only used when we are not using hardware layers.
+ * (see RecentsConfiguration.useHardwareLayers)
+ */
+ void setDimAlpha(int alpha) {
+ mDimColorFilter.setColor(Color.argb(alpha, 0, 0, 0));
+ mDimLayerPaint.setColorFilter(mDimColorFilter);
+ setLayerType(LAYER_TYPE_HARDWARE, mDimLayerPaint);
+ }
+
/** Returns the secondary color for a primary color. */
int getSecondaryColor(int primaryColor, boolean useLightOverlayColor) {
int overlayColor = useLightOverlayColor ? Color.WHITE : Color.BLACK;
@@ -213,9 +212,8 @@ public class TaskViewHeader extends FrameLayout {
mConfig.taskBarViewLightTextColor : mConfig.taskBarViewDarkTextColor);
mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ?
mLightDismissDrawable : mDarkDismissDrawable);
- mDismissButton.setContentDescription(
- getContext().getString(R.string.accessibility_recents_item_will_be_dismissed,
- t.activityLabel));
+ mDismissButton.setContentDescription(String.format(mDismissContentDescription,
+ t.activityLabel));
}
/** Unbinds the bar view from the task */
@@ -231,7 +229,7 @@ public class TaskViewHeader extends FrameLayout {
.alpha(0f)
.setStartDelay(0)
.setInterpolator(mConfig.fastOutSlowInInterpolator)
- .setDuration(mConfig.taskBarExitAnimDuration)
+ .setDuration(mConfig.taskViewExitToAppDuration)
.withLayer()
.start();
}
@@ -239,15 +237,17 @@ public class TaskViewHeader extends FrameLayout {
/** Animates this task bar if the user does not interact with the stack after a certain time. */
void startNoUserInteractionAnimation() {
- mDismissButton.setVisibility(View.VISIBLE);
- mDismissButton.setAlpha(0f);
- mDismissButton.animate()
- .alpha(1f)
- .setStartDelay(0)
- .setInterpolator(mConfig.fastOutLinearInInterpolator)
- .setDuration(mConfig.taskBarEnterAnimDuration)
- .withLayer()
- .start();
+ if (mDismissButton.getVisibility() != View.VISIBLE) {
+ mDismissButton.setVisibility(View.VISIBLE);
+ mDismissButton.setAlpha(0f);
+ mDismissButton.animate()
+ .alpha(1f)
+ .setStartDelay(0)
+ .setInterpolator(mConfig.fastOutLinearInInterpolator)
+ .setDuration(mConfig.taskViewEnterFromAppDuration)
+ .withLayer()
+ .start();
+ }
}
/** Mark this task view that the user does has not interacted with the stack after a certain time. */
@@ -259,6 +259,11 @@ public class TaskViewHeader extends FrameLayout {
}
}
+ /** Resets the state tracking that the user has not interacted with the stack after a certain time. */
+ void resetNoUserInteractionState() {
+ mDismissButton.setVisibility(View.INVISIBLE);
+ }
+
@Override
protected int[] onCreateDrawableState(int extraSpace) {
@@ -268,13 +273,16 @@ public class TaskViewHeader extends FrameLayout {
}
/** Notifies the associated TaskView has been focused. */
- void onTaskViewFocusChanged(boolean focused) {
+ void onTaskViewFocusChanged(boolean focused, boolean animateFocusedState) {
+ // If we are not animating the visible state, just return
+ if (!animateFocusedState) return;
+
boolean isRunning = false;
if (mFocusAnimator != null) {
isRunning = mFocusAnimator.isRunning();
- mFocusAnimator.removeAllListeners();
- mFocusAnimator.cancel();
+ Utilities.cancelAnimationWithoutCallbacks(mFocusAnimator);
}
+
if (focused) {
int secondaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark);
int[][] states = new int[][] {
@@ -295,7 +303,7 @@ public class TaskViewHeader extends FrameLayout {
int currentColor = mBackgroundColor;
int lightPrimaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark);
ValueAnimator backgroundColor = ValueAnimator.ofObject(new ArgbEvaluator(),
- lightPrimaryColor, currentColor);
+ currentColor, lightPrimaryColor);
backgroundColor.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
@@ -349,11 +357,4 @@ public class TaskViewHeader extends FrameLayout {
}
}
}
-
- public void setDimAlpha(int alpha) {
- int color = Color.argb(alpha, 0, 0, 0);
- mDimFilter.setColor(color);
- mDimPaint.setColorFilter(mDimFilter);
- setLayerType(LAYER_TYPE_HARDWARE, mDimPaint);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
index a946a84..117a7d3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
@@ -33,37 +33,48 @@ import android.graphics.Shader;
import android.util.AttributeSet;
import android.view.View;
import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.model.Task;
-/** The task thumbnail view */
+/**
+ * The task thumbnail view. It implements an image view that allows for animating the dim and
+ * alpha of the thumbnail image.
+ */
public class TaskViewThumbnail extends View {
- private final int mCornerRadius;
- private final Matrix mScaleMatrix = new Matrix();
RecentsConfiguration mConfig;
- // Task bar clipping
- Rect mClipRect = new Rect();
+ // Drawing
+ float mDimAlpha;
+ Matrix mScaleMatrix = new Matrix();
Paint mDrawPaint = new Paint();
+ RectF mBitmapRect = new RectF();
+ RectF mLayoutRect = new RectF();
+ BitmapShader mBitmapShader;
LightingColorFilter mLightingColorFilter = new LightingColorFilter(0xffffffff, 0);
- private final RectF mBitmapRect = new RectF();
- private final RectF mLayoutRect = new RectF();
- private BitmapShader mBitmapShader;
- private float mBitmapAlpha;
- private float mDimAlpha;
- private View mTaskBar;
- private boolean mInvisible;
- private ValueAnimator mAlphaAnimator;
- private ValueAnimator.AnimatorUpdateListener mAlphaUpdateListener
+
+ // Thumbnail alpha
+ float mThumbnailAlpha;
+ ValueAnimator mThumbnailAlphaAnimator;
+ ValueAnimator.AnimatorUpdateListener mThumbnailAlphaUpdateListener
= new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
- mBitmapAlpha = (float) animation.getAnimatedValue();
- updateFilter();
+ mThumbnailAlpha = (float) animation.getAnimatedValue();
+ updateThumbnailPaintFilter();
}
};
+ // Task bar clipping, the top of this thumbnail can be clipped against the opaque header
+ // bar that overlaps this thumbnail
+ View mTaskBar;
+ Rect mClipRect = new Rect();
+
+ // Visibility optimization, if the thumbnail height is less than the height of the header
+ // bar for the task view, then just mark this thumbnail view as invisible
+ boolean mInvisible;
+
public TaskViewThumbnail(Context context) {
this(context, null);
}
@@ -79,53 +90,82 @@ public class TaskViewThumbnail extends View {
public TaskViewThumbnail(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mConfig = RecentsConfiguration.getInstance();
- mCornerRadius = mConfig.taskViewRoundedCornerRadiusPx;
mDrawPaint.setColorFilter(mLightingColorFilter);
mDrawPaint.setFilterBitmap(true);
mDrawPaint.setAntiAlias(true);
}
@Override
+ protected void onFinishInflate() {
+ mThumbnailAlpha = mConfig.taskViewThumbnailAlpha;
+ updateThumbnailPaintFilter();
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ if (changed) {
+ mLayoutRect.set(0, 0, getWidth(), getHeight());
+ updateThumbnailScale();
+ }
+ }
+
+ @Override
protected void onDraw(Canvas canvas) {
if (mInvisible) {
return;
}
- canvas.drawRoundRect(0,
- 0,
- getWidth(),
- getHeight(),
- mCornerRadius,
- mCornerRadius,
- mDrawPaint);
+ // Draw the thumbnail with the rounded corners
+ canvas.drawRoundRect(0, 0, getWidth(), getHeight(),
+ mConfig.taskViewRoundedCornerRadiusPx,
+ mConfig.taskViewRoundedCornerRadiusPx, mDrawPaint);
}
- @Override
- protected void onFinishInflate() {
- mBitmapAlpha = 0.9f;
- updateFilter();
+ /** Sets the thumbnail to a given bitmap. */
+ void setThumbnail(Bitmap bm) {
+ if (bm != null) {
+ mBitmapShader = new BitmapShader(bm, Shader.TileMode.CLAMP,
+ Shader.TileMode.CLAMP);
+ mDrawPaint.setShader(mBitmapShader);
+ mBitmapRect.set(0, 0, bm.getWidth(), bm.getHeight());
+ updateThumbnailScale();
+ } else {
+ mBitmapShader = null;
+ mDrawPaint.setShader(null);
+ }
+ updateThumbnailPaintFilter();
}
- private void updateFilter() {
+ /** Updates the paint to draw the thumbnail. */
+ void updateThumbnailPaintFilter() {
if (mInvisible) {
return;
}
- int mul = (int) ((1.0f - mDimAlpha) * mBitmapAlpha * 255);
- int add = (int) ((1.0f - mDimAlpha) * (1 - mBitmapAlpha) * 255);
+ int mul = (int) ((1.0f - mDimAlpha) * mThumbnailAlpha * 255);
+ int add = (int) ((1.0f - mDimAlpha) * (1 - mThumbnailAlpha) * 255);
if (mBitmapShader != null) {
mLightingColorFilter.setColorMultiply(Color.argb(255, mul, mul, mul));
mLightingColorFilter.setColorAdd(Color.argb(0, add, add, add));
mDrawPaint.setColorFilter(mLightingColorFilter);
mDrawPaint.setColor(0xffffffff);
} else {
- mDrawPaint.setColorFilter(null);
int grey = mul + add;
+ mDrawPaint.setColorFilter(null);
mDrawPaint.setColor(Color.argb(255, grey, grey, grey));
}
invalidate();
}
+ /** Updates the thumbnail shader's scale transform. */
+ void updateThumbnailScale() {
+ if (mBitmapShader != null) {
+ mScaleMatrix.setRectToRect(mBitmapRect, mLayoutRect, Matrix.ScaleToFit.FILL);
+ mBitmapShader.setLocalMatrix(mScaleMatrix);
+ }
+ }
+
/** Updates the clip rect based on the given task bar. */
- void enableTaskBarClip(View taskBar) {
+ void updateClipToTaskBar(View taskBar) {
mTaskBar = taskBar;
int top = (int) Math.max(0, taskBar.getTranslationY() +
taskBar.getMeasuredHeight() - 1);
@@ -133,75 +173,39 @@ public class TaskViewThumbnail extends View {
setClipBounds(mClipRect);
}
- void updateVisibility(int clipBottom) {
- boolean invisible = mTaskBar != null && getHeight() - clipBottom < mTaskBar.getHeight();
+ /** Updates the visibility of the the thumbnail. */
+ void updateThumbnailVisibility(int clipBottom) {
+ boolean invisible = mTaskBar != null && (getHeight() - clipBottom) <= mTaskBar.getHeight();
if (invisible != mInvisible) {
mInvisible = invisible;
if (!mInvisible) {
- updateFilter();
+ updateThumbnailPaintFilter();
}
invalidate();
}
}
- /** Binds the thumbnail view to the screenshot. */
- boolean bindToScreenshot(Bitmap ss) {
- setImageBitmap(ss);
- return ss != null;
- }
-
- /** Unbinds the thumbnail view from the screenshot. */
- void unbindFromScreenshot() {
- setImageBitmap(null);
+ /**
+ * Sets the dim alpha, only used when we are not using hardware layers.
+ * (see RecentsConfiguration.useHardwareLayers)
+ */
+ public void setDimAlpha(float dimAlpha) {
+ mDimAlpha = dimAlpha;
+ updateThumbnailPaintFilter();
}
/** Binds the thumbnail view to the task */
void rebindToTask(Task t) {
if (t.thumbnail != null) {
- setImageBitmap(t.thumbnail);
+ setThumbnail(t.thumbnail);
} else {
- setImageBitmap(null);
+ setThumbnail(null);
}
}
- public void setImageBitmap(Bitmap bm) {
- if (bm != null) {
- mBitmapShader = new BitmapShader(bm, Shader.TileMode.CLAMP,
- Shader.TileMode.CLAMP);
- mDrawPaint.setShader(mBitmapShader);
- mBitmapRect.set(0, 0, bm.getWidth(), bm.getHeight());
- updateBitmapScale();
- } else {
- mBitmapShader = null;
- mDrawPaint.setShader(null);
- }
- updateFilter();
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- if (changed) {
- mLayoutRect.set(0, 0, getWidth(), getHeight());
- updateBitmapScale();
- }
- }
-
- private void updateBitmapScale() {
- if (mBitmapShader != null) {
- mScaleMatrix.setRectToRect(mBitmapRect, mLayoutRect, Matrix.ScaleToFit.FILL);
- mBitmapShader.setLocalMatrix(mScaleMatrix);
- }
- }
-
- public void setDimAlpha(float dimAlpha) {
- mDimAlpha = dimAlpha;
- updateFilter();
- }
-
/** Unbinds the thumbnail view from the task */
void unbindFromTask() {
- setImageBitmap(null);
+ setThumbnail(null);
}
/** Handles focus changes. */
@@ -217,54 +221,46 @@ public class TaskViewThumbnail extends View {
}
}
- /** Prepares for the enter recents animation. */
+ /**
+ * Prepares for the enter recents animation, this gets called before the the view
+ * is first visible and will be followed by a startEnterRecentsAnimation() call.
+ */
void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask) {
if (isTaskViewLaunchTargetTask) {
- mBitmapAlpha = 1f;
+ mThumbnailAlpha = 1f;
} else {
- mBitmapAlpha = mConfig.taskViewThumbnailAlpha;
+ mThumbnailAlpha = mConfig.taskViewThumbnailAlpha;
}
- updateFilter();
+ updateThumbnailPaintFilter();
}
- /** Animates this task thumbnail as it enters recents */
+ /** Animates this task thumbnail as it enters Recents. */
void startEnterRecentsAnimation(int delay, Runnable postAnimRunnable) {
startFadeAnimation(mConfig.taskViewThumbnailAlpha, delay,
- mConfig.taskBarEnterAnimDuration, postAnimRunnable);
+ mConfig.taskViewEnterFromAppDuration, postAnimRunnable);
}
- /** Animates this task thumbnail as it exits recents */
+ /** Animates this task thumbnail as it exits Recents. */
void startLaunchTaskAnimation(Runnable postAnimRunnable) {
- startFadeAnimation(1f, 0, mConfig.taskBarExitAnimDuration, postAnimRunnable);
+ startFadeAnimation(1f, 0, mConfig.taskViewExitToAppDuration, postAnimRunnable);
}
- /** Animates the thumbnail alpha. */
+ /** Starts a new thumbnail alpha animation. */
void startFadeAnimation(float finalAlpha, int delay, int duration, final Runnable postAnimRunnable) {
- if (mAlphaAnimator != null) {
- mAlphaAnimator.cancel();
- }
- mAlphaAnimator = ValueAnimator.ofFloat(mBitmapAlpha, finalAlpha);
- mAlphaAnimator.addUpdateListener(mAlphaUpdateListener);
- mAlphaAnimator.setStartDelay(delay);
- mAlphaAnimator.setInterpolator(mConfig.fastOutSlowInInterpolator);
- mAlphaAnimator.setDuration(duration);
- mAlphaAnimator.start();
+ Utilities.cancelAnimationWithoutCallbacks(mThumbnailAlphaAnimator);
+ mThumbnailAlphaAnimator = ValueAnimator.ofFloat(mThumbnailAlpha, finalAlpha);
+ mThumbnailAlphaAnimator.setStartDelay(delay);
+ mThumbnailAlphaAnimator.setDuration(duration);
+ mThumbnailAlphaAnimator.setInterpolator(mConfig.fastOutSlowInInterpolator);
+ mThumbnailAlphaAnimator.addUpdateListener(mThumbnailAlphaUpdateListener);
if (postAnimRunnable != null) {
- mAlphaAnimator.addListener(new AnimatorListenerAdapter() {
- public boolean mCancelled;
-
- @Override
- public void onAnimationCancel(Animator animation) {
- mCancelled = true;
- }
-
+ mThumbnailAlphaAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- if (!mCancelled) {
- postAnimRunnable.run();
- }
+ postAnimRunnable.run();
}
});
}
+ mThumbnailAlphaAnimator.start();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/ViewAnimation.java b/packages/SystemUI/src/com/android/systemui/recents/views/ViewAnimation.java
index 4586f12..e1d80fd 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/ViewAnimation.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/ViewAnimation.java
@@ -27,7 +27,7 @@ public class ViewAnimation {
public static class TaskViewEnterContext {
// A trigger to run some logic when all the animations complete. This works around the fact
// that it is difficult to coordinate ViewPropertyAnimators
- ReferenceCountedTrigger postAnimationTrigger;
+ public ReferenceCountedTrigger postAnimationTrigger;
// An update listener to notify as the enter animation progresses (used for the home transition)
ValueAnimator.AnimatorUpdateListener updateListener;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/ViewPool.java b/packages/SystemUI/src/com/android/systemui/recents/views/ViewPool.java
index af0094e..12b91af 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/ViewPool.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/ViewPool.java
@@ -75,4 +75,12 @@ public class ViewPool<V, T> {
mViewCreator.prepareViewToLeavePool(v, prepareData, isNewView);
return v;
}
+
+ /** Returns an iterator to the list of the views in the pool. */
+ Iterator<V> poolViewIterator() {
+ if (mPool != null) {
+ return mPool.iterator();
+ }
+ return null;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index e4faa6a..465a141 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -25,8 +25,6 @@ import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
-import android.graphics.ColorMatrix;
-import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
@@ -53,6 +51,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
private static final long DOUBLETAP_TIMEOUT_MS = 1200;
private static final int BACKGROUND_ANIMATION_LENGTH_MS = 220;
private static final int ACTIVATE_ANIMATION_LENGTH = 220;
+ private static final int DARK_ANIMATION_LENGTH = 170;
/**
* The amount of width, which is kept in the end when performing a disappear animation (also
@@ -84,6 +83,11 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
*/
private static final float VERTICAL_ANIMATION_START = 1.0f;
+ /**
+ * Scale for the background to animate from when exiting dark mode.
+ */
+ private static final float DARK_EXIT_SCALE_START = 0.93f;
+
private static final Interpolator ACTIVATE_INVERSE_INTERPOLATOR
= new PathInterpolator(0.6f, 0, 0.5f, 1);
private static final Interpolator ACTIVATE_INVERSE_ALPHA_INTERPOLATOR
@@ -94,7 +98,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
private boolean mDimmed;
private boolean mDark;
- private final Paint mDarkPaint = createDarkPaint();
private int mBgTint = 0;
private final int mRoundedRectCornerRadius;
@@ -332,40 +335,35 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
if (mDimmed != dimmed) {
mDimmed = dimmed;
if (fade) {
- fadeBackground();
+ fadeDimmedBackground();
} else {
updateBackground();
}
}
}
- public void setDark(boolean dark, boolean fade) {
- // TODO implement fade
- if (mDark != dark) {
- mDark = dark;
- if (mDark) {
- setLayerType(View.LAYER_TYPE_HARDWARE, mDarkPaint);
+ public void setDark(boolean dark, boolean fade, long delay) {
+ super.setDark(dark, fade, delay);
+ if (mDark == dark) {
+ return;
+ }
+ mDark = dark;
+ if (!dark && fade) {
+ if (mActivated) {
+ mBackgroundDimmed.setVisibility(View.VISIBLE);
+ mBackgroundNormal.setVisibility(View.VISIBLE);
+ } else if (mDimmed) {
+ mBackgroundDimmed.setVisibility(View.VISIBLE);
+ mBackgroundNormal.setVisibility(View.INVISIBLE);
} else {
- setLayerType(View.LAYER_TYPE_NONE, null);
+ mBackgroundDimmed.setVisibility(View.INVISIBLE);
+ mBackgroundNormal.setVisibility(View.VISIBLE);
}
+ fadeInFromDark(delay);
+ } else {
+ updateBackground();
}
- }
-
- private static Paint createDarkPaint() {
- final Paint p = new Paint();
- final float[] invert = {
- -1f, 0f, 0f, 1f, 1f,
- 0f, -1f, 0f, 1f, 1f,
- 0f, 0f, -1f, 1f, 1f,
- 0f, 0f, 0f, 1f, 0f
- };
- final ColorMatrix m = new ColorMatrix(invert);
- final ColorMatrix grayscale = new ColorMatrix();
- grayscale.setSaturation(0);
- m.preConcat(grayscale);
- p.setColorFilter(new ColorMatrixColorFilter(m));
- return p;
- }
+ }
public void setShowingLegacyBackground(boolean showing) {
mShowingLegacyBackground = showing;
@@ -402,7 +400,40 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
mBackgroundNormal.setRippleColor(rippleColor);
}
- private void fadeBackground() {
+ /**
+ * Fades in the background when exiting dark mode.
+ */
+ private void fadeInFromDark(long delay) {
+ final View background = mDimmed ? mBackgroundDimmed : mBackgroundNormal;
+ background.setAlpha(0f);
+ background.setPivotX(mBackgroundDimmed.getWidth() / 2f);
+ background.setPivotY(getActualHeight() / 2f);
+ background.setScaleX(DARK_EXIT_SCALE_START);
+ background.setScaleY(DARK_EXIT_SCALE_START);
+ background.animate()
+ .alpha(1f)
+ .scaleX(1f)
+ .scaleY(1f)
+ .setDuration(DARK_ANIMATION_LENGTH)
+ .setStartDelay(delay)
+ .setInterpolator(mLinearOutSlowInInterpolator)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ // Jump state if we are cancelled
+ background.setScaleX(1f);
+ background.setScaleY(1f);
+ background.setAlpha(1f);
+ }
+ })
+ .start();
+ }
+
+ /**
+ * Fades the background when the dimmed state changes.
+ */
+ private void fadeDimmedBackground() {
+ mBackgroundDimmed.animate().cancel();
mBackgroundNormal.animate().cancel();
if (mDimmed) {
mBackgroundDimmed.setVisibility(View.VISIBLE);
@@ -443,11 +474,14 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
}
private void updateBackground() {
- if (mDimmed) {
+ cancelFadeAnimations();
+ if (mDark) {
+ mBackgroundDimmed.setVisibility(View.INVISIBLE);
+ mBackgroundNormal.setVisibility(View.INVISIBLE);
+ } else if (mDimmed) {
mBackgroundDimmed.setVisibility(View.VISIBLE);
mBackgroundNormal.setVisibility(View.INVISIBLE);
} else {
- cancelFadeAnimations();
mBackgroundDimmed.setVisibility(View.INVISIBLE);
mBackgroundNormal.setVisibility(View.VISIBLE);
mBackgroundNormal.setAlpha(1f);
@@ -459,6 +493,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
if (mBackgroundAnimator != null) {
mBackgroundAnimator.cancel();
}
+ mBackgroundDimmed.animate().cancel();
mBackgroundNormal.animate().cancel();
}
@@ -508,7 +543,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
if (mAppearAnimator != null) {
mAppearAnimator.cancel();
}
- mAnimationTranslationY = translationDirection * mActualHeight;
+ mAnimationTranslationY = translationDirection * getActualHeight();
if (mAppearAnimationFraction == -1.0f) {
// not initialized yet, we start anew
if (isAppearing) {
@@ -601,14 +636,15 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
float top;
float bottom;
+ final int actualHeight = getActualHeight();
if (mAnimationTranslationY > 0.0f) {
- bottom = mActualHeight - heightFraction * mAnimationTranslationY * 0.1f
+ bottom = actualHeight - heightFraction * mAnimationTranslationY * 0.1f
- translateYTotalAmount;
top = bottom * heightFraction;
} else {
- top = heightFraction * (mActualHeight + mAnimationTranslationY) * 0.1f -
+ top = heightFraction * (actualHeight + mAnimationTranslationY) * 0.1f -
translateYTotalAmount;
- bottom = mActualHeight * (1 - heightFraction) + top * heightFraction;
+ bottom = actualHeight * (1 - heightFraction) + top * heightFraction;
}
mAppearAnimationRect.set(left, top, right, bottom);
setOutlineRect(left, top + mAppearAnimationTranslation, right,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedImageView.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedImageView.java
new file mode 100644
index 0000000..094161d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedImageView.java
@@ -0,0 +1,51 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageView;
+
+/**
+ * An ImageView which does not have overlapping rendering commands and therefore does not need a
+ * layer when alpha is changed.
+ */
+public class AlphaOptimizedImageView extends ImageView
+{
+ public AlphaOptimizedImageView(Context context) {
+ super(context);
+ }
+
+ public AlphaOptimizedImageView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public AlphaOptimizedImageView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public AlphaOptimizedImageView(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/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index f5e5517..8a03a2b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -59,6 +59,7 @@ import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
import android.util.Log;
+import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.view.Display;
@@ -69,6 +70,7 @@ 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;
@@ -91,7 +93,6 @@ import com.android.systemui.SearchPanelView;
import com.android.systemui.SwipeHelper;
import com.android.systemui.SystemUI;
import com.android.systemui.statusbar.NotificationData.Entry;
-import com.android.systemui.statusbar.phone.KeyguardTouchDelegate;
import com.android.systemui.statusbar.phone.NavigationBarView;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.HeadsUpNotificationView;
@@ -112,6 +113,9 @@ public abstract class BaseStatusBar extends SystemUI implements
public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
public static final boolean MULTIUSER_DEBUG = false;
+ // STOPSHIP disable once we resolve b/18102199
+ private static final boolean NOTIFICATION_CLICK_DEBUG = true;
+
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;
@@ -154,9 +158,6 @@ public abstract class BaseStatusBar extends SystemUI implements
protected HeadsUpNotificationView mHeadsUpNotificationView;
protected int mHeadsUpNotificationDecay;
- // used to notify status bar for suppressing notification LED
- protected boolean mPanelSlightlyVisible;
-
// Search panel
protected SearchPanelView mSearchPanelView;
@@ -168,6 +169,20 @@ public abstract class BaseStatusBar extends SystemUI implements
// on-screen navigation buttons
protected NavigationBarView mNavigationBarView = null;
+
+ protected Boolean mScreenOn;
+
+ // The second field is a bit different from the first one because it only listens to screen on/
+ // screen of events from Keyguard. We need this so we don't have a race condition with the
+ // broadcast. In the future, we should remove the first field altogether and rename the second
+ // field.
+ protected boolean mScreenOnFromKeyguard;
+
+ protected boolean mVisible;
+
+ // mScreenOnFromKeyguard && mVisible.
+ private boolean mVisibleToUser;
+
private Locale mLocale;
private float mFontScale;
@@ -266,6 +281,7 @@ public abstract class BaseStatusBar extends SystemUI implements
if (DEBUG) {
Log.v(TAG, "Notification click handler invoked for intent: " + pendingIntent);
}
+ logActionClick(view);
// The intent we are sending is for the application, which
// won't have permission to immediately start an activity after
// the user switches to home. We know it is safe to do at this
@@ -295,7 +311,8 @@ public abstract class BaseStatusBar extends SystemUI implements
// close the shade if it was open
if (handled) {
- animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
+ animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
+ true /* force */);
visibilityChanged(false);
}
// Wait for activity start.
@@ -308,6 +325,40 @@ public abstract class BaseStatusBar extends SystemUI implements
}
}
+ private void logActionClick(View view) {
+ ViewParent parent = view.getParent();
+ String key = getNotificationKeyForParent(parent);
+ if (key == null) {
+ Log.w(TAG, "Couldn't determine notification for click.");
+ return;
+ }
+ int index = -1;
+ // If this is a default template, determine the index of the button.
+ if (view.getId() == com.android.internal.R.id.action0 &&
+ parent != null && parent instanceof ViewGroup) {
+ ViewGroup actionGroup = (ViewGroup) parent;
+ index = actionGroup.indexOfChild(view);
+ }
+ if (NOTIFICATION_CLICK_DEBUG) {
+ Log.d(TAG, "Clicked on button " + index + " for " + key);
+ }
+ try {
+ mBarService.onNotificationActionClick(key, index);
+ } catch (RemoteException e) {
+ // Ignore
+ }
+ }
+
+ private String getNotificationKeyForParent(ViewParent parent) {
+ while (parent != null) {
+ if (parent instanceof ExpandableNotificationRow) {
+ return ((ExpandableNotificationRow) parent).getStatusBarNotification().getKey();
+ }
+ parent = parent.getParent();
+ }
+ return null;
+ }
+
private boolean superOnClickHandler(View view, PendingIntent pendingIntent,
Intent fillInIntent) {
return super.onClickHandler(view, pendingIntent, fillInIntent);
@@ -341,7 +392,8 @@ public abstract class BaseStatusBar extends SystemUI implements
Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0);
if (BANNER_ACTION_SETUP.equals(action)) {
- animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
+ animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
+ true /* force */);
mContext.startActivity(new Intent(Settings.ACTION_APP_NOTIFICATION_REDACTION)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
@@ -548,6 +600,7 @@ public abstract class BaseStatusBar extends SystemUI implements
}
mCurrentUserId = ActivityManager.getCurrentUser();
+ setHeadsUpUser(mCurrentUserId);
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_USER_SWITCHED);
@@ -610,7 +663,13 @@ public abstract class BaseStatusBar extends SystemUI implements
}
public void userSwitched(int newUserId) {
- // should be overridden
+ setHeadsUpUser(newUserId);
+ }
+
+ private void setHeadsUpUser(int newUserId) {
+ if (mHeadsUpNotificationView != null) {
+ mHeadsUpNotificationView.setUser(newUserId);
+ }
}
public boolean isHeadsUp(String key) {
@@ -625,9 +684,12 @@ public abstract class BaseStatusBar extends SystemUI implements
Log.v(TAG, String.format("%s: current userid: %d, notification userid: %d",
n, thisUserId, notificationUserId));
}
+ return isCurrentProfile(notificationUserId);
+ }
+
+ protected boolean isCurrentProfile(int userId) {
synchronized (mCurrentProfiles) {
- return notificationUserId == UserHandle.USER_ALL
- || mCurrentProfiles.get(notificationUserId) != null;
+ return userId == UserHandle.USER_ALL || mCurrentProfiles.get(userId) != null;
}
}
@@ -766,7 +828,7 @@ public abstract class BaseStatusBar extends SystemUI implements
}
}
});
- animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
+ animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
return true;
}
}, false /* afterKeyguardGone */);
@@ -1434,8 +1496,6 @@ public abstract class BaseStatusBar extends SystemUI implements
entry.autoRedacted = true;
}
- row.setClearable(sbn.isClearable());
-
if (MULTIUSER_DEBUG) {
TextView debug = (TextView) row.findViewById(R.id.debug_info);
if (debug != null) {
@@ -1480,6 +1540,9 @@ public abstract class BaseStatusBar extends SystemUI implements
}
public void onClick(final View v) {
+ if (NOTIFICATION_CLICK_DEBUG) {
+ Log.d(TAG, "Clicked on content of " + mNotificationKey);
+ }
final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing();
final boolean afterKeyguardGone = mIntent.isActivity()
&& PreviewInflater.wouldLaunchResolverActivity(mContext, mIntent.getIntent(),
@@ -1487,7 +1550,11 @@ public abstract class BaseStatusBar extends SystemUI implements
dismissKeyguardThenExecute(new OnDismissAction() {
public boolean onDismiss() {
if (mIsHeadsUp) {
- mHeadsUpNotificationView.clear();
+ // 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();
}
new Thread() {
@Override
@@ -1531,7 +1598,8 @@ public abstract class BaseStatusBar extends SystemUI implements
}.start();
// close the shade if it was open
- animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
+ animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
+ true /* force */);
visibilityChanged(false);
return mIntent != null && mIntent.isActivity();
@@ -1553,28 +1621,45 @@ public abstract class BaseStatusBar extends SystemUI implements
}
}
+ protected void visibilityChanged(boolean visible) {
+ if (mVisible != visible) {
+ mVisible = visible;
+ if (!visible) {
+ dismissPopups();
+ }
+ }
+ updateVisibleToUser();
+ }
+
+ protected void updateVisibleToUser() {
+ boolean oldVisibleToUser = mVisibleToUser;
+ mVisibleToUser = mVisible && mScreenOnFromKeyguard;
+
+ if (oldVisibleToUser != mVisibleToUser) {
+ handleVisibleToUserChanged(mVisibleToUser);
+ }
+ }
+
/**
- * The LEDs are turned o)ff when the notification panel is shown, even just a little bit.
+ * 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 visibilityChanged(boolean visible) {
- if (mPanelSlightlyVisible != visible) {
- mPanelSlightlyVisible = visible;
- if (!visible) {
- dismissPopups();
- }
- try {
- if (visible) {
- mBarService.onPanelRevealed();
- } else {
- mBarService.onPanelHidden();
- }
- } catch (RemoteException ex) {
- // Won't fail unless the world has ended.
+ 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);
+ } else {
+ mBarService.onPanelHidden();
}
+ } catch (RemoteException ex) {
+ // Won't fail unless the world has ended.
}
}
@@ -1998,6 +2083,10 @@ public abstract class BaseStatusBar extends SystemUI implements
return false;
}
+ if (mHeadsUpNotificationView.isSnoozed(sbn.getPackageName())) {
+ return false;
+ }
+
Notification notification = sbn.getNotification();
// some predicates to make the boolean logic legible
boolean isNoisy = (notification.defaults & Notification.DEFAULT_SOUND) != 0
@@ -2012,13 +2101,13 @@ public abstract class BaseStatusBar extends SystemUI implements
boolean accessibilityForcesLaunch = isFullscreen
&& mAccessibilityManager.isTouchExplorationEnabled();
- final KeyguardTouchDelegate keyguard = KeyguardTouchDelegate.getInstance(mContext);
boolean interrupt = (isFullscreen || (isHighPriority && (isNoisy || hasTicker)))
&& isAllowed
&& !accessibilityForcesLaunch
&& mPowerManager.isScreenOn()
- && !keyguard.isShowingAndNotOccluded()
- && !keyguard.isInputRestricted();
+ && (!mStatusBarKeyguardViewManager.isShowing()
+ || mStatusBarKeyguardViewManager.isOccluded())
+ && !mStatusBarKeyguardViewManager.isInputRestricted();
try {
interrupt = interrupt && !mDreamManager.isDreaming();
} catch (RemoteException e) {
@@ -2028,10 +2117,6 @@ public abstract class BaseStatusBar extends SystemUI implements
return interrupt;
}
- public boolean inKeyguardRestrictedInputMode() {
- return KeyguardTouchDelegate.getInstance(mContext).isInputRestricted();
- }
-
public void setInteracting(int barWindow, boolean interacting) {
// hook for subclasses
}
@@ -2089,4 +2174,16 @@ public abstract class BaseStatusBar extends SystemUI implements
// Ignore.
}
}
+
+ public boolean isKeyguardSecure() {
+ if (mStatusBarKeyguardViewManager == null) {
+ // startKeyguard() hasn't been called yet, so we don't know.
+ // Make sure anything that needs to know isKeyguardSecure() checks and re-checks this
+ // value onVisibilityChanged().
+ Slog.w(TAG, "isKeyguardSecure() called before startKeyguard(), returning false",
+ new Throwable());
+ return false;
+ }
+ return mStatusBarKeyguardViewManager.isSecure();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 9db875f..0b1b883 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -56,6 +56,7 @@ public class CommandQueue extends IStatusBar.Stub {
private static final int MSG_BUZZ_BEEP_BLINKED = 15 << MSG_SHIFT;
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;
public static final int FLAG_EXCLUDE_NONE = 0;
public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -97,6 +98,7 @@ public class CommandQueue extends IStatusBar.Stub {
public void buzzBeepBlinked();
public void notificationLightOff();
public void notificationLightPulse(int argb, int onMillis, int offMillis);
+ public void showScreenPinningRequest();
}
public CommandQueue(Callbacks callbacks, StatusBarIconList list) {
@@ -238,6 +240,12 @@ public class CommandQueue extends IStatusBar.Stub {
}
}
+ public void showScreenPinningRequest() {
+ synchronized (mList) {
+ mHandler.sendEmptyMessage(MSG_SHOW_SCREEN_PIN_REQUEST);
+ }
+ }
+
private final class H extends Handler {
public void handleMessage(Message msg) {
final int what = msg.what & MSG_MASK;
@@ -317,6 +325,9 @@ public class CommandQueue extends IStatusBar.Stub {
case MSG_NOTIFICATION_LIGHT_PULSE:
mCallbacks.notificationLightPulse((Integer) msg.obj, msg.arg1, msg.arg2);
break;
+ case MSG_SHOW_SCREEN_PIN_REQUEST:
+ mCallbacks.showScreenPinningRequest();
+ break;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DismissView.java b/packages/SystemUI/src/com/android/systemui/statusbar/DismissView.java
index 897dbf2..d9276bf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/DismissView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/DismissView.java
@@ -17,12 +17,15 @@
package com.android.systemui.statusbar;
import android.content.Context;
+import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import com.android.systemui.R;
public class DismissView extends StackScrollerDecorView {
+ private boolean mDismissAllInProgress;
+ private DismissViewButton mDismissButton;
public DismissView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -33,7 +36,44 @@ public class DismissView extends StackScrollerDecorView {
return findViewById(R.id.dismiss_text);
}
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mDismissButton = (DismissViewButton) findContentView();
+ }
+
public void setOnButtonClickListener(OnClickListener listener) {
mContent.setOnClickListener(listener);
}
+
+ public boolean isOnEmptySpace(float touchX, float touchY) {
+ return touchX < mContent.getX()
+ || touchX > mContent.getX() + mContent.getWidth()
+ || touchY < mContent.getY()
+ || touchY > mContent.getY() + mContent.getHeight();
+ }
+
+ public void showClearButton() {
+ mDismissButton.showButton();
+ }
+
+ public void setDismissAllInProgress(boolean dismissAllInProgress) {
+ if (dismissAllInProgress) {
+ setClipBounds(null);
+ }
+ mDismissAllInProgress = dismissAllInProgress;
+ }
+
+ @Override
+ public void setClipBounds(Rect clipBounds) {
+ if (mDismissAllInProgress) {
+ // we don't want any clipping to happen!
+ return;
+ }
+ super.setClipBounds(clipBounds);
+ }
+
+ public boolean isButtonVisible() {
+ return mDismissButton.isButtonStatic();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DismissViewButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/DismissViewButton.java
new file mode 100644
index 0000000..f2a5673
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/DismissViewButton.java
@@ -0,0 +1,136 @@
+/*
+ * 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;
+
+import android.content.Context;
+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;
+
+public class DismissViewButton extends Button {
+ private AnimatedVectorDrawable mAnimatedDismissDrawable;
+ private final Drawable mStaticDismissDrawable;
+ private Drawable mActiveDrawable;
+
+ public DismissViewButton(Context context) {
+ this(context, null);
+ }
+
+ public DismissViewButton(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public DismissViewButton(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public DismissViewButton(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ mAnimatedDismissDrawable = (AnimatedVectorDrawable) getContext().getResources().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.setBounds(0,
+ 0,
+ mStaticDismissDrawable.getIntrinsicWidth(),
+ mStaticDismissDrawable.getIntrinsicHeight());
+ mStaticDismissDrawable.setCallback(this);
+ mActiveDrawable = mStaticDismissDrawable;
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ canvas.save();
+ int drawableHeight = mActiveDrawable.getBounds().height();
+ boolean isRtl = (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
+ int dx = isRtl ? getWidth() / 2 + drawableHeight / 2 : getWidth() / 2 - drawableHeight / 2;
+ canvas.translate(dx, getHeight() / 2.0f + drawableHeight /
+ 2.0f);
+ canvas.scale(isRtl ? -1.0f : 1.0f, -1.0f);
+ mActiveDrawable.draw(canvas);
+ canvas.restore();
+ }
+
+ @Override
+ public boolean performClick() {
+ if (!mAnimatedDismissDrawable.isRunning()) {
+ mActiveDrawable = mAnimatedDismissDrawable;
+ mAnimatedDismissDrawable.start();
+ }
+ return super.performClick();
+ }
+
+ @Override
+ protected boolean verifyDrawable(Drawable who) {
+ return super.verifyDrawable(who)
+ || who == mAnimatedDismissDrawable
+ || who == mStaticDismissDrawable;
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+
+ /**
+ * This method returns the drawing rect for the view which is different from the regular
+ * drawing rect, since we layout all children in the {@link NotificationStackScrollLayout} at
+ * position 0 and usually the translation is neglected. The standard implementation doesn't
+ * account for translation.
+ *
+ * @param outRect The (scrolled) drawing bounds of the view.
+ */
+ @Override
+ public void getDrawingRect(Rect outRect) {
+ super.getDrawingRect(outRect);
+ float translationX = ((ViewGroup) mParent).getTranslationX();
+ float translationY = ((ViewGroup) mParent).getTranslationY();
+ outRect.left += translationX;
+ outRect.right += translationX;
+ outRect.top += translationY;
+ outRect.bottom += translationY;
+ }
+
+ public void showButton() {
+ mActiveDrawable = mStaticDismissDrawable;
+ invalidate();
+ }
+
+ /**
+ * @return Whether the button is currently static and not being animated.
+ */
+ public boolean isButtonStatic() {
+ return mActiveDrawable == mStaticDismissDrawable;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DismissViewImageButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/DismissViewImageButton.java
deleted file mode 100644
index d55b0b3..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/DismissViewImageButton.java
+++ /dev/null
@@ -1,64 +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;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageButton;
-import com.android.systemui.R;
-import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
-
-public class DismissViewImageButton extends ImageButton {
- public DismissViewImageButton(Context context) {
- super(context);
- }
-
- public DismissViewImageButton(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public DismissViewImageButton(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- }
-
- public DismissViewImageButton(Context context, AttributeSet attrs, int defStyleAttr,
- int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- }
-
- /**
- * This method returns the drawing rect for the view which is different from the regular
- * drawing rect, since we layout all children in the {@link NotificationStackScrollLayout} at
- * position 0 and usually the translation is neglected. The standard implementation doesn't
- * account for translation.
- *
- * @param outRect The (scrolled) drawing bounds of the view.
- */
- @Override
- public void getDrawingRect(Rect outRect) {
- super.getDrawingRect(outRect);
- float translationX = ((ViewGroup) mParent).getTranslationX();
- float translationY = ((ViewGroup) mParent).getTranslationY();
- outRect.left += translationX;
- outRect.right += translationX;
- outRect.top += translationY;
- outRect.bottom += translationY;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
index df475d5..c9f0260 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
@@ -126,7 +126,8 @@ public class DragDownHelper implements Gefingerpoken {
}
return true;
case MotionEvent.ACTION_UP:
- if (mDraggedFarEnough && mDragDownCallback.onDraggedDown(mStartingChild)) {
+ if (mDraggedFarEnough && mDragDownCallback.onDraggedDown(mStartingChild,
+ (int) (y - mInitialTouchY))) {
if (mStartingChild == null) {
mDragDownCallback.setEmptyDragAmount(0f);
}
@@ -221,7 +222,7 @@ public class DragDownHelper implements Gefingerpoken {
/**
* @return true if the interaction is accepted, false if it should be cancelled
*/
- boolean onDraggedDown(View startingChild);
+ boolean onDraggedDown(View startingChild, int dragLengthY);
void onDragDownReset();
void onThresholdReached();
void onTouchSlopExceeded();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 9196dc8..8ad8406 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -22,11 +22,10 @@ import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.Drawable;
import android.service.notification.StatusBarNotification;
import android.util.AttributeSet;
+import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewGroup;
import android.view.ViewStub;
import android.view.accessibility.AccessibilityEvent;
-
import android.widget.ImageView;
import com.android.systemui.R;
@@ -70,6 +69,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
private NotificationGuts mGuts;
private StatusBarNotification mStatusBarNotification;
+ private boolean mIsHeadsUp;
public void setIconAnimationRunning(boolean running) {
setIconAnimationRunning(running, mPublicLayout);
@@ -118,12 +118,17 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
public void setStatusBarNotification(StatusBarNotification statusBarNotification) {
mStatusBarNotification = statusBarNotification;
+ updateVetoButton();
}
public StatusBarNotification getStatusBarNotification() {
return mStatusBarNotification;
}
+ public void setHeadsUp(boolean isHeadsUp) {
+ mIsHeadsUp = isHeadsUp;
+ }
+
public interface ExpansionLogger {
public void logNotificationExpansion(String key, boolean userAction, boolean expanded);
}
@@ -149,13 +154,16 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
mShowingPublicInitialized = false;
mIsSystemExpanded = false;
mExpansionDisabled = false;
- mPublicLayout.reset();
- mPrivateLayout.reset();
+ mPublicLayout.reset(mIsHeadsUp);
+ mPrivateLayout.reset(mIsHeadsUp);
resetHeight();
logExpansionEvent(false, wasExpanded);
}
public void resetHeight() {
+ if (mIsHeadsUp) {
+ resetActualHeight();
+ }
mMaxExpandHeight = 0;
mWasReset = true;
onHeightReset();
@@ -163,6 +171,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
}
@Override
+ protected boolean filterMotionEvent(MotionEvent event) {
+ return mIsHeadsUp || super.filterMotionEvent(event);
+ }
+
+ @Override
protected void onFinishInflate() {
super.onFinishInflate();
mPublicLayout = (NotificationContentView) findViewById(R.id.expandedPublic);
@@ -194,11 +207,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
}
@Override
- public void setDark(boolean dark, boolean fade) {
- super.setDark(dark, fade);
+ public void setDark(boolean dark, boolean fade, long delay) {
+ super.setDark(dark, fade, delay);
final NotificationContentView showing = getShowingLayout();
if (showing != null) {
- showing.setDark(dark, fade);
+ showing.setDark(dark, fade, delay);
}
}
@@ -291,17 +304,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
* @return Can the underlying notification be cleared?
*/
public boolean isClearable() {
- return mClearable;
- }
-
- /**
- * Set whether the notification can be cleared.
- *
- * @param clearable
- */
- public void setClearable(boolean clearable) {
- mClearable = clearable;
- updateVetoButton();
+ return mStatusBarNotification != null && mStatusBarNotification.isClearable();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
index f85d32b..a18fff2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
@@ -41,7 +41,7 @@ public abstract class ExpandableOutlineView extends ExpandableView {
outline.setRect(0,
mClipTopAmount,
getWidth(),
- Math.max(mActualHeight, mClipTopAmount));
+ Math.max(getActualHeight(), mClipTopAmount));
} else {
outline.setRect(mOutlineRect);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
index c8f756e..ebc663c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
@@ -36,9 +36,10 @@ public abstract class ExpandableView extends FrameLayout {
private final int mMaxNotificationHeight;
private OnHeightChangedListener mOnHeightChangedListener;
- protected int mActualHeight;
+ private int mActualHeight;
protected int mClipTopAmount;
private boolean mActualHeightInitialized;
+ private boolean mDark;
private ArrayList<View> mMatchParentViews = new ArrayList<View>();
public ExpandableView(Context context, AttributeSet attrs) {
@@ -103,6 +104,15 @@ public abstract class ExpandableView extends FrameLayout {
}
}
+ /**
+ * Resets the height of the view on the next layout pass
+ */
+ protected void resetActualHeight() {
+ mActualHeight = 0;
+ mActualHeightInitialized = false;
+ requestLayout();
+ }
+
protected int getInitialHeight() {
return getHeight();
}
@@ -115,7 +125,7 @@ public abstract class ExpandableView extends FrameLayout {
return false;
}
- private boolean filterMotionEvent(MotionEvent event) {
+ protected boolean filterMotionEvent(MotionEvent event) {
return event.getActionMasked() != MotionEvent.ACTION_DOWN
|| event.getY() > mClipTopAmount && event.getY() < mActualHeight;
}
@@ -176,8 +186,14 @@ public abstract class ExpandableView extends FrameLayout {
*
* @param dark Whether the notification should be dark.
* @param fade Whether an animation should be played to change the state.
+ * @param delay If fading, the delay of the animation.
*/
- public void setDark(boolean dark, boolean fade) {
+ public void setDark(boolean dark, boolean fade, long delay) {
+ mDark = dark;
+ }
+
+ public boolean isDark() {
+ return mDark;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index ce35e4b..58067c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -146,8 +146,8 @@ public class KeyguardIndicationController {
try {
long chargingTimeRemaining = mBatteryInfo.computeChargeTimeRemaining();
if (chargingTimeRemaining > 0) {
- String chargingTimeFormatted =
- Formatter.formatShortElapsedTime(mContext, chargingTimeRemaining);
+ String chargingTimeFormatted = Formatter.formatShortElapsedTimeRoundingUpToMinutes(
+ mContext, chargingTimeRemaining);
return mContext.getResources().getString(
R.string.keyguard_indication_charging_time, chargingTimeFormatted);
}
@@ -162,8 +162,9 @@ public class KeyguardIndicationController {
KeyguardUpdateMonitorCallback mUpdateMonitor = new KeyguardUpdateMonitorCallback() {
@Override
public void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) {
- mPowerPluggedIn = status.status == BatteryManager.BATTERY_STATUS_CHARGING
+ boolean isChargingOrFull = status.status == BatteryManager.BATTERY_STATUS_CHARGING
|| status.status == BatteryManager.BATTERY_STATUS_FULL;
+ mPowerPluggedIn = status.isPluggedIn() && isChargingOrFull;
mPowerCharged = status.isCharged();
updateIndication();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java
index 5db680a..0fc46e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java
@@ -23,9 +23,7 @@ import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.RippleDrawable;
import android.util.AttributeSet;
-import android.view.MotionEvent;
import android.view.View;
-import com.android.systemui.R;
/**
* A view that can be used for both the dimmed and normal background of an notification.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index 502490f..914b3d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -27,11 +27,11 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
+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;
/**
@@ -42,14 +42,14 @@ import com.android.systemui.R;
public class NotificationContentView extends FrameLayout {
private static final long ANIMATION_DURATION_LENGTH = 170;
- private static final Paint INVERT_PAINT = createInvertPaint();
- private static final ColorFilter NO_COLOR_FILTER = new ColorFilter();
private final Rect mClipBounds = new Rect();
private View mContractedChild;
private View mExpandedChild;
+ private NotificationViewWrapper mContractedWrapper;
+
private int mSmallHeight;
private int mClipTopAmount;
private int mActualHeight;
@@ -60,11 +60,21 @@ public class NotificationContentView extends FrameLayout {
private boolean mDark;
private final Paint mFadePaint = new Paint();
+ private boolean mAnimate;
+ private ViewTreeObserver.OnPreDrawListener mEnableAnimationPredrawListener
+ = new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ mAnimate = true;
+ getViewTreeObserver().removeOnPreDrawListener(this);
+ return true;
+ }
+ };
public NotificationContentView(Context context, AttributeSet attrs) {
super(context, attrs);
mFadePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
- reset();
+ reset(true);
}
@Override
@@ -73,7 +83,13 @@ public class NotificationContentView extends FrameLayout {
updateClipping();
}
- public void reset() {
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ updateVisibility();
+ }
+
+ public void reset(boolean resetActualHeight) {
if (mContractedChild != null) {
mContractedChild.animate().cancel();
}
@@ -84,8 +100,10 @@ public class NotificationContentView extends FrameLayout {
mContractedChild = null;
mExpandedChild = null;
mSmallHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
- mActualHeight = mSmallHeight;
mContractedVisible = true;
+ if (resetActualHeight) {
+ mActualHeight = mSmallHeight;
+ }
}
public View getContractedChild() {
@@ -104,7 +122,9 @@ public class NotificationContentView extends FrameLayout {
sanitizeContractedLayoutParams(child);
addView(child);
mContractedChild = child;
+ mContractedWrapper = NotificationViewWrapper.wrap(getContext(), child);
selectLayout(false /* animate */, true /* force */);
+ mContractedWrapper.setDark(mDark, false /* animate */, 0 /* delay */);
}
public void setExpandedChild(View child) {
@@ -117,9 +137,31 @@ public class NotificationContentView extends FrameLayout {
selectLayout(false /* animate */, true /* force */);
}
+ @Override
+ protected void onVisibilityChanged(View changedView, int visibility) {
+ super.onVisibilityChanged(changedView, visibility);
+ updateVisibility();
+ }
+
+ private void updateVisibility() {
+ setVisible(isShown());
+ }
+
+ private void setVisible(final boolean isVisible) {
+ if (isVisible) {
+
+ // We only animate if we are drawn at least once, otherwise the view might animate when
+ // it's shown the first time
+ getViewTreeObserver().addOnPreDrawListener(mEnableAnimationPredrawListener);
+ } else {
+ getViewTreeObserver().removeOnPreDrawListener(mEnableAnimationPredrawListener);
+ mAnimate = false;
+ }
+ }
+
public void setActualHeight(int actualHeight) {
mActualHeight = actualHeight;
- selectLayout(true /* animate */, false /* force */);
+ selectLayout(mAnimate /* animate */, false /* force */);
updateClipping();
}
@@ -203,44 +245,20 @@ public class NotificationContentView extends FrameLayout {
public void notifyContentUpdated() {
selectLayout(false /* animate */, true /* force */);
+ if (mContractedChild != null) {
+ mContractedWrapper.notifyContentUpdated();
+ mContractedWrapper.setDark(mDark, false /* animate */, 0 /* delay */);
+ }
}
public boolean isContentExpandable() {
return mExpandedChild != null;
}
- public void setDark(boolean dark, boolean fade) {
+ public void setDark(boolean dark, boolean fade, long delay) {
if (mDark == dark || mContractedChild == null) return;
mDark = dark;
- setImageViewDark(dark, fade, com.android.internal.R.id.right_icon);
- setImageViewDark(dark, fade, com.android.internal.R.id.icon);
- }
-
- private void setImageViewDark(boolean dark, boolean fade, int imageViewId) {
- // TODO: implement fade
- final ImageView v = (ImageView) mContractedChild.findViewById(imageViewId);
- if (v == null) return;
- final Drawable d = v.getBackground();
- if (dark) {
- v.setLayerType(LAYER_TYPE_HARDWARE, INVERT_PAINT);
- if (d != null) {
- v.setTag(R.id.doze_saved_filter_tag, d.getColorFilter() != null ? d.getColorFilter()
- : NO_COLOR_FILTER);
- d.setColorFilter(getResources().getColor(R.color.doze_small_icon_background_color),
- PorterDuff.Mode.SRC_ATOP);
- v.setImageAlpha(getResources().getInteger(R.integer.doze_small_icon_alpha));
- }
- } else {
- v.setLayerType(LAYER_TYPE_NONE, null);
- if (d != null) {
- final ColorFilter filter = (ColorFilter) v.getTag(R.id.doze_saved_filter_tag);
- if (filter != null) {
- d.setColorFilter(filter == NO_COLOR_FILTER ? null : filter);
- v.setTag(R.id.doze_saved_filter_tag, null);
- }
- v.setImageAlpha(0xff);
- }
- }
+ mContractedWrapper.setDark(dark, fade, delay);
}
@Override
@@ -250,16 +268,4 @@ public class NotificationContentView extends FrameLayout {
// layout, and saves us some layers.
return false;
}
-
- private static Paint createInvertPaint() {
- final Paint p = new Paint();
- final float[] invert = {
- -1f, 0f, 0f, 1f, 1f,
- 0f, -1f, 0f, 1f, 1f,
- 0f, 0f, -1f, 1f, 1f,
- 0f, 0f, 0f, 1f, 0f
- };
- p.setColorFilter(new ColorMatrixColorFilter(new ColorMatrix(invert)));
- return p;
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationCustomViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationCustomViewWrapper.java
new file mode 100644
index 0000000..0702d7e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationCustomViewWrapper.java
@@ -0,0 +1,44 @@
+/*
+ * 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;
+
+import android.view.View;
+
+import com.android.systemui.ViewInvertHelper;
+import com.android.systemui.statusbar.phone.NotificationPanelView;
+
+/**
+ * Wraps a notification containing a custom view.
+ */
+public class NotificationCustomViewWrapper extends NotificationViewWrapper {
+
+ private final ViewInvertHelper mInvertHelper;
+
+ protected NotificationCustomViewWrapper(View view) {
+ super(view);
+ mInvertHelper = new ViewInvertHelper(view, NotificationPanelView.DOZE_ANIMATION_DURATION);
+ }
+
+ @Override
+ public void setDark(boolean dark, boolean fade, long delay) {
+ if (fade) {
+ mInvertHelper.fade(dark, delay);
+ } else {
+ mInvertHelper.update(dark);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Prefs.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaViewWrapper.java
index f339401..953c373 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Prefs.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaViewWrapper.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010 The Android Open Source Project
+ * 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.
@@ -11,22 +11,27 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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.policy;
+package com.android.systemui.statusbar;
import android.content.Context;
-import android.content.SharedPreferences;
+import android.view.View;
-public class Prefs {
- private static final String SHARED_PREFS_NAME = "status_bar";
+/**
+ * Wraps a media notification.
+ */
+public class NotificationMediaViewWrapper extends NotificationTemplateViewWrapper {
- public static SharedPreferences read(Context context) {
- return context.getSharedPreferences(Prefs.SHARED_PREFS_NAME, Context.MODE_PRIVATE);
+ protected NotificationMediaViewWrapper(Context ctx, View view) {
+ super(ctx, view);
}
- public static SharedPreferences.Editor edit(Context context) {
- return context.getSharedPreferences(Prefs.SHARED_PREFS_NAME, Context.MODE_PRIVATE).edit();
+ @Override
+ public void setDark(boolean dark, boolean fade, long delay) {
+
+ // Only update the large icon, because the rest is already inverted.
+ setPictureGrayscale(dark, fade, delay);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java
index edfd205..bfa3aa5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java
@@ -21,6 +21,8 @@ import android.util.AttributeSet;
import android.widget.TextView;
import com.android.systemui.R;
+import com.android.systemui.ViewInvertHelper;
+import com.android.systemui.statusbar.phone.NotificationPanelView;
/**
* Container view for overflowing notification icons on Keyguard.
@@ -28,6 +30,8 @@ import com.android.systemui.R;
public class NotificationOverflowContainer extends ActivatableNotificationView {
private NotificationOverflowIconsView mIconsView;
+ private ViewInvertHelper mViewInvertHelper;
+ private boolean mDark;
public NotificationOverflowContainer(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -39,6 +43,20 @@ 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),
+ NotificationPanelView.DOZE_ANIMATION_DURATION);
+ }
+
+ @Override
+ public void setDark(boolean dark, boolean fade, long delay) {
+ super.setDark(dark, fade, delay);
+ if (mDark == dark) return;
+ mDark = dark;
+ if (fade) {
+ mViewInvertHelper.fade(dark, delay);
+ } else {
+ mViewInvertHelper.update(dark);
+ }
}
public NotificationOverflowIconsView getIconsView() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java
new file mode 100644
index 0000000..59b62e5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java
@@ -0,0 +1,251 @@
+/*
+ * 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;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.drawable.Drawable;
+import android.view.View;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.widget.ImageView;
+
+import com.android.systemui.R;
+import com.android.systemui.ViewInvertHelper;
+import com.android.systemui.statusbar.phone.NotificationPanelView;
+
+/**
+ * Wraps a notification view inflated from a template.
+ */
+public class NotificationTemplateViewWrapper extends NotificationViewWrapper {
+
+ private final ColorMatrix mGrayscaleColorMatrix = new ColorMatrix();
+ private final PorterDuffColorFilter mIconColorFilter = new PorterDuffColorFilter(
+ 0, PorterDuff.Mode.SRC_ATOP);
+ private final int mIconDarkAlpha;
+ private final int mIconBackgroundDarkColor;
+ private final Interpolator mLinearOutSlowInInterpolator;
+
+ private int mIconBackgroundColor;
+ private ViewInvertHelper mInvertHelper;
+ private ImageView mIcon;
+ protected ImageView mPicture;
+
+ /** Whether the icon needs to be forced grayscale when in dark mode. */
+ private boolean mIconForceGraysaleWhenDark;
+
+ protected NotificationTemplateViewWrapper(Context ctx, View view) {
+ super(view);
+ mIconDarkAlpha = ctx.getResources().getInteger(R.integer.doze_small_icon_alpha);
+ mIconBackgroundDarkColor =
+ ctx.getResources().getColor(R.color.doze_small_icon_background_color);
+ mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(ctx,
+ android.R.interpolator.linear_out_slow_in);
+ resolveViews();
+ }
+
+ private void resolveViews() {
+ View mainColumn = mView.findViewById(com.android.internal.R.id.notification_main_column);
+ mInvertHelper = mainColumn != null
+ ? new ViewInvertHelper(mainColumn, NotificationPanelView.DOZE_ANIMATION_DURATION)
+ : null;
+ ImageView largeIcon = (ImageView) mView.findViewById(com.android.internal.R.id.icon);
+ ImageView rightIcon = (ImageView) mView.findViewById(com.android.internal.R.id.right_icon);
+ mIcon = resolveIcon(largeIcon, rightIcon);
+ mPicture = resolvePicture(largeIcon);
+ mIconBackgroundColor = resolveBackgroundColor(mIcon);
+
+ // If the icon already has a color filter, we assume that we already forced the icon to be
+ // white when we created the notification.
+ mIconForceGraysaleWhenDark = mIcon != null && mIcon.getDrawable().getColorFilter() != null;
+ }
+
+ private ImageView resolveIcon(ImageView largeIcon, ImageView rightIcon) {
+ return largeIcon != null && largeIcon.getBackground() != null ? largeIcon
+ : rightIcon != null && rightIcon.getVisibility() == View.VISIBLE ? rightIcon
+ : null;
+ }
+
+ private ImageView resolvePicture(ImageView largeIcon) {
+ return largeIcon != null && largeIcon.getBackground() == null
+ ? largeIcon
+ : null;
+ }
+
+ private int resolveBackgroundColor(ImageView icon) {
+ if (icon != null && icon.getBackground() != null) {
+ ColorFilter filter = icon.getBackground().getColorFilter();
+ if (filter instanceof PorterDuffColorFilter) {
+ return ((PorterDuffColorFilter) filter).getColor();
+ }
+ }
+ return 0;
+ }
+
+ @Override
+ public void notifyContentUpdated() {
+ super.notifyContentUpdated();
+
+ // Reinspect the notification.
+ resolveViews();
+ }
+
+ @Override
+ public void setDark(boolean dark, boolean fade, long delay) {
+ if (mInvertHelper != null) {
+ if (fade) {
+ mInvertHelper.fade(dark, delay);
+ } else {
+ mInvertHelper.update(dark);
+ }
+ }
+ if (mIcon != null) {
+ if (fade) {
+ fadeIconColorFilter(mIcon, dark, delay);
+ fadeIconAlpha(mIcon, dark, delay);
+ if (!mIconForceGraysaleWhenDark) {
+ fadeGrayscale(mIcon, dark, delay);
+ }
+ } else {
+ updateIconColorFilter(mIcon, dark);
+ updateIconAlpha(mIcon, dark);
+ if (!mIconForceGraysaleWhenDark) {
+ updateGrayscale(mIcon, dark);
+ }
+ }
+ }
+ setPictureGrayscale(dark, fade, delay);
+ }
+
+ protected void setPictureGrayscale(boolean grayscale, boolean fade, long delay) {
+ if (mPicture != null) {
+ if (fade) {
+ fadeGrayscale(mPicture, grayscale, delay);
+ } else {
+ updateGrayscale(mPicture, grayscale);
+ }
+ }
+ }
+
+ private void startIntensityAnimation(ValueAnimator.AnimatorUpdateListener updateListener,
+ boolean dark, long delay, Animator.AnimatorListener listener) {
+ float startIntensity = dark ? 0f : 1f;
+ float endIntensity = dark ? 1f : 0f;
+ ValueAnimator animator = ValueAnimator.ofFloat(startIntensity, endIntensity);
+ animator.addUpdateListener(updateListener);
+ animator.setDuration(NotificationPanelView.DOZE_ANIMATION_DURATION);
+ animator.setInterpolator(mLinearOutSlowInInterpolator);
+ animator.setStartDelay(delay);
+ if (listener != null) {
+ animator.addListener(listener);
+ }
+ animator.start();
+ }
+
+ private void fadeIconColorFilter(final ImageView target, boolean dark, long delay) {
+ startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ updateIconColorFilter(target, (Float) animation.getAnimatedValue());
+ }
+ }, dark, delay, null /* listener */);
+ }
+
+ private void fadeIconAlpha(final ImageView target, boolean dark, long delay) {
+ startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ float t = (float) animation.getAnimatedValue();
+ target.setImageAlpha((int) (255 * (1f - t) + mIconDarkAlpha * t));
+ }
+ }, dark, delay, null /* listener */);
+ }
+
+ protected void fadeGrayscale(final ImageView target, final boolean dark, long delay) {
+ startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ updateGrayscaleMatrix((float) animation.getAnimatedValue());
+ target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix));
+ }
+ }, dark, delay, new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (!dark) {
+ target.setColorFilter(null);
+ }
+ }
+ });
+ }
+
+ private void updateIconColorFilter(ImageView target, boolean dark) {
+ updateIconColorFilter(target, dark ? 1f : 0f);
+ }
+
+ private void updateIconColorFilter(ImageView target, float intensity) {
+ int color = interpolateColor(mIconBackgroundColor, mIconBackgroundDarkColor, intensity);
+ mIconColorFilter.setColor(color);
+ Drawable background = target.getBackground();
+
+ // The background might be null for legacy notifications. Also, the notification might have
+ // been modified during the animation, so background might be null here.
+ if (background != null) {
+ background.mutate().setColorFilter(mIconColorFilter);
+ }
+ }
+
+ private void updateIconAlpha(ImageView target, boolean dark) {
+ target.setImageAlpha(dark ? mIconDarkAlpha : 255);
+ }
+
+ protected void updateGrayscale(ImageView target, boolean dark) {
+ if (dark) {
+ updateGrayscaleMatrix(1f);
+ target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix));
+ } else {
+ target.setColorFilter(null);
+ }
+ }
+
+ private void updateGrayscaleMatrix(float intensity) {
+ mGrayscaleColorMatrix.setSaturation(1 - intensity);
+ }
+
+ private static int interpolateColor(int source, int target, float t) {
+ int aSource = Color.alpha(source);
+ int rSource = Color.red(source);
+ int gSource = Color.green(source);
+ int bSource = Color.blue(source);
+ int aTarget = Color.alpha(target);
+ int rTarget = Color.red(target);
+ int gTarget = Color.green(target);
+ int bTarget = Color.blue(target);
+ return Color.argb(
+ (int) (aSource * (1f - t) + aTarget * t),
+ (int) (rSource * (1f - t) + rTarget * t),
+ (int) (gSource * (1f - t) + gTarget * t),
+ (int) (bSource * (1f - t) + bTarget * t));
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java
new file mode 100644
index 0000000..78b9739
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java
@@ -0,0 +1,61 @@
+/*
+ * 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;
+
+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 {
+
+ 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);
+ } else {
+ return new NotificationCustomViewWrapper(v);
+ }
+ }
+
+ protected NotificationViewWrapper(View view) {
+ mView = view;
+ }
+
+ /**
+ * In dark mode, we draw as little as possible, assuming a black background.
+ *
+ * @param dark whether we should display ourselves in dark mode
+ * @param fade whether to animate the transition if the mode changes
+ * @param delay if fading, the delay of the animation
+ */
+ public abstract void setDark(boolean dark, boolean fade, long delay);
+
+ /**
+ * Notifies this wrapper that the content of the view might have changed.
+ */
+ public void notifyContentUpdated() {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
index b71c9bf..8e35ee9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
@@ -17,8 +17,10 @@
package com.android.systemui.statusbar;
import android.content.Context;
+import android.telephony.SubscriptionInfo;
import android.util.AttributeSet;
import android.util.Log;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
@@ -29,6 +31,9 @@ import com.android.systemui.R;
import com.android.systemui.statusbar.policy.NetworkControllerImpl;
import com.android.systemui.statusbar.policy.SecurityController;
+import java.util.ArrayList;
+import java.util.List;
+
// Intimately tied to the design of res/layout/signal_cluster_view.xml
public class SignalClusterView
extends LinearLayout
@@ -41,23 +46,26 @@ public class SignalClusterView
NetworkControllerImpl mNC;
SecurityController mSC;
+ private boolean mNoSimsVisible = false;
private boolean mVpnVisible = false;
private boolean mWifiVisible = false;
private int mWifiStrengthId = 0;
- private boolean mMobileVisible = false;
- private int mMobileStrengthId = 0, mMobileTypeId = 0;
private boolean mIsAirplaneMode = false;
private int mAirplaneIconId = 0;
- private String mWifiDescription, mMobileDescription, mMobileTypeDescription;
- private boolean mRoaming;
- private boolean mIsMobileTypeIconWide;
+ private int mAirplaneContentDescription;
+ private String mWifiDescription;
+ private ArrayList<PhoneState> mPhoneStates = new ArrayList<PhoneState>();
- ViewGroup mWifiGroup, mMobileGroup;
- ImageView mVpn, mWifi, mMobile, mMobileType, mAirplane;
+ ViewGroup mWifiGroup;
+ ImageView mVpn, mWifi, mAirplane, mNoSims;
View mWifiAirplaneSpacer;
View mWifiSignalSpacer;
+ LinearLayout mMobileSignalGroup;
private int mWideTypeIconStartPadding;
+ private int mSecondaryTelephonyPadding;
+ private int mEndPadding;
+ private int mEndPaddingNothingVisible;
public SignalClusterView(Context context) {
this(context, null);
@@ -88,6 +96,12 @@ public class SignalClusterView
super.onFinishInflate();
mWideTypeIconStartPadding = getContext().getResources().getDimensionPixelSize(
R.dimen.wide_type_icon_start_padding);
+ mSecondaryTelephonyPadding = getContext().getResources().getDimensionPixelSize(
+ R.dimen.secondary_telephony_padding);
+ mEndPadding = getContext().getResources().getDimensionPixelSize(
+ R.dimen.signal_cluster_battery_padding);
+ mEndPaddingNothingVisible = getContext().getResources().getDimensionPixelSize(
+ R.dimen.no_signal_cluster_battery_padding);
}
@Override
@@ -97,12 +111,14 @@ public class SignalClusterView
mVpn = (ImageView) findViewById(R.id.vpn);
mWifiGroup = (ViewGroup) findViewById(R.id.wifi_combo);
mWifi = (ImageView) findViewById(R.id.wifi_signal);
- mMobileGroup = (ViewGroup) findViewById(R.id.mobile_combo);
- mMobile = (ImageView) findViewById(R.id.mobile_signal);
- mMobileType = (ImageView) findViewById(R.id.mobile_type);
mAirplane = (ImageView) findViewById(R.id.airplane);
+ mNoSims = (ImageView) findViewById(R.id.no_sims);
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);
+ }
apply();
}
@@ -112,10 +128,9 @@ public class SignalClusterView
mVpn = null;
mWifiGroup = null;
mWifi = null;
- mMobileGroup = null;
- mMobile = null;
- mMobileType = null;
mAirplane = null;
+ mMobileSignalGroup.removeAllViews();
+ mMobileSignalGroup = null;
super.onDetachedFromWindow();
}
@@ -143,23 +158,60 @@ public class SignalClusterView
@Override
public void setMobileDataIndicators(boolean visible, int strengthIcon, int typeIcon,
- String contentDescription, String typeContentDescription, boolean roaming,
- boolean isTypeIconWide) {
- mMobileVisible = visible;
- mMobileStrengthId = strengthIcon;
- mMobileTypeId = typeIcon;
- mMobileDescription = contentDescription;
- mMobileTypeDescription = typeContentDescription;
- mRoaming = roaming;
- mIsMobileTypeIconWide = isTypeIconWide;
+ String contentDescription, String typeContentDescription, boolean isTypeIconWide,
+ int subId) {
+ PhoneState state = getOrInflateState(subId);
+ state.mMobileVisible = visible;
+ state.mMobileStrengthId = strengthIcon;
+ state.mMobileTypeId = typeIcon;
+ state.mMobileDescription = contentDescription;
+ state.mMobileTypeDescription = typeContentDescription;
+ state.mIsMobileTypeIconWide = isTypeIconWide;
apply();
}
@Override
- public void setIsAirplaneMode(boolean is, int airplaneIconId) {
+ public void setNoSims(boolean show) {
+ mNoSimsVisible = show;
+ }
+
+ @Override
+ public void setSubs(List<SubscriptionInfo> subs) {
+ // Clear out all old subIds.
+ mPhoneStates.clear();
+ if (mMobileSignalGroup != null) {
+ mMobileSignalGroup.removeAllViews();
+ }
+ final int n = subs.size();
+ for (int i = 0; i < n; i++) {
+ inflatePhoneState(subs.get(i).getSubscriptionId());
+ }
+ }
+
+ private PhoneState getOrInflateState(int subId) {
+ for (PhoneState state : mPhoneStates) {
+ if (state.mSubId == subId) {
+ return state;
+ }
+ }
+ return inflatePhoneState(subId);
+ }
+
+ private PhoneState inflatePhoneState(int subId) {
+ PhoneState state = new PhoneState(subId, mContext);
+ if (mMobileSignalGroup != null) {
+ mMobileSignalGroup.addView(state.mMobileGroup);
+ }
+ mPhoneStates.add(state);
+ return state;
+ }
+
+ @Override
+ public void setIsAirplaneMode(boolean is, int airplaneIconId, int contentDescription) {
mIsAirplaneMode = is;
mAirplaneIconId = airplaneIconId;
+ mAirplaneContentDescription = contentDescription;
apply();
}
@@ -170,8 +222,9 @@ public class SignalClusterView
// ignore content description, so populate manually
if (mWifiVisible && mWifiGroup != null && mWifiGroup.getContentDescription() != null)
event.getText().add(mWifiGroup.getContentDescription());
- if (mMobileVisible && mMobileGroup != null && mMobileGroup.getContentDescription() != null)
- event.getText().add(mMobileGroup.getContentDescription());
+ for (PhoneState state : mPhoneStates) {
+ state.populateAccessibilityEvent(event);
+ }
return super.dispatchPopulateAccessibilityEvent(event);
}
@@ -183,12 +236,13 @@ public class SignalClusterView
mWifi.setImageDrawable(null);
}
- if (mMobile != null) {
- mMobile.setImageDrawable(null);
- }
-
- if (mMobileType != null) {
- mMobileType.setImageDrawable(null);
+ for (PhoneState state : mPhoneStates) {
+ if (state.mMobile != null) {
+ state.mMobile.setImageDrawable(null);
+ }
+ if (state.mMobileType != null) {
+ state.mMobileType.setImageDrawable(null);
+ }
}
if(mAirplane != null) {
@@ -222,17 +276,21 @@ public class SignalClusterView
(mWifiVisible ? "VISIBLE" : "GONE"),
mWifiStrengthId));
- if (mMobileVisible && !mIsAirplaneMode) {
- mMobile.setImageResource(mMobileStrengthId);
- mMobileType.setImageResource(mMobileTypeId);
- mMobileGroup.setContentDescription(mMobileTypeDescription + " " + mMobileDescription);
- mMobileGroup.setVisibility(View.VISIBLE);
- } else {
- mMobileGroup.setVisibility(View.GONE);
+ boolean anyMobileVisible = false;
+ int firstMobileTypeId = 0;
+ for (PhoneState state : mPhoneStates) {
+ if (state.apply(anyMobileVisible)) {
+ if (!anyMobileVisible) {
+ firstMobileTypeId = state.mMobileTypeId;
+ anyMobileVisible = true;
+ }
+ }
}
if (mIsAirplaneMode) {
mAirplane.setImageResource(mAirplaneIconId);
+ mAirplane.setContentDescription(mAirplaneContentDescription != 0 ?
+ mContext.getString(mAirplaneContentDescription) : null);
mAirplane.setVisibility(View.VISIBLE);
} else {
mAirplane.setVisibility(View.GONE);
@@ -244,20 +302,73 @@ public class SignalClusterView
mWifiAirplaneSpacer.setVisibility(View.GONE);
}
- if (mRoaming && mMobileVisible && mWifiVisible) {
+ if (((anyMobileVisible && firstMobileTypeId != 0) || mNoSimsVisible) && mWifiVisible) {
mWifiSignalSpacer.setVisibility(View.VISIBLE);
} else {
mWifiSignalSpacer.setVisibility(View.GONE);
}
- mMobile.setPaddingRelative(mIsMobileTypeIconWide ? mWideTypeIconStartPadding : 0, 0, 0, 0);
+ mNoSims.setVisibility(mNoSimsVisible ? View.VISIBLE : View.GONE);
- if (DEBUG) Log.d(TAG,
- String.format("mobile: %s sig=%d typ=%d",
- (mMobileVisible ? "VISIBLE" : "GONE"),
- mMobileStrengthId, mMobileTypeId));
+ boolean anythingVisible = mNoSimsVisible || mWifiVisible || mIsAirplaneMode
+ || anyMobileVisible || mVpnVisible;
+ setPaddingRelative(0, 0, anythingVisible ? mEndPadding : mEndPaddingNothingVisible, 0);
+ }
+
+ private class PhoneState {
+ private final int mSubId;
+ private boolean mMobileVisible = false;
+ private int mMobileStrengthId = 0, mMobileTypeId = 0;
+ private boolean mIsMobileTypeIconWide;
+ private String mMobileDescription, mMobileTypeDescription;
+
+ private ViewGroup mMobileGroup;
+ private ImageView mMobile, mMobileType;
+
+ public PhoneState(int subId, Context context) {
+ ViewGroup root = (ViewGroup) LayoutInflater.from(context)
+ .inflate(R.layout.mobile_signal_group, null);
+ setViews(root);
+ mSubId = subId;
+ }
+
+ public void setViews(ViewGroup root) {
+ mMobileGroup = root;
+ mMobile = (ImageView) root.findViewById(R.id.mobile_signal);
+ mMobileType = (ImageView) root.findViewById(R.id.mobile_type);
+ }
- mMobileType.setVisibility((mRoaming || mMobileTypeId != 0) ? View.VISIBLE : View.GONE);
+ public boolean apply(boolean isSecondaryIcon) {
+ if (mMobileVisible && !mIsAirplaneMode) {
+ mMobile.setImageResource(mMobileStrengthId);
+ mMobileType.setImageResource(mMobileTypeId);
+ mMobileGroup.setContentDescription(mMobileTypeDescription
+ + " " + mMobileDescription);
+ mMobileGroup.setVisibility(View.VISIBLE);
+ } else {
+ mMobileGroup.setVisibility(View.GONE);
+ }
+
+ // When this isn't next to wifi, give it some extra padding between the signals.
+ mMobileGroup.setPaddingRelative(isSecondaryIcon ? mSecondaryTelephonyPadding : 0,
+ 0, 0, 0);
+ mMobile.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));
+
+ mMobileType.setVisibility(mMobileTypeId != 0 ? View.VISIBLE : View.GONE);
+
+ return mMobileVisible;
+ }
+
+ public void populateAccessibilityEvent(AccessibilityEvent event) {
+ if (mMobileVisible && mMobileGroup != null
+ && mMobileGroup.getContentDescription() != null) {
+ event.getText().add(mMobileGroup.getContentDescription());
+ }
+ }
}
}
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 32fb567..e89e15d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
@@ -45,6 +45,7 @@ public class BarTransitions {
public static final int MODE_LIGHTS_OUT = 3;
public static final int MODE_TRANSPARENT = 4;
public static final int MODE_WARNING = 5;
+ public static final int MODE_LIGHTS_OUT_TRANSPARENT = 6;
public static final int LIGHTS_IN_DURATION = 250;
public static final int LIGHTS_OUT_DURATION = 750;
@@ -75,6 +76,9 @@ public class BarTransitions {
|| mode == MODE_TRANSPARENT)) {
mode = MODE_OPAQUE;
}
+ if (!HIGH_END && (mode == MODE_LIGHTS_OUT_TRANSPARENT)) {
+ mode = MODE_LIGHTS_OUT;
+ }
if (mMode == mode) return;
int oldMode = mMode;
mMode = mode;
@@ -102,6 +106,7 @@ public class BarTransitions {
if (mode == MODE_LIGHTS_OUT) return "MODE_LIGHTS_OUT";
if (mode == MODE_TRANSPARENT) return "MODE_TRANSPARENT";
if (mode == MODE_WARNING) return "MODE_WARNING";
+ if (mode == MODE_LIGHTS_OUT_TRANSPARENT) return "MODE_LIGHTS_OUT_TRANSPARENT";
throw new IllegalArgumentException("Unknown mode " + mode);
}
@@ -109,6 +114,10 @@ public class BarTransitions {
mBarBackground.finishAnimation();
}
+ protected boolean isLightsOut(int mode) {
+ return mode == MODE_LIGHTS_OUT || mode == MODE_LIGHTS_OUT_TRANSPARENT;
+ }
+
private static class BarBackgroundDrawable extends Drawable {
private final int mOpaque;
private final int mSemiTransparent;
@@ -196,7 +205,7 @@ public class BarTransitions {
targetColor = mSemiTransparent;
} else if (mMode == MODE_SEMI_TRANSPARENT) {
targetColor = mSemiTransparent;
- } else if (mMode == MODE_TRANSPARENT) {
+ } else if (mMode == MODE_TRANSPARENT || mMode == MODE_LIGHTS_OUT_TRANSPARENT) {
targetColor = mTransparent;
} else {
targetColor = mOpaque;
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 c5d06b9..6cb5bcc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
@@ -122,9 +122,14 @@ public class DemoStatusIcons extends LinearLayout implements DemoMode {
}
String cast = args.getString("cast");
if (cast != null) {
- int iconId = cast.equals("cast") ? R.drawable.stat_sys_cast : 0;
+ int iconId = cast.equals("show") ? R.drawable.stat_sys_cast : 0;
updateSlot("cast", null, iconId);
}
+ String hotspot = args.getString("hotspot");
+ if (hotspot != null) {
+ int iconId = hotspot.equals("show") ? R.drawable.stat_sys_hotspot : 0;
+ updateSlot("hotspot", null, iconId);
+ }
}
}
@@ -154,7 +159,7 @@ public class DemoStatusIcons extends LinearLayout implements DemoMode {
}
}
StatusBarIcon icon = new StatusBarIcon(iconPkg, UserHandle.CURRENT, iconId, 0, 0, "Demo");
- StatusBarIconView v = new StatusBarIconView(getContext(), null);
+ StatusBarIconView v = new StatusBarIconView(getContext(), null, null);
v.setTag(slot);
v.set(icon);
addView(v, 0, new LinearLayout.LayoutParams(mIconSize, mIconSize));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index b566bbc..6b167b4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -46,30 +46,44 @@ public class DozeParameters {
public void dump(PrintWriter pw) {
pw.println(" DozeParameters:");
pw.print(" getDisplayStateSupported(): "); pw.println(getDisplayStateSupported());
- pw.print(" getPulseDuration(): "); pw.println(getPulseDuration());
- pw.print(" getPulseInDuration(): "); pw.println(getPulseInDuration());
+ pw.print(" getPulseDuration(pickup=false): "); pw.println(getPulseDuration(false));
+ pw.print(" getPulseDuration(pickup=true): "); pw.println(getPulseDuration(true));
+ pw.print(" getPulseInDuration(pickup=false): "); pw.println(getPulseInDuration(false));
+ pw.print(" getPulseInDuration(pickup=true): "); pw.println(getPulseInDuration(true));
+ pw.print(" getPulseInDelay(pickup=false): "); pw.println(getPulseInDelay(false));
+ pw.print(" getPulseInDelay(pickup=true): "); pw.println(getPulseInDelay(true));
pw.print(" getPulseInVisibleDuration(): "); pw.println(getPulseVisibleDuration());
pw.print(" getPulseOutDuration(): "); pw.println(getPulseOutDuration());
pw.print(" getPulseOnSigMotion(): "); pw.println(getPulseOnSigMotion());
pw.print(" getVibrateOnSigMotion(): "); pw.println(getVibrateOnSigMotion());
pw.print(" getPulseOnPickup(): "); pw.println(getPulseOnPickup());
pw.print(" getVibrateOnPickup(): "); pw.println(getVibrateOnPickup());
+ pw.print(" getProxCheckBeforePulse(): "); pw.println(getProxCheckBeforePulse());
pw.print(" getPulseOnNotifications(): "); pw.println(getPulseOnNotifications());
pw.print(" getPulseSchedule(): "); pw.println(getPulseSchedule());
pw.print(" getPulseScheduleResets(): "); pw.println(getPulseScheduleResets());
pw.print(" getPickupVibrationThreshold(): "); pw.println(getPickupVibrationThreshold());
+ pw.print(" getPickupPerformsProxCheck(): "); pw.println(getPickupPerformsProxCheck());
}
public boolean getDisplayStateSupported() {
return getBoolean("doze.display.supported", R.bool.doze_display_state_supported);
}
- public int getPulseDuration() {
- return getPulseInDuration() + getPulseVisibleDuration() + getPulseOutDuration();
+ public int getPulseDuration(boolean pickup) {
+ return getPulseInDuration(pickup) + getPulseVisibleDuration() + getPulseOutDuration();
}
- public int getPulseInDuration() {
- return getInt("doze.pulse.duration.in", R.integer.doze_pulse_duration_in);
+ public int getPulseInDuration(boolean pickup) {
+ return pickup
+ ? getInt("doze.pulse.duration.in.pickup", R.integer.doze_pulse_duration_in_pickup)
+ : getInt("doze.pulse.duration.in", R.integer.doze_pulse_duration_in);
+ }
+
+ public int getPulseInDelay(boolean pickup) {
+ return pickup
+ ? getInt("doze.pulse.delay.in.pickup", R.integer.doze_pulse_delay_in_pickup)
+ : getInt("doze.pulse.delay.in", R.integer.doze_pulse_delay_in);
}
public int getPulseVisibleDuration() {
@@ -96,6 +110,14 @@ public class DozeParameters {
return SystemProperties.getBoolean("doze.vibrate.pickup", false);
}
+ public boolean getProxCheckBeforePulse() {
+ return getBoolean("doze.pulse.proxcheck", R.bool.doze_proximity_check_before_pulse);
+ }
+
+ public boolean getPickupPerformsProxCheck() {
+ return getBoolean("doze.pickup.proxcheck", R.bool.doze_pickup_performs_proximity_check);
+ }
+
public boolean getPulseOnNotifications() {
return getBoolean("doze.pulse.notifications", R.bool.doze_pulse_on_notifications);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
new file mode 100644
index 0000000..3e17328
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
@@ -0,0 +1,266 @@
+/*
+ * 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.phone;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.Handler;
+import android.util.Log;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+
+import com.android.systemui.doze.DozeHost;
+import com.android.systemui.doze.DozeLog;
+
+/**
+ * Controller which handles all the doze animations of the scrims.
+ */
+public class DozeScrimController {
+ private static final String TAG = "DozeScrimController";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private final DozeParameters mDozeParameters;
+ private final Interpolator mPulseInInterpolator = PhoneStatusBar.ALPHA_OUT;
+ private final Interpolator mPulseInInterpolatorPickup;
+ private final Interpolator mPulseOutInterpolator = PhoneStatusBar.ALPHA_IN;
+ private final Interpolator mDozeAnimationInterpolator;
+ private final Handler mHandler = new Handler();
+ private final ScrimController mScrimController;
+
+ private boolean mDozing;
+ private DozeHost.PulseCallback mPulseCallback;
+ private int mPulseReason;
+ private Animator mInFrontAnimator;
+ private Animator mBehindAnimator;
+ private float mInFrontTarget;
+ private float mBehindTarget;
+
+ public DozeScrimController(ScrimController scrimController, Context context) {
+ mScrimController = scrimController;
+ mDozeParameters = new DozeParameters(context);
+ mDozeAnimationInterpolator = mPulseInInterpolatorPickup =
+ AnimationUtils.loadInterpolator(context, android.R.interpolator.linear_out_slow_in);
+ }
+
+ public void setDozing(boolean dozing, boolean animate) {
+ if (mDozing == dozing) return;
+ mDozing = dozing;
+ if (mDozing) {
+ abortAnimations();
+ mScrimController.setDozeBehindAlpha(1f);
+ mScrimController.setDozeInFrontAlpha(1f);
+ } else {
+ cancelPulsing();
+ if (animate) {
+ startScrimAnimation(false /* inFront */, 0f /* target */,
+ NotificationPanelView.DOZE_ANIMATION_DURATION, mDozeAnimationInterpolator);
+ startScrimAnimation(true /* inFront */, 0f /* target */,
+ NotificationPanelView.DOZE_ANIMATION_DURATION, mDozeAnimationInterpolator);
+ } else {
+ abortAnimations();
+ mScrimController.setDozeBehindAlpha(0f);
+ mScrimController.setDozeInFrontAlpha(0f);
+ }
+ }
+ }
+
+ /** When dozing, fade screen contents in and out using the front scrim. */
+ public void pulse(@NonNull DozeHost.PulseCallback callback, int reason) {
+ if (callback == null) {
+ throw new IllegalArgumentException("callback must not be null");
+ }
+
+ if (!mDozing || mPulseCallback != null) {
+ // Pulse suppressed.
+ callback.onPulseFinished();
+ return;
+ }
+
+ // Begin pulse. Note that it's very important that the pulse finished callback
+ // be invoked when we're done so that the caller can drop the pulse wakelock.
+ mPulseCallback = callback;
+ mPulseReason = reason;
+ mHandler.post(mPulseIn);
+ }
+
+ public boolean isPulsing() {
+ return mPulseCallback != null;
+ }
+
+ private void cancelPulsing() {
+ if (DEBUG) Log.d(TAG, "Cancel pulsing");
+
+ if (mPulseCallback != null) {
+ mHandler.removeCallbacks(mPulseIn);
+ mHandler.removeCallbacks(mPulseOut);
+ pulseFinished();
+ }
+ }
+
+ private void pulseStarted() {
+ if (mPulseCallback != null) {
+ mPulseCallback.onPulseStarted();
+ }
+ }
+
+ private void pulseFinished() {
+ if (mPulseCallback != null) {
+ mPulseCallback.onPulseFinished();
+ mPulseCallback = null;
+ }
+ }
+
+ private void abortAnimations() {
+ if (mInFrontAnimator != null) {
+ mInFrontAnimator.cancel();
+ }
+ if (mBehindAnimator != null) {
+ mBehindAnimator.cancel();
+ }
+ }
+
+ private void startScrimAnimation(final boolean inFront, float target, long duration,
+ Interpolator interpolator) {
+ startScrimAnimation(inFront, target, duration, interpolator, 0 /* delay */,
+ null /* endRunnable */);
+ }
+
+ private void startScrimAnimation(final boolean inFront, float target, long duration,
+ Interpolator interpolator, long delay, final Runnable endRunnable) {
+ Animator current = getCurrentAnimator(inFront);
+ if (current != null) {
+ float currentTarget = getCurrentTarget(inFront);
+ if (currentTarget == target) {
+ return;
+ }
+ current.cancel();
+ }
+ ValueAnimator anim = ValueAnimator.ofFloat(getDozeAlpha(inFront), target);
+ anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ float value = (float) animation.getAnimatedValue();
+ setDozeAlpha(inFront, value);
+ }
+ });
+ anim.setInterpolator(interpolator);
+ anim.setDuration(duration);
+ anim.setStartDelay(delay);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ setCurrentAnimator(inFront, null);
+ if (endRunnable != null) {
+ endRunnable.run();
+ }
+ }
+ });
+ anim.start();
+ setCurrentAnimator(inFront, anim);
+ setCurrentTarget(inFront, target);
+ }
+
+ private float getCurrentTarget(boolean inFront) {
+ return inFront ? mInFrontTarget : mBehindTarget;
+ }
+
+ private void setCurrentTarget(boolean inFront, float target) {
+ if (inFront) {
+ mInFrontTarget = target;
+ } else {
+ mBehindTarget = target;
+ }
+ }
+
+ private Animator getCurrentAnimator(boolean inFront) {
+ return inFront ? mInFrontAnimator : mBehindAnimator;
+ }
+
+ private void setCurrentAnimator(boolean inFront, Animator animator) {
+ if (inFront) {
+ mInFrontAnimator = animator;
+ } else {
+ mBehindAnimator = animator;
+ }
+ }
+
+ private void setDozeAlpha(boolean inFront, float alpha) {
+ if (inFront) {
+ mScrimController.setDozeInFrontAlpha(alpha);
+ } else {
+ mScrimController.setDozeBehindAlpha(alpha);
+ }
+ }
+
+ private float getDozeAlpha(boolean inFront) {
+ return inFront
+ ? mScrimController.getDozeInFrontAlpha()
+ : mScrimController.getDozeBehindAlpha();
+ }
+
+ private final Runnable mPulseIn = new Runnable() {
+ @Override
+ public void run() {
+ if (DEBUG) Log.d(TAG, "Pulse in, mDozing=" + mDozing + " mPulseReason="
+ + DozeLog.pulseReasonToString(mPulseReason));
+ if (!mDozing) return;
+ DozeLog.tracePulseStart(mPulseReason);
+ final boolean pickup = mPulseReason == DozeLog.PULSE_REASON_SENSOR_PICKUP;
+ startScrimAnimation(true /* inFront */, 0f,
+ mDozeParameters.getPulseInDuration(pickup),
+ pickup ? mPulseInInterpolatorPickup : mPulseInInterpolator,
+ mDozeParameters.getPulseInDelay(pickup),
+ mPulseInFinished);
+
+ // Signal that the pulse is ready to turn the screen on and draw.
+ pulseStarted();
+ }
+ };
+
+ private final Runnable mPulseInFinished = new Runnable() {
+ @Override
+ public void run() {
+ if (DEBUG) Log.d(TAG, "Pulse in finished, mDozing=" + mDozing);
+ if (!mDozing) return;
+ mHandler.postDelayed(mPulseOut, mDozeParameters.getPulseVisibleDuration());
+ }
+ };
+
+ private final Runnable mPulseOut = new Runnable() {
+ @Override
+ public void run() {
+ if (DEBUG) Log.d(TAG, "Pulse out, mDozing=" + mDozing);
+ if (!mDozing) return;
+ startScrimAnimation(true /* inFront */, 1f, mDozeParameters.getPulseOutDuration(),
+ mPulseOutInterpolator, 0 /* delay */, mPulseOutFinished);
+ }
+ };
+
+ private final Runnable mPulseOutFinished = new Runnable() {
+ @Override
+ public void run() {
+ if (DEBUG) Log.d(TAG, "Pulse out finished");
+ DozeLog.tracePulseFinish();
+
+ // Signal that the pulse is all finished so we can turn the screen off now.
+ pulseFinished();
+ }
+ };
+}
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 f1dcffb..3b8fccc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
@@ -317,7 +317,7 @@ public class KeyguardAffordanceHelper {
animator.addListener(mFlingEndListener);
if (!snapBack) {
startFinishingCircleAnimation(vel * 0.375f, mAnimationEndRunnable);
- mCallback.onAnimationToSideStarted(mTranslation < 0);
+ mCallback.onAnimationToSideStarted(mTranslation < 0, mTranslation, vel);
} else {
reset(true);
}
@@ -442,6 +442,10 @@ public class KeyguardAffordanceHelper {
initIcons();
}
+ public void onRtlPropertiesChanged() {
+ initIcons();
+ }
+
public void reset(boolean animate) {
if (mSwipeAnimator != null) {
mSwipeAnimator.cancel();
@@ -457,7 +461,7 @@ public class KeyguardAffordanceHelper {
*
* @param rightPage Is the page animated to the right page?
*/
- void onAnimationToSideStarted(boolean rightPage);
+ void onAnimationToSideStarted(boolean rightPage, float translation, float vel);
/**
* Notifies the callback the animation to a side page has ended.
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 dd5df12..0c21b20 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -39,12 +39,16 @@ import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import android.widget.TextView;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.EventLogConstants;
+import com.android.systemui.EventLogTags;
import com.android.systemui.R;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.KeyguardAffordanceView;
@@ -72,6 +76,8 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
private static final Intent INSECURE_CAMERA_INTENT =
new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
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 KeyguardAffordanceView mCameraImageView;
private KeyguardAffordanceView mPhoneImageView;
@@ -92,7 +98,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
private PhoneStatusBar mPhoneStatusBar;
private final TrustDrawable mTrustDrawable;
-
+ private final Interpolator mLinearOutSlowInInterpolator;
private int mLastUnlockIconRes = 0;
public KeyguardBottomAreaView(Context context) {
@@ -111,6 +117,8 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mTrustDrawable = new TrustDrawable(mContext);
+ mLinearOutSlowInInterpolator =
+ AnimationUtils.loadInterpolator(context, android.R.interpolator.linear_out_slow_in);
}
private AccessibilityDelegate mAccessibilityDelegate = new AccessibilityDelegate() {
@@ -133,7 +141,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
if (action == ACTION_CLICK) {
if (host == mLockIcon) {
mPhoneStatusBar.animateCollapsePanels(
- CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
+ CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
return true;
} else if (host == mCameraImageView) {
launchCamera();
@@ -212,6 +220,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
public void setPhoneStatusBar(PhoneStatusBar phoneStatusBar) {
mPhoneStatusBar = phoneStatusBar;
+ updateCameraVisibility(); // in case onFinishInflate() was called too early
}
private Intent getCameraIntent() {
@@ -223,6 +232,10 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
}
private void updateCameraVisibility() {
+ if (mCameraImageView == null) {
+ // Things are not set up yet; reply hazy, ask again later
+ return;
+ }
ResolveInfo resolved = mContext.getPackageManager().resolveActivityAsUser(getCameraIntent(),
PackageManager.MATCH_DEFAULT_ONLY,
mLockPatternUtils.getCurrentUser());
@@ -245,13 +258,13 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
private boolean isCameraDisabledByDpm() {
final DevicePolicyManager dpm =
(DevicePolicyManager) getContext().getSystemService(Context.DEVICE_POLICY_SERVICE);
- if (dpm != null) {
+ if (dpm != null && mPhoneStatusBar != null) {
try {
final int userId = ActivityManagerNative.getDefault().getCurrentUser().id;
final int disabledFlags = dpm.getKeyguardDisabledFeatures(null, userId);
final boolean disabledBecauseKeyguardSecure =
(disabledFlags & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA) != 0
- && KeyguardTouchDelegate.getInstance(getContext()).isSecure();
+ && mPhoneStatusBar.isKeyguardSecure();
return dpm.getCameraDisabled(null) || disabledBecauseKeyguardSecure;
} catch (RemoteException e) {
Log.e(TAG, "Can't get userId", e);
@@ -314,6 +327,9 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
}
private void handleTrustCircleClick() {
+ EventLogTags.writeSysuiLockscreenGesture(
+ EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_TAP_LOCK, 0 /* lengthDp - N/A */,
+ 0 /* velocityDp - N/A */);
mIndicationController.showTransientIndication(
R.string.keyguard_indication_trust_disabled);
mLockPatternUtils.requireCredentialEntry(mLockPatternUtils.getCurrentUser());
@@ -382,7 +398,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
// TODO: Real icon for facelock.
int iconRes = mUnlockMethodCache.isFaceUnlockRunning()
? com.android.internal.R.drawable.ic_account_circle
- : mUnlockMethodCache.isMethodInsecure() ? R.drawable.ic_lock_open_24dp
+ : mUnlockMethodCache.isCurrentlyInsecure() ? R.drawable.ic_lock_open_24dp
: R.drawable.ic_lock_24dp;
if (mLastUnlockIconRes != iconRes) {
Drawable icon = mContext.getDrawable(iconRes);
@@ -432,7 +448,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
}
@Override
- public void onMethodSecureChanged(boolean methodSecure) {
+ public void onUnlockMethodStateChanged() {
updateLockIcon();
updateCameraVisibility();
}
@@ -450,6 +466,35 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
}
}
+ public void startFinishDozeAnimation() {
+ long delay = 0;
+ if (mPhoneImageView.getVisibility() == View.VISIBLE) {
+ startFinishDozeAnimationElement(mPhoneImageView, delay);
+ delay += DOZE_ANIMATION_STAGGER_DELAY;
+ }
+ startFinishDozeAnimationElement(mLockIcon, delay);
+ delay += DOZE_ANIMATION_STAGGER_DELAY;
+ if (mCameraImageView.getVisibility() == View.VISIBLE) {
+ startFinishDozeAnimationElement(mCameraImageView, delay);
+ }
+ mIndicationText.setAlpha(0f);
+ mIndicationText.animate()
+ .alpha(1f)
+ .setInterpolator(mLinearOutSlowInInterpolator)
+ .setDuration(NotificationPanelView.DOZE_ANIMATION_DURATION);
+ }
+
+ private void startFinishDozeAnimationElement(View element, long delay) {
+ element.setAlpha(0f);
+ element.setTranslationY(element.getHeight() / 2);
+ element.animate()
+ .alpha(1f)
+ .translationY(0f)
+ .setInterpolator(mLinearOutSlowInInterpolator)
+ .setStartDelay(delay)
+ .setDuration(DOZE_ANIMATION_ELEMENT_DURATION);
+ }
+
private final BroadcastReceiver mDevicePolicyReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
post(new Runnable() {
@@ -477,6 +522,11 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
public void onScreenTurnedOff(int why) {
updateLockIcon();
}
+
+ @Override
+ public void onKeyguardVisibilityChanged(boolean showing) {
+ updateLockIcon();
+ }
};
public void setKeyguardIndicationController(
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 5507944..d0fe32e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -22,6 +22,7 @@ import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardViewBase;
@@ -57,13 +58,14 @@ public class KeyguardBouncer {
mWindowManager = windowManager;
}
- public void show() {
+ public void show(boolean resetSecuritySelection) {
ensureView();
+ if (resetSecuritySelection) {
+ // showPrimarySecurityScreen() updates the current security method. This is needed in
+ // case we are already showing and the current security method changed.
+ mKeyguardView.showPrimarySecurityScreen();
+ }
if (mRoot.getVisibility() == View.VISIBLE || mShowingSoon) {
-
- // show() updates the current security method. This is needed in case we are already
- // showing and the current security method changed.
- mKeyguardView.show();
return;
}
@@ -74,7 +76,7 @@ public class KeyguardBouncer {
// Split up the work over multiple frames.
mChoreographer.postCallbackDelayed(Choreographer.CALLBACK_ANIMATION, mShowRunnable,
- null, 48);
+ null, 16);
}
}
@@ -85,6 +87,7 @@ public class KeyguardBouncer {
mKeyguardView.onResume();
mKeyguardView.startAppearAnimation();
mShowingSoon = false;
+ mKeyguardView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
}
};
@@ -96,7 +99,7 @@ public class KeyguardBouncer {
public void showWithDismissAction(OnDismissAction r) {
ensureView();
mKeyguardView.setOnDismissAction(r);
- show();
+ show(false /* resetSecuritySelection */);
}
public void hide(boolean destroyView) {
@@ -152,7 +155,11 @@ public class KeyguardBouncer {
}
public void prepare() {
+ boolean wasInitialized = mRoot != null;
ensureView();
+ if (wasInitialized) {
+ mKeyguardView.showPrimarySecurityScreen();
+ }
}
private void ensureView() {
@@ -184,18 +191,32 @@ public class KeyguardBouncer {
}
/**
- * @return True if and only if the current security method should be shown before showing
- * the notifications on Keyguard, like SIM PIN/PUK.
+ * @return True if and only if the security method should be shown before showing the
+ * notifications on Keyguard, like SIM PIN/PUK.
*/
public boolean needsFullscreenBouncer() {
if (mKeyguardView != null) {
SecurityMode mode = mKeyguardView.getSecurityMode();
- return mode == SecurityMode.SimPin
- || mode == SecurityMode.SimPuk;
+ return mode == SecurityMode.SimPin || mode == SecurityMode.SimPuk;
+ }
+ return false;
+ }
+
+ /**
+ * Like {@link #needsFullscreenBouncer}, but uses the currently visible security method, which
+ * makes this method much faster.
+ */
+ public boolean isFullscreenBouncer() {
+ if (mKeyguardView != null) {
+ SecurityMode mode = mKeyguardView.getCurrentSecurityMode();
+ return mode == SecurityMode.SimPin || mode == SecurityMode.SimPuk;
}
return false;
}
+ /**
+ * WARNING: This method might cause Binder calls.
+ */
public boolean isSecure() {
return mKeyguardView == null || mKeyguardView.getSecurityMode() != SecurityMode.None;
}
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 650a14f..40c9134 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -36,6 +36,8 @@ import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
import com.android.systemui.statusbar.policy.UserInfoController;
+import java.text.NumberFormat;
+
/**
* The header group on Keyguard.
*/
@@ -150,7 +152,8 @@ public class KeyguardStatusBarView extends RelativeLayout
@Override
public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
- mBatteryLevel.setText(getResources().getString(R.string.battery_level_template, level));
+ String percentage = NumberFormat.getPercentInstance().format((double) level / 100.0);
+ mBatteryLevel.setText(percentage);
boolean changed = mBatteryCharging != charging;
mBatteryCharging = charging;
if (changed) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardTouchDelegate.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardTouchDelegate.java
deleted file mode 100644
index 754075a..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardTouchDelegate.java
+++ /dev/null
@@ -1,208 +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.statusbar.phone;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.Slog;
-import android.view.MotionEvent;
-
-import com.android.internal.policy.IKeyguardService;
-
-import java.util.ArrayList;
-import java.util.List;
-
-
-/**
- * Facilitates event communication between navigation bar and keyguard. Currently used to
- * control WidgetPager in keyguard to expose the camera widget.
- *
- */
-public class KeyguardTouchDelegate {
- // TODO: propagate changes to these to {@link KeyguardServiceDelegate}
- static final String KEYGUARD_PACKAGE = "com.android.systemui";
- static final String KEYGUARD_CLASS = "com.android.systemui.keyguard.KeyguardService";
-
- private static KeyguardTouchDelegate sInstance;
- private static final List<OnKeyguardConnectionListener> sConnectionListeners =
- new ArrayList<OnKeyguardConnectionListener>();
-
- private volatile IKeyguardService mService;
-
- protected static final boolean DEBUG = false;
- protected static final String TAG = "KeyguardTouchDelegate";
-
- private final ServiceConnection mKeyguardConnection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- Slog.v(TAG, "Connected to keyguard");
- mService = IKeyguardService.Stub.asInterface(service);
-
- for (int i = 0; i < sConnectionListeners.size(); i++) {
- OnKeyguardConnectionListener listener = sConnectionListeners.get(i);
- listener.onKeyguardServiceConnected(KeyguardTouchDelegate.this);
- }
- }
-
- @Override
- public void onServiceDisconnected(ComponentName name) {
- Slog.v(TAG, "Disconnected from keyguard");
- mService = null;
- sInstance = null; // force reconnection if this goes away
-
- for (int i = 0; i < sConnectionListeners.size(); i++) {
- OnKeyguardConnectionListener listener = sConnectionListeners.get(i);
- listener.onKeyguardServiceDisconnected(KeyguardTouchDelegate.this);
- }
- }
-
- };
-
- private KeyguardTouchDelegate(Context context) {
- Intent intent = new Intent();
- intent.setClassName(KEYGUARD_PACKAGE, KEYGUARD_CLASS);
- if (!context.bindServiceAsUser(intent, mKeyguardConnection,
- Context.BIND_AUTO_CREATE, UserHandle.OWNER)) {
- if (DEBUG) Slog.v(TAG, "*** Keyguard: can't bind to " + KEYGUARD_CLASS);
- } else {
- if (DEBUG) Slog.v(TAG, "*** Keyguard started");
- }
- }
-
- public static KeyguardTouchDelegate getInstance(Context context) {
- KeyguardTouchDelegate instance = sInstance;
- if (instance == null) {
- instance = sInstance = new KeyguardTouchDelegate(context);
- }
- return instance;
- }
-
- public boolean isSecure() {
- final IKeyguardService service = mService;
- if (service != null) {
- try {
- return service.isSecure();
- } catch (RemoteException e) {
- Slog.e(TAG, "RemoteException calling keyguard.isSecure()!", e);
- }
- } else {
- Slog.w(TAG, "isSecure(): NO SERVICE!");
- }
- return false;
- }
-
- public boolean dispatch(MotionEvent event) {
- final IKeyguardService service = mService;
- if (service != null) {
- try {
- service.dispatch(event);
- return true;
- } catch (RemoteException e) {
- // What to do?
- Slog.e(TAG, "RemoteException sending event to keyguard!", e);
- }
- } else {
- Slog.w(TAG, "dispatch(event): NO SERVICE!");
- }
- return false;
- }
-
- public boolean isInputRestricted() {
- final IKeyguardService service = mService;
- if (service != null) {
- try {
- return service.isInputRestricted();
- } catch (RemoteException e) {
- Slog.w(TAG , "Remote Exception", e);
- }
- } else {
- Slog.w(TAG, "isInputRestricted(): NO SERVICE!");
- }
- return false;
- }
-
- public boolean isShowingAndNotOccluded() {
- final IKeyguardService service = mService;
- if (service != null) {
- try {
- return service.isShowingAndNotOccluded();
- } catch (RemoteException e) {
- Slog.w(TAG , "Remote Exception", e);
- }
- } else {
- Slog.w(TAG, "isShowingAndNotOccluded(): NO SERVICE!");
- }
- return false;
- }
-
- public void showAssistant() {
- final IKeyguardService service = mService;
- if (service != null) {
- try {
- service.showAssistant();
- } catch (RemoteException e) {
- // What to do?
- Slog.e(TAG, "RemoteException launching assistant!", e);
- }
- } else {
- Slog.w(TAG, "showAssistant(event): NO SERVICE!");
- }
- }
-
- public void launchCamera() {
- final IKeyguardService service = mService;
- if (service != null) {
- try {
- service.launchCamera();
- } catch (RemoteException e) {
- // What to do?
- Slog.e(TAG, "RemoteException launching camera!", e);
- }
- } else {
- Slog.w(TAG, "launchCamera(): NO SERVICE!");
- }
- }
-
- public void dismiss() {
- final IKeyguardService service = mService;
- if (service != null) {
- try {
- service.dismiss();
- } catch (RemoteException e) {
- // What to do?
- Slog.e(TAG, "RemoteException dismissing keyguard!", e);
- }
- } else {
- Slog.w(TAG, "dismiss(): NO SERVICE!");
- }
- }
-
- public static void addListener(OnKeyguardConnectionListener listener) {
- sConnectionListeners.add(listener);
- }
-
- public interface OnKeyguardConnectionListener {
-
- void onKeyguardServiceConnected(KeyguardTouchDelegate keyguardTouchDelegate);
- void onKeyguardServiceDisconnected(KeyguardTouchDelegate keyguardTouchDelegate);
- }
-}
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 4715d0a..82f5a9e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
@@ -67,15 +67,18 @@ public class MultiUserSwitch extends FrameLayout implements View.OnClickListener
@Override
public void onClick(View v) {
- if (opensUserSwitcherWhenClicked()) {
+ if (UserSwitcherController.isUserSwitcherAvailable(mUserManager)) {
if (mKeyguardMode) {
if (mKeyguardUserSwitcher != null) {
mKeyguardUserSwitcher.show(true /* animate */);
}
} else {
if (mQsPanel != null) {
- mQsPanel.showDetailAdapter(true,
- mQsPanel.getHost().getUserSwitcherController().userDetailAdapter);
+ UserSwitcherController userSwitcherController =
+ mQsPanel.getHost().getUserSwitcherController();
+ if (userSwitcherController != null) {
+ mQsPanel.showDetailAdapter(true, userSwitcherController.userDetailAdapter);
+ }
}
}
} else {
@@ -92,12 +95,14 @@ public class MultiUserSwitch extends FrameLayout implements View.OnClickListener
if (isClickable()) {
String text;
- if (opensUserSwitcherWhenClicked()) {
+ if (UserSwitcherController.isUserSwitcherAvailable(mUserManager)) {
String currentUser = null;
if (mQsPanel != null) {
UserSwitcherController controller = mQsPanel.getHost()
.getUserSwitcherController();
- currentUser = controller.getCurrentUserName(mContext);
+ if (controller != null) {
+ currentUser = controller.getCurrentUserName(mContext);
+ }
}
if (TextUtils.isEmpty(currentUser)) {
text = mContext.getString(R.string.accessibility_multi_user_switch_switcher);
@@ -121,8 +126,4 @@ public class MultiUserSwitch extends FrameLayout implements View.OnClickListener
return false;
}
- private boolean opensUserSwitcherWhenClicked() {
- UserManager um = UserManager.get(getContext());
- return UserManager.supportsMultipleUsers() && um.isUserSwitcherEnabled();
- }
}
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 f3930ba..7ec84da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
@@ -60,9 +60,13 @@ public final class NavigationBarTransitions extends BarTransitions {
@Override
public void transitionTo(int mode, boolean animate) {
mRequestedMode = mode;
- if (mVertical && (mode == MODE_TRANSLUCENT || mode == MODE_TRANSPARENT)) {
+ if (mVertical) {
// translucent mode not allowed when vertical
- mode = MODE_OPAQUE;
+ 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);
}
@@ -84,7 +88,7 @@ public final class NavigationBarTransitions extends BarTransitions {
applyBackButtonQuiescentAlpha(mode, animate);
// apply to lights out
- applyLightsOut(mode == MODE_LIGHTS_OUT, animate, force);
+ applyLightsOut(isLightsOut(mode), animate, force);
}
private float alphaForMode(int mode) {
@@ -171,7 +175,8 @@ public final class NavigationBarTransitions extends BarTransitions {
applyLightsOut(false, false, false);
try {
- mBarService.setSystemUiVisibility(0, View.SYSTEM_UI_FLAG_LOW_PROFILE);
+ mBarService.setSystemUiVisibility(0, View.SYSTEM_UI_FLAG_LOW_PROFILE,
+ "LightsOutListener");
} catch (android.os.RemoteException ex) {
}
}
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 88e71e2..1e4dfb4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -21,6 +21,7 @@ import android.animation.LayoutTransition.TransitionListener;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
+import android.app.ActivityManagerNative;
import android.app.StatusBarManager;
import android.content.Context;
import android.content.res.Configuration;
@@ -30,17 +31,21 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Message;
+import android.os.RemoteException;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Display;
+import android.view.Gravity;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
+import android.widget.FrameLayout;
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;
@@ -330,7 +335,7 @@ public class NavigationBarView extends LinearLayout {
mDisabledFlags = disabledFlags;
final boolean disableHome = ((disabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0);
- final boolean disableRecent = ((disabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0);
+ boolean disableRecent = ((disabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0);
final boolean disableBack = ((disabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0)
&& ((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) == 0);
final boolean disableSearch = ((disabledFlags & View.STATUS_BAR_DISABLE_SEARCH) != 0);
@@ -355,6 +360,11 @@ public class NavigationBarView extends LinearLayout {
}
}
}
+ if (inLockTask() && disableRecent && !disableHome) {
+ // Don't hide recents when in lock task, it is used for exiting.
+ // Unless home is hidden, then in DPM locked mode and no exit available.
+ disableRecent = false;
+ }
getBackButton() .setVisibility(disableBack ? View.INVISIBLE : View.VISIBLE);
getHomeButton() .setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE);
@@ -363,6 +373,14 @@ public class NavigationBarView extends LinearLayout {
mBarTransitions.applyBackButtonQuiescentAlpha(mBarTransitions.getMode(), true /*animate*/);
}
+ private boolean inLockTask() {
+ try {
+ return ActivityManagerNative.getDefault().isInLockTaskMode();
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
private void setVisibleOrGone(View view, boolean visible) {
if (view != null) {
view.setVisibility(visible ? VISIBLE : GONE);
@@ -503,15 +521,31 @@ public class NavigationBarView extends LinearLayout {
// We swap all children of the 90 and 270 degree layouts, since they are vertical
View rotation90 = mRotatedViews[Surface.ROTATION_90];
swapChildrenOrderIfVertical(rotation90.findViewById(R.id.nav_buttons));
+ adjustExtraKeyGravity(rotation90, isLayoutRtl);
View rotation270 = mRotatedViews[Surface.ROTATION_270];
if (rotation90 != rotation270) {
swapChildrenOrderIfVertical(rotation270.findViewById(R.id.nav_buttons));
+ adjustExtraKeyGravity(rotation270, isLayoutRtl);
}
mIsLayoutRtl = isLayoutRtl;
}
}
+ private void adjustExtraKeyGravity(View navBar, boolean isLayoutRtl) {
+ View menu = navBar.findViewById(R.id.menu);
+ View imeSwitcher = navBar.findViewById(R.id.ime_switcher);
+ if (menu != null) {
+ FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) menu.getLayoutParams();
+ lp.gravity = isLayoutRtl ? Gravity.BOTTOM : Gravity.TOP;
+ menu.setLayoutParams(lp);
+ }
+ if (imeSwitcher != null) {
+ FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) imeSwitcher.getLayoutParams();
+ lp.gravity = isLayoutRtl ? Gravity.BOTTOM : Gravity.TOP;
+ imeSwitcher.setLayoutParams(lp);
+ }
+ }
/**
* Swaps the children order of a LinearLayout if it's orientation is Vertical
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 a6fccb6..0ae34bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -23,8 +23,9 @@ import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Configuration;
+import android.graphics.Canvas;
import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
+import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.MathUtils;
import android.view.MotionEvent;
@@ -38,7 +39,10 @@ import android.widget.FrameLayout;
import android.widget.TextView;
import com.android.keyguard.KeyguardStatusView;
+import com.android.systemui.EventLogTags;
+import com.android.systemui.EventLogConstants;
import com.android.systemui.R;
+import com.android.systemui.qs.QSContainer;
import com.android.systemui.qs.QSPanel;
import com.android.systemui.statusbar.ExpandableView;
import com.android.systemui.statusbar.FlingAnimationUtils;
@@ -52,7 +56,9 @@ import com.android.systemui.statusbar.stack.StackStateAnimator;
public class NotificationPanelView extends PanelView implements
ExpandableView.OnHeightChangedListener, ObservableScrollView.Listener,
View.OnClickListener, NotificationStackScrollLayout.OnOverscrollTopChangedListener,
- KeyguardAffordanceHelper.Callback {
+ KeyguardAffordanceHelper.Callback, NotificationStackScrollLayout.OnEmptySpaceClickListener {
+
+ private static final boolean DEBUG = false;
// Cap and total height of Roboto font. Needs to be adjusted when font for the big clock is
// changed.
@@ -62,15 +68,13 @@ 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 int DOZE_BACKGROUND_COLOR = 0xff000000;
- private static final int TAG_KEY_ANIM = R.id.scrim;
- private static final long DOZE_BACKGROUND_ANIM_DURATION = ScrimController.ANIMATION_DURATION;
+ public static final long DOZE_ANIMATION_DURATION = 700;
private KeyguardAffordanceHelper mAfforanceHelper;
private StatusBarHeaderView mHeader;
private KeyguardUserSwitcher mKeyguardUserSwitcher;
private KeyguardStatusBarView mKeyguardStatusBar;
- private View mQsContainer;
+ private QSContainer mQsContainer;
private QSPanel mQsPanel;
private KeyguardStatusView mKeyguardStatusView;
private ObservableScrollView mScrollView;
@@ -132,6 +136,7 @@ public class NotificationPanelView extends PanelView implements
private Interpolator mFastOutSlowInInterpolator;
private Interpolator mFastOutLinearInterpolator;
+ private Interpolator mDozeAnimationInterpolator;
private ObjectAnimator mClockAnimator;
private int mClockAnimationTarget = -1;
private int mTopPaddingAdjustment;
@@ -143,7 +148,8 @@ public class NotificationPanelView extends PanelView implements
private boolean mBlockTouches;
private int mNotificationScrimWaitDistance;
- private boolean mTwoFingerQsExpand;
+ // Used for two finger gesture as well as accessibility shortcut to QS.
+ private boolean mQsExpandImmediate;
private boolean mTwoFingerQsExpandPossible;
/**
@@ -159,6 +165,7 @@ public class NotificationPanelView extends PanelView implements
private boolean mKeyguardStatusViewAnimating;
private boolean mHeaderAnimatingIn;
private ObjectAnimator mQsContainerAnimator;
+ private ValueAnimator mQsSizeChangeAnimator;
private boolean mShadeEmpty;
@@ -167,8 +174,12 @@ public class NotificationPanelView extends PanelView implements
private boolean mQsTouchAboveFalsingThreshold;
private int mQsFalsingThreshold;
+ private float mKeyguardStatusBarAnimateAlpha = 1f;
+ private int mOldLayoutDirection;
+
public NotificationPanelView(Context context, AttributeSet attrs) {
super(context, attrs);
+ setWillNotDraw(!DEBUG);
}
public void setStatusBar(PhoneStatusBar bar) {
@@ -182,7 +193,7 @@ public class NotificationPanelView extends PanelView implements
mHeader.setOnClickListener(this);
mKeyguardStatusBar = (KeyguardStatusBarView) findViewById(R.id.keyguard_header);
mKeyguardStatusView = (KeyguardStatusView) findViewById(R.id.keyguard_status_view);
- mQsContainer = findViewById(R.id.quick_settings_container);
+ mQsContainer = (QSContainer) findViewById(R.id.quick_settings_container);
mQsPanel = (QSPanel) findViewById(R.id.quick_settings_panel);
mClockView = (TextView) findViewById(R.id.clock_view);
mScrollView = (ObservableScrollView) findViewById(R.id.scroll_view);
@@ -194,11 +205,14 @@ public class NotificationPanelView extends PanelView implements
findViewById(R.id.notification_stack_scroller);
mNotificationStackScroller.setOnHeightChangedListener(this);
mNotificationStackScroller.setOverscrollTopChangedListener(this);
+ mNotificationStackScroller.setOnEmptySpaceClickListener(this);
mNotificationStackScroller.setScrollView(mScrollView);
mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(getContext(),
android.R.interpolator.fast_out_slow_in);
mFastOutLinearInterpolator = AnimationUtils.loadInterpolator(getContext(),
android.R.interpolator.fast_out_linear_in);
+ mDozeAnimationInterpolator = AnimationUtils.loadInterpolator(getContext(),
+ android.R.interpolator.linear_out_slow_in);
mKeyguardBottomArea = (KeyguardBottomAreaView) findViewById(R.id.keyguard_bottom_area);
mQsNavbarScrim = findViewById(R.id.qs_navbar_scrim);
mAfforanceHelper = new KeyguardAffordanceHelper(this, getContext());
@@ -274,21 +288,35 @@ public class NotificationPanelView extends PanelView implements
mKeyguardStatusView.setPivotY((FONT_HEIGHT - CAP_HEIGHT) / 2048f * mClockView.getTextSize());
// Calculate quick setting heights.
+ int oldMaxHeight = mQsMaxExpansionHeight;
mQsMinExpansionHeight = mKeyguardShowing ? 0 : mHeader.getCollapsedHeight() + mQsPeekHeight;
- mQsMaxExpansionHeight = mHeader.getExpandedHeight() + mQsContainer.getHeight();
+ mQsMaxExpansionHeight = mHeader.getExpandedHeight() + mQsContainer.getDesiredHeight();
positionClockAndNotifications();
- if (mQsExpanded) {
- if (mQsFullyExpanded) {
- mQsExpansionHeight = mQsMaxExpansionHeight;
- requestScrollerTopPaddingUpdate(false /* animate */);
+ if (mQsExpanded && mQsFullyExpanded) {
+ mQsExpansionHeight = mQsMaxExpansionHeight;
+ requestScrollerTopPaddingUpdate(false /* animate */);
+ requestPanelHeightUpdate();
+
+ // Size has changed, start an animation.
+ if (mQsMaxExpansionHeight != oldMaxHeight) {
+ startQsSizeChangeAnimation(oldMaxHeight, mQsMaxExpansionHeight);
}
- } else {
+ } else if (!mQsExpanded) {
setQsExpansion(mQsMinExpansionHeight + mLastOverscroll);
- mNotificationStackScroller.setStackHeight(getExpandedHeight());
- updateHeader();
}
+ mNotificationStackScroller.setStackHeight(getExpandedHeight());
+ updateHeader();
mNotificationStackScroller.updateIsSmallScreen(
mHeader.getCollapsedHeight() + mQsPeekHeight);
+
+ // If we are running a size change animation, the animation takes care of the height of
+ // the container. However, if we are not animating, we always need to make the QS container
+ // the desired height so when closing the QS detail, it stays smaller after the size change
+ // animation is finished but the detail view is still being animated away (this animation
+ // takes longer than the size change animation).
+ if (mQsSizeChangeAnimator == null) {
+ mQsContainer.setHeightOverride(mQsContainer.getDesiredHeight());
+ }
}
@Override
@@ -301,6 +329,32 @@ public class NotificationPanelView extends PanelView implements
mSecureCameraLaunchManager.destroy();
}
+ private void startQsSizeChangeAnimation(int oldHeight, final int newHeight) {
+ if (mQsSizeChangeAnimator != null) {
+ oldHeight = (int) mQsSizeChangeAnimator.getAnimatedValue();
+ mQsSizeChangeAnimator.cancel();
+ }
+ mQsSizeChangeAnimator = ValueAnimator.ofInt(oldHeight, newHeight);
+ mQsSizeChangeAnimator.setDuration(300);
+ mQsSizeChangeAnimator.setInterpolator(mFastOutSlowInInterpolator);
+ mQsSizeChangeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ requestScrollerTopPaddingUpdate(false /* animate */);
+ requestPanelHeightUpdate();
+ int height = (int) mQsSizeChangeAnimator.getAnimatedValue();
+ mQsContainer.setHeightOverride(height - mHeader.getExpandedHeight());
+ }
+ });
+ mQsSizeChangeAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mQsSizeChangeAnimator = null;
+ }
+ });
+ mQsSizeChangeAnimator.start();
+ }
+
/**
* Positions the clock and notifications dynamically depending on how many notifications are
* showing.
@@ -422,6 +476,13 @@ public class NotificationPanelView extends PanelView implements
}
}
+ public void expandWithQs() {
+ if (mQsExpansionEnabled) {
+ mQsExpandImmediate = true;
+ }
+ expand();
+ }
+
@Override
public void fling(float vel, boolean expand) {
GestureRecorder gr = ((PhoneStatusBarView) mBar).mBar.getGestureRecorder();
@@ -500,11 +561,11 @@ public class NotificationPanelView extends PanelView implements
}
if (Math.abs(h) > mTouchSlop && Math.abs(h) > Math.abs(x - mInitialTouchX)
&& shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) {
+ mQsTracking = true;
onQsExpansionStarted();
mInitialHeightOnTouch = mQsExpansionHeight;
mInitialTouchY = y;
mInitialTouchX = x;
- mQsTracking = true;
mIntercepting = false;
mNotificationStackScroller.removeLongPressCallback();
return true;
@@ -515,19 +576,22 @@ public class NotificationPanelView extends PanelView implements
case MotionEvent.ACTION_UP:
trackMovement(event);
if (mQsTracking) {
- flingQsWithCurrentVelocity();
+ flingQsWithCurrentVelocity(
+ event.getActionMasked() == MotionEvent.ACTION_CANCEL);
mQsTracking = false;
}
mIntercepting = false;
break;
}
+ return super.onInterceptTouchEvent(event);
+ }
- // Allow closing the whole panel when in SHADE state.
- if (mStatusBarState == StatusBarState.SHADE) {
- return super.onInterceptTouchEvent(event);
- } else {
- return !mQsExpanded && super.onInterceptTouchEvent(event);
- }
+ @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();
}
private void resetDownStates(MotionEvent event) {
@@ -548,9 +612,9 @@ public class NotificationPanelView extends PanelView implements
super.requestDisallowInterceptTouchEvent(disallowIntercept);
}
- private void flingQsWithCurrentVelocity() {
+ private void flingQsWithCurrentVelocity(boolean isCancelMotionEvent) {
float vel = getCurrentVelocity();
- flingSettings(vel, flingExpandsQs(vel));
+ flingSettings(vel, flingExpandsQs(vel) && !isCancelMotionEvent);
}
private boolean flingExpandsQs(float vel) {
@@ -602,7 +666,7 @@ public class NotificationPanelView extends PanelView implements
if (mExpandedHeight != 0) {
handleQsDown(event);
}
- if (!mTwoFingerQsExpand && mQsTracking) {
+ if (!mQsExpandImmediate && mQsTracking) {
onQsTouch(event);
if (!mConflictingQsExpansionGesture) {
return true;
@@ -619,7 +683,7 @@ public class NotificationPanelView extends PanelView implements
if (mTwoFingerQsExpandPossible && event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN
&& event.getPointerCount() == 2
&& event.getY(event.getActionIndex()) < mStatusBarMinHeight) {
- mTwoFingerQsExpand = true;
+ mQsExpandImmediate = true;
requestPanelHeightUpdate();
// Normally, we start listening when the panel is expanded, but here we need to start
@@ -631,10 +695,9 @@ public class NotificationPanelView extends PanelView implements
}
private boolean isInQsArea(float x, float y) {
- return mStatusBarState != StatusBarState.SHADE ||
- (x >= mScrollView.getLeft() && x <= mScrollView.getRight()) &&
- (y <= mNotificationStackScroller.getBottomMostNotificationBottom()
- || y <= mQsContainer.getY() + mQsContainer.getHeight());
+ return (x >= mScrollView.getLeft() && x <= mScrollView.getRight()) &&
+ (y <= mNotificationStackScroller.getBottomMostNotificationBottom()
+ || y <= mQsContainer.getY() + mQsContainer.getHeight());
}
private void handleQsDown(MotionEvent event) {
@@ -720,7 +783,8 @@ public class NotificationPanelView extends PanelView implements
float fraction = getQsExpansionFraction();
if ((fraction != 0f || y >= mInitialTouchY)
&& (fraction != 1f || y <= mInitialTouchY)) {
- flingQsWithCurrentVelocity();
+ flingQsWithCurrentVelocity(
+ event.getActionMasked() == MotionEvent.ACTION_CANCEL);
} else {
mScrollYOverride = -1;
}
@@ -741,11 +805,11 @@ public class NotificationPanelView extends PanelView implements
public void onOverscrolled(float lastTouchX, float lastTouchY, int amount) {
if (mIntercepting && shouldQuickSettingsIntercept(lastTouchX, lastTouchY,
-1 /* yDiff: Not relevant here */)) {
+ mQsTracking = true;
onQsExpansionStarted(amount);
mInitialHeightOnTouch = mQsExpansionHeight;
mInitialTouchY = mLastTouchY;
mInitialTouchX = mLastTouchX;
- mQsTracking = true;
}
}
@@ -792,6 +856,7 @@ public class NotificationPanelView extends PanelView implements
}
mScrollView.scrollTo(0, 0);
setQsExpansion(height);
+ requestPanelHeightUpdate();
}
private void setQsExpanded(boolean expanded) {
@@ -802,6 +867,7 @@ public class NotificationPanelView extends PanelView implements
requestPanelHeightUpdate();
mNotificationStackScroller.setInterceptDelegateEnabled(expanded);
mStatusBar.setQsExpanded(expanded);
+ mQsPanel.setExpanded(expanded);
}
}
@@ -908,6 +974,8 @@ public class NotificationPanelView extends PanelView implements
@Override
public void run() {
mKeyguardStatusBar.setVisibility(View.INVISIBLE);
+ mKeyguardStatusBar.setAlpha(1f);
+ mKeyguardStatusBarAnimateAlpha = 1f;
}
};
@@ -917,10 +985,31 @@ public class NotificationPanelView extends PanelView implements
.setStartDelay(mStatusBar.getKeyguardFadingAwayDelay())
.setDuration(mStatusBar.getKeyguardFadingAwayDuration()/2)
.setInterpolator(PhoneStatusBar.ALPHA_OUT)
+ .setUpdateListener(mStatusBarAnimateAlphaListener)
.withEndAction(mAnimateKeyguardStatusBarInvisibleEndRunnable)
.start();
}
+ private final ValueAnimator.AnimatorUpdateListener mStatusBarAnimateAlphaListener =
+ new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ mKeyguardStatusBarAnimateAlpha = mKeyguardStatusBar.getAlpha();
+ }
+ };
+
+ private void animateKeyguardStatusBarIn() {
+ mKeyguardStatusBar.setVisibility(View.VISIBLE);
+ mKeyguardStatusBar.setAlpha(0f);
+ mKeyguardStatusBar.animate()
+ .alpha(1f)
+ .setStartDelay(0)
+ .setDuration(DOZE_ANIMATION_DURATION)
+ .setInterpolator(mDozeAnimationInterpolator)
+ .setUpdateListener(mStatusBarAnimateAlphaListener)
+ .start();
+ }
+
private final Runnable mAnimateKeyguardBottomAreaInvisibleEndRunnable = new Runnable() {
@Override
public void run() {
@@ -1011,7 +1100,7 @@ public class NotificationPanelView extends PanelView implements
? View.VISIBLE
: View.INVISIBLE);
if (mKeyguardUserSwitcher != null && mQsExpanded && !mStackScrollerOverscrolling) {
- mKeyguardUserSwitcher.hide(true /* animate */);
+ mKeyguardUserSwitcher.hideIfNotSimple(true /* animate */);
}
}
@@ -1047,6 +1136,9 @@ public class NotificationPanelView extends PanelView implements
R.string.accessibility_desc_quick_settings));
mLastAnnouncementWasQuickSettings = true;
}
+ if (DEBUG) {
+ invalidate();
+ }
}
private String getKeyguardOrLockScreenString() {
@@ -1073,7 +1165,7 @@ public class NotificationPanelView extends PanelView implements
private void setQsTranslation(float height) {
if (!mHeaderAnimatingIn) {
- mQsContainer.setY(height - mQsContainer.getHeight() + getHeaderTranslation());
+ mQsContainer.setY(height - mQsContainer.getDesiredHeight() + getHeaderTranslation());
}
if (mKeyguardShowing) {
mHeader.setY(interpolate(getQsExpansionFraction(), -mHeader.getHeight(), 0));
@@ -1081,9 +1173,28 @@ public class NotificationPanelView extends PanelView implements
}
private float calculateQsTopPadding() {
- // We can only do the smoother transition on Keyguard when we also are not collapsing from a
- // scrolled quick settings.
- if (mKeyguardShowing && mScrollYOverride == -1) {
+ if (mKeyguardShowing
+ && (mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted)) {
+
+ // Either QS pushes the notifications down when fully expanded, or QS is fully above the
+ // notifications (mostly on tablets). maxNotifications denotes the normal top padding
+ // on Keyguard, maxQs denotes the top padding from the quick settings panel. We need to
+ // take the maximum and linearly interpolate with the panel expansion for a nice motion.
+ int maxNotifications = mClockPositionResult.stackScrollerPadding
+ - mClockPositionResult.stackScrollerPaddingAdjustment
+ - mNotificationTopPadding;
+ int maxQs = getTempQsMaxExpansion();
+ int max = mStatusBarState == StatusBarState.KEYGUARD
+ ? Math.max(maxNotifications, maxQs)
+ : maxQs;
+ return (int) interpolate(getExpandedFraction(),
+ mQsMinExpansionHeight, max);
+ } else if (mQsSizeChangeAnimator != null) {
+ return (int) mQsSizeChangeAnimator.getAnimatedValue();
+ } else if (mKeyguardShowing && mScrollYOverride == -1) {
+
+ // We can only do the smoother transition on Keyguard when we also are not collapsing
+ // from a scrolled quick settings.
return interpolate(getQsExpansionFraction(),
mNotificationStackScroller.getIntrinsicPadding() - mNotificationTopPadding,
mQsMaxExpansionHeight);
@@ -1095,7 +1206,9 @@ public class NotificationPanelView extends PanelView implements
private void requestScrollerTopPaddingUpdate(boolean animate) {
mNotificationStackScroller.updateTopPadding(calculateQsTopPadding(),
mScrollView.getScrollY(),
- mAnimateNextTopPaddingChange || animate);
+ mAnimateNextTopPaddingChange || animate,
+ mKeyguardShowing
+ && (mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted));
mAnimateNextTopPaddingChange = false;
}
@@ -1190,11 +1303,9 @@ public class NotificationPanelView extends PanelView implements
@Override
protected boolean isScrolledToBottom() {
- if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
- return true;
- }
if (!isInSettings()) {
- return mNotificationStackScroller.isScrolledToBottom();
+ return mStatusBar.getBarState() == StatusBarState.KEYGUARD
+ || mNotificationStackScroller.isScrolledToBottom();
} else {
return mScrollView.isScrolledToBottom();
}
@@ -1210,8 +1321,8 @@ public class NotificationPanelView extends PanelView implements
min = Math.max(min, minHeight);
}
int maxHeight;
- if (mTwoFingerQsExpand || mQsExpanded || mIsExpanding && mQsExpandedWhenExpandingStarted) {
- maxHeight = Math.max(calculatePanelHeightQsExpanded(), calculatePanelHeightShade());
+ if (mQsExpandImmediate || mQsExpanded || mIsExpanding && mQsExpandedWhenExpandingStarted) {
+ maxHeight = calculatePanelHeightQsExpanded();
} else {
maxHeight = calculatePanelHeightShade();
}
@@ -1225,18 +1336,26 @@ public class NotificationPanelView extends PanelView implements
@Override
protected void onHeightUpdated(float expandedHeight) {
- if (!mQsExpanded) {
+ if (!mQsExpanded || mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted) {
positionClockAndNotifications();
}
- if (mTwoFingerQsExpand || mQsExpanded && !mQsTracking && mQsExpansionAnimator == null
+ if (mQsExpandImmediate || mQsExpanded && !mQsTracking && mQsExpansionAnimator == null
&& !mQsExpansionFromOverscroll) {
- float panelHeightQsCollapsed = mNotificationStackScroller.getIntrinsicPadding()
- + mNotificationStackScroller.getMinStackHeight()
- + mNotificationStackScroller.getNotificationTopPadding();
- float panelHeightQsExpanded = calculatePanelHeightQsExpanded();
- float t = (expandedHeight - panelHeightQsCollapsed)
- / (panelHeightQsExpanded - panelHeightQsCollapsed);
+ float t;
+ if (mKeyguardShowing) {
+ // On Keyguard, interpolate the QS expansion linearly to the panel expansion
+ t = expandedHeight / getMaxPanelHeight();
+ } else {
+
+ // In Shade, interpolate linearly such that QS is closed whenever panel height is
+ // minimum QS expansion + minStackHeight
+ float panelHeightQsCollapsed = mNotificationStackScroller.getIntrinsicPadding()
+ + mNotificationStackScroller.getMinStackHeight();
+ float panelHeightQsExpanded = calculatePanelHeightQsExpanded();
+ t = (expandedHeight - panelHeightQsCollapsed)
+ / (panelHeightQsExpanded - panelHeightQsCollapsed);
+ }
setQsExpansion(mQsMinExpansionHeight
+ t * (getTempQsMaxExpansion() - mQsMinExpansionHeight));
}
@@ -1244,6 +1363,9 @@ public class NotificationPanelView extends PanelView implements
updateHeader();
updateUnlockIcon();
updateNotificationTranslucency();
+ if (DEBUG) {
+ invalidate();
+ }
}
/**
@@ -1270,10 +1392,30 @@ public class NotificationPanelView extends PanelView implements
float notificationHeight = mNotificationStackScroller.getHeight()
- mNotificationStackScroller.getEmptyBottomMargin()
- mNotificationStackScroller.getTopPadding();
- float totalHeight = mQsMaxExpansionHeight + notificationHeight
- + mNotificationStackScroller.getNotificationTopPadding();
+
+ // When only empty shade view is visible in QS collapsed state, simulate that we would have
+ // it in expanded QS state as well so we don't run into troubles when fading the view in/out
+ // and expanding/collapsing the whole panel from/to quick settings.
+ if (mNotificationStackScroller.getNotGoneChildCount() == 0
+ && mShadeEmpty) {
+ notificationHeight = mNotificationStackScroller.getEmptyShadeViewHeight()
+ + mNotificationStackScroller.getBottomStackPeekSize()
+ + mNotificationStackScroller.getCollapseSecondCardPadding();
+ }
+ int maxQsHeight = mQsMaxExpansionHeight;
+
+ // If an animation is changing the size of the QS panel, take the animated value.
+ if (mQsSizeChangeAnimator != null) {
+ maxQsHeight = (int) mQsSizeChangeAnimator.getAnimatedValue();
+ }
+ float totalHeight = Math.max(
+ maxQsHeight + mNotificationStackScroller.getNotificationTopPadding(),
+ mStatusBarState == StatusBarState.KEYGUARD
+ ? mClockPositionResult.stackScrollerPadding - mTopPaddingAdjustment
+ : 0)
+ + notificationHeight;
if (totalHeight > mNotificationStackScroller.getHeight()) {
- float fullyCollapsedHeight = mQsMaxExpansionHeight
+ float fullyCollapsedHeight = maxQsHeight
+ mNotificationStackScroller.getMinStackHeight()
+ mNotificationStackScroller.getNotificationTopPadding()
- getScrollViewScrollY();
@@ -1283,7 +1425,7 @@ public class NotificationPanelView extends PanelView implements
}
private int getScrollViewScrollY() {
- if (mScrollYOverride != -1) {
+ if (mScrollYOverride != -1 && !mQsTracking) {
return mScrollYOverride;
} else {
return mScrollView.getScrollY();
@@ -1386,7 +1528,8 @@ public class NotificationPanelView extends PanelView implements
alphaNotifications = MathUtils.constrain(alphaNotifications, 0, 1);
alphaNotifications = (float) Math.pow(alphaNotifications, 0.75);
float alphaQsExpansion = 1 - Math.min(1, getQsExpansionFraction() * 2);
- mKeyguardStatusBar.setAlpha(Math.min(alphaNotifications, alphaQsExpansion));
+ mKeyguardStatusBar.setAlpha(Math.min(alphaNotifications, alphaQsExpansion)
+ * mKeyguardStatusBarAnimateAlpha);
mKeyguardBottomArea.setAlpha(Math.min(1 - getQsExpansionFraction(), alphaNotifications));
setQsTranslation(mQsExpansionHeight);
}
@@ -1403,7 +1546,7 @@ public class NotificationPanelView extends PanelView implements
super.onExpandingStarted();
mNotificationStackScroller.onExpansionStarted();
mIsExpanding = true;
- mQsExpandedWhenExpandingStarted = mQsExpanded;
+ mQsExpandedWhenExpandingStarted = mQsFullyExpanded;
if (mQsExpanded) {
onQsExpansionStarted();
}
@@ -1420,7 +1563,7 @@ public class NotificationPanelView extends PanelView implements
} else {
setListening(true);
}
- mTwoFingerQsExpand = false;
+ mQsExpandImmediate = false;
mTwoFingerQsExpandPossible = false;
}
@@ -1438,7 +1581,7 @@ public class NotificationPanelView extends PanelView implements
@Override
protected void setOverExpansion(float overExpansion, boolean isPixels) {
- if (mConflictingQsExpansionGesture || mTwoFingerQsExpand) {
+ if (mConflictingQsExpansionGesture || mQsExpandImmediate) {
return;
}
if (mStatusBar.getBarState() != StatusBarState.KEYGUARD) {
@@ -1457,11 +1600,12 @@ public class NotificationPanelView extends PanelView implements
@Override
protected void onTrackingStarted() {
super.onTrackingStarted();
+ if (mQsFullyExpanded) {
+ mQsExpandImmediate = true;
+ }
if (mStatusBar.getBarState() == StatusBarState.KEYGUARD
|| mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) {
mAfforanceHelper.animateHideLeftRightIcon();
- } else if (mQsExpanded) {
- mTwoFingerQsExpand = true;
}
}
@@ -1516,6 +1660,14 @@ public class NotificationPanelView extends PanelView implements
}
@Override
+ public void onRtlPropertiesChanged(int layoutDirection) {
+ if (layoutDirection != mOldLayoutDirection) {
+ mAfforanceHelper.onRtlPropertiesChanged();
+ mOldLayoutDirection = layoutDirection;
+ }
+ }
+
+ @Override
public void onClick(View v) {
if (v == mHeader) {
onQsExpansionStarted();
@@ -1528,15 +1680,23 @@ public class NotificationPanelView extends PanelView implements
}
@Override
- public void onAnimationToSideStarted(boolean rightPage) {
+ public void onAnimationToSideStarted(boolean rightPage, float translation, float vel) {
boolean start = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? rightPage : !rightPage;
mIsLaunchTransitionRunning = true;
mLaunchAnimationEndRunnable = null;
+ float displayDensity = mStatusBar.getDisplayDensity();
+ int lengthDp = Math.abs((int) (translation / displayDensity));
+ int velocityDp = Math.abs((int) (vel / displayDensity));
if (start) {
+ EventLogTags.writeSysuiLockscreenGesture(
+ EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_SWIPE_DIALER, lengthDp, velocityDp);
mKeyguardBottomArea.launchPhone();
} else {
+ EventLogTags.writeSysuiLockscreenGesture(
+ EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_SWIPE_CAMERA, lengthDp, velocityDp);
mSecureCameraLaunchManager.startSecureCameraLaunch();
}
+ mStatusBar.startLaunchTransitionTimeout();
mBlockTouches = true;
}
@@ -1665,7 +1825,7 @@ public class NotificationPanelView extends PanelView implements
@Override
protected boolean fullyExpandedClearAllVisible() {
return mNotificationStackScroller.isDismissViewNotGone()
- && mNotificationStackScroller.isScrolledToBottom() && !mTwoFingerQsExpand;
+ && mNotificationStackScroller.isScrolledToBottom() && !mQsExpandImmediate;
}
@Override
@@ -1735,19 +1895,20 @@ public class NotificationPanelView extends PanelView implements
return (1 - t) * start + t * end;
}
- private void updateKeyguardStatusBarVisibility() {
- mKeyguardStatusBar.setVisibility(mKeyguardShowing && !mDozing ? VISIBLE : INVISIBLE);
- }
-
- public void setDozing(boolean dozing) {
+ public void setDozing(boolean dozing, boolean animate) {
if (dozing == mDozing) return;
mDozing = dozing;
if (mDozing) {
- setBackgroundColorAlpha(this, DOZE_BACKGROUND_COLOR, 0xff, false /*animate*/);
+ mKeyguardStatusBar.setVisibility(View.INVISIBLE);
+ mKeyguardBottomArea.setVisibility(View.INVISIBLE);
} else {
- setBackgroundColorAlpha(this, DOZE_BACKGROUND_COLOR, 0, true /*animate*/);
+ mKeyguardBottomArea.setVisibility(View.VISIBLE);
+ mKeyguardStatusBar.setVisibility(View.VISIBLE);
+ if (animate) {
+ animateKeyguardStatusBarIn();
+ mKeyguardBottomArea.startFinishDozeAnimation();
+ }
}
- updateKeyguardStatusBarVisibility();
}
@Override
@@ -1755,51 +1916,6 @@ public class NotificationPanelView extends PanelView implements
return mDozing;
}
- private static void setBackgroundColorAlpha(final View target, int rgb, int targetAlpha,
- boolean animate) {
- int currentAlpha = getBackgroundAlpha(target);
- if (currentAlpha == targetAlpha) {
- return;
- }
- final int r = Color.red(rgb);
- final int g = Color.green(rgb);
- final int b = Color.blue(rgb);
- Object runningAnim = target.getTag(TAG_KEY_ANIM);
- if (runningAnim instanceof ValueAnimator) {
- ((ValueAnimator) runningAnim).cancel();
- }
- if (!animate) {
- target.setBackgroundColor(Color.argb(targetAlpha, r, g, b));
- return;
- }
- ValueAnimator anim = ValueAnimator.ofInt(currentAlpha, targetAlpha);
- anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- int value = (int) animation.getAnimatedValue();
- target.setBackgroundColor(Color.argb(value, r, g, b));
- }
- });
- anim.setDuration(DOZE_BACKGROUND_ANIM_DURATION);
- anim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- target.setTag(TAG_KEY_ANIM, null);
- }
- });
- anim.start();
- target.setTag(TAG_KEY_ANIM, anim);
- }
-
- private static int getBackgroundAlpha(View view) {
- if (view.getBackground() instanceof ColorDrawable) {
- ColorDrawable drawable = (ColorDrawable) view.getBackground();
- return Color.alpha(drawable.getColor());
- } else {
- return 0;
- }
- }
-
public void setShadeEmpty(boolean shadeEmpty) {
mShadeEmpty = shadeEmpty;
updateEmptyShadeView();
@@ -1833,4 +1949,35 @@ public class NotificationPanelView extends PanelView implements
public void onScreenTurnedOn() {
mKeyguardStatusView.refreshTime();
}
+
+ @Override
+ public void onEmptySpaceClicked(float x, float y) {
+ onEmptySpaceClick(x);
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ super.dispatchDraw(canvas);
+ if (DEBUG) {
+ Paint p = new Paint();
+ p.setColor(Color.RED);
+ p.setStrokeWidth(2);
+ p.setStyle(Paint.Style.STROKE);
+ canvas.drawLine(0, getMaxPanelHeight(), getWidth(), getMaxPanelHeight(), p);
+ p.setColor(Color.BLUE);
+ canvas.drawLine(0, getExpandedHeight(), getWidth(), getExpandedHeight(), p);
+ p.setColor(Color.GREEN);
+ canvas.drawLine(0, calculatePanelHeightQsExpanded(), getWidth(),
+ calculatePanelHeightQsExpanded(), p);
+ p.setColor(Color.YELLOW);
+ canvas.drawLine(0, calculatePanelHeightShade(), getWidth(),
+ calculatePanelHeightShade(), p);
+ p.setColor(Color.MAGENTA);
+ canvas.drawLine(0, calculateQsTopPadding(), getWidth(),
+ calculateQsTopPadding(), p);
+ p.setColor(Color.CYAN);
+ canvas.drawLine(0, mNotificationStackScroller.getTopPadding(), getWidth(),
+ mNotificationStackScroller.getTopPadding(), p);
+ }
+ }
}
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 f74d2f4..3efaaff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
@@ -236,4 +236,8 @@ public class PanelBar extends FrameLayout {
public void 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 a7ff0bd..d86ccee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -32,6 +32,8 @@ import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
+import com.android.systemui.EventLogConstants;
+import com.android.systemui.EventLogTags;
import com.android.systemui.R;
import com.android.systemui.doze.DozeLog;
import com.android.systemui.statusbar.FlingAnimationUtils;
@@ -68,9 +70,9 @@ public abstract class PanelView extends FrameLayout {
protected int mTouchSlop;
protected boolean mHintAnimationRunning;
private boolean mOverExpandedBeforeFling;
- private float mOriginalIndicationY;
private boolean mTouchAboveFalsingThreshold;
private int mUnlockFalsingThreshold;
+ private boolean mTouchStartedInEmptyArea;
private ValueAnimator mHeightAnimator;
private ObjectAnimator mPeekAnimator;
@@ -98,6 +100,7 @@ public abstract class PanelView extends FrameLayout {
private boolean mCollapseAfterPeek;
private boolean mExpanding;
private boolean mGestureWaitForTouchSlop;
+ private boolean mDozingOnDown;
private Runnable mPeekRunnable = new Runnable() {
@Override
public void run() {
@@ -107,7 +110,7 @@ public abstract class PanelView extends FrameLayout {
};
protected void onExpandingFinished() {
- mClosing = false;
+ endClosing();
mBar.onExpandingFinished();
}
@@ -244,15 +247,14 @@ public abstract class PanelView extends FrameLayout {
mUpdateFlingOnLayout = false;
mPeekTouching = mPanelClosedOnDown;
mTouchAboveFalsingThreshold = false;
+ mDozingOnDown = isDozing();
if (mVelocityTracker == null) {
initVelocityTracker();
}
trackMovement(event);
if (!waitForTouchSlop || (mHeightAnimator != null && !mHintAnimationRunning) ||
mPeekPending || mPeekAnimator != null) {
- if (mHeightAnimator != null) {
- mHeightAnimator.cancel(); // end any outstanding animations
- }
+ cancelHeightAnimator();
cancelPeek();
mTouchSlopExceeded = (mHeightAnimator != null && !mHintAnimationRunning)
|| mPeekPending || mPeekAnimator != null;
@@ -287,15 +289,13 @@ public abstract class PanelView extends FrameLayout {
|| mInitialOffsetOnTouch == 0f)) {
mTouchSlopExceeded = true;
if (waitForTouchSlop && !mTracking) {
- if (!mJustPeeked) {
+ if (!mJustPeeked && mInitialOffsetOnTouch != 0f) {
mInitialOffsetOnTouch = mExpandedHeight;
mInitialTouchX = x;
mInitialTouchY = y;
h = 0;
}
- if (mHeightAnimator != null) {
- mHeightAnimator.cancel(); // end any outstanding animations
- }
+ cancelHeightAnimator();
removeCallbacks(mPeekRunnable);
mPeekPending = false;
onTrackingStarted();
@@ -334,11 +334,21 @@ public abstract class PanelView extends FrameLayout {
vectorVel = (float) Math.hypot(
mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
}
- boolean expand = flingExpands(vel, vectorVel);
+ boolean expand = flingExpands(vel, vectorVel)
+ || event.getActionMasked() == MotionEvent.ACTION_CANCEL;
onTrackingStopped(expand);
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();
+ int heightDp = (int) Math.abs((y - mInitialTouchY) / displayDensity);
+ int velocityDp = (int) Math.abs(vel / displayDensity);
+ EventLogTags.writeSysuiLockscreenGesture(
+ EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_SWIPE_UP_UNLOCK,
+ heightDp, velocityDp);
+ }
fling(vel, expand);
mUpdateFlingOnLayout = expand && mPanelClosedOnDown && !mHasLayoutedSinceDown;
if (mUpdateFlingOnLayout) {
@@ -372,7 +382,7 @@ public abstract class PanelView extends FrameLayout {
}
protected void onTrackingStarted() {
- mClosing = false;
+ endClosing();
mTracking = true;
mCollapseAfterPeek = false;
mBar.onTrackingStarted(PanelView.this);
@@ -407,21 +417,21 @@ public abstract class PanelView extends FrameLayout {
mStatusBar.userActivity();
if (mHeightAnimator != null && !mHintAnimationRunning ||
mPeekPending || mPeekAnimator != null) {
- if (mHeightAnimator != null) {
- mHeightAnimator.cancel(); // end any outstanding animations
- }
+ cancelHeightAnimator();
cancelPeek();
mTouchSlopExceeded = true;
return true;
}
mInitialTouchY = y;
mInitialTouchX = x;
+ mTouchStartedInEmptyArea = !isInContentBounds(x, y);
mTouchSlopExceeded = false;
mJustPeeked = false;
mPanelClosedOnDown = mExpandedHeight == 0.0f;
mHasLayoutedSinceDown = false;
mUpdateFlingOnLayout = false;
mTouchAboveFalsingThreshold = false;
+ mDozingOnDown = isDozing();
initVelocityTracker();
trackMovement(event);
break;
@@ -439,11 +449,9 @@ public abstract class PanelView extends FrameLayout {
case MotionEvent.ACTION_MOVE:
final float h = y - mInitialTouchY;
trackMovement(event);
- if (scrolledToBottom) {
+ if (scrolledToBottom || mTouchStartedInEmptyArea) {
if (h < -mTouchSlop && h < -Math.abs(x - mInitialTouchX)) {
- if (mHeightAnimator != null) {
- mHeightAnimator.cancel();
- }
+ cancelHeightAnimator();
mInitialOffsetOnTouch = mExpandedHeight;
mInitialTouchY = y;
mInitialTouchX = x;
@@ -461,6 +469,25 @@ public abstract class PanelView extends FrameLayout {
return false;
}
+ /**
+ * @return Whether a pair of coordinates are inside the visible view content bounds.
+ */
+ protected abstract boolean isInContentBounds(float x, float y);
+
+ private void cancelHeightAnimator() {
+ if (mHeightAnimator != null) {
+ mHeightAnimator.cancel();
+ }
+ endClosing();
+ }
+
+ private void endClosing() {
+ if (mClosing) {
+ mClosing = false;
+ onClosingFinished();
+ }
+ }
+
private void initVelocityTracker() {
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
@@ -627,10 +654,10 @@ public abstract class PanelView extends FrameLayout {
}
mExpandedHeight = Math.max(0, mExpandedHeight);
- onHeightUpdated(mExpandedHeight);
mExpandedFraction = Math.min(1f, fhWithoutOverExpansion == 0
? 0
: mExpandedHeight / fhWithoutOverExpansion);
+ onHeightUpdated(mExpandedHeight);
notifyBarPanelExpansionChanged();
}
@@ -700,9 +727,7 @@ public abstract class PanelView extends FrameLayout {
mPeekRunnable.run();
}
} else if (!isFullyCollapsed() && !mTracking && !mClosing) {
- if (mHeightAnimator != null) {
- mHeightAnimator.cancel();
- }
+ cancelHeightAnimator();
mClosing = true;
notifyExpandingStarted();
if (delayed) {
@@ -785,13 +810,16 @@ public abstract class PanelView extends FrameLayout {
private void abortAnimations() {
cancelPeek();
- if (mHeightAnimator != null) {
- mHeightAnimator.cancel();
- }
+ cancelHeightAnimator();
removeCallbacks(mPostCollapseRunnable);
removeCallbacks(mFlingCollapseRunnable);
}
+ protected void onClosingFinished() {
+ mBar.onClosingFinished();
+ }
+
+
protected void startUnlockHintAnimation() {
// We don't need to hint the user if an animation is already running or the user is changing
@@ -841,16 +869,15 @@ public abstract class PanelView extends FrameLayout {
});
animator.start();
mHeightAnimator = animator;
- mOriginalIndicationY = mKeyguardBottomArea.getIndicationView().getY();
mKeyguardBottomArea.getIndicationView().animate()
- .y(mOriginalIndicationY - mHintDistance)
+ .translationY(-mHintDistance)
.setDuration(250)
.setInterpolator(mFastOutSlowInInterpolator)
.withEndAction(new Runnable() {
@Override
public void run() {
mKeyguardBottomArea.getIndicationView().animate()
- .y(mOriginalIndicationY)
+ .translationY(0)
.setDuration(450)
.setInterpolator(mBounceInterpolator)
.start();
@@ -898,7 +925,7 @@ public abstract class PanelView extends FrameLayout {
*
* @return whether the panel will be expanded after the action performed by this method
*/
- private boolean onEmptySpaceClick(float x) {
+ protected boolean onEmptySpaceClick(float x) {
if (mHintAnimationRunning) {
return true;
}
@@ -924,7 +951,10 @@ public abstract class PanelView extends FrameLayout {
private boolean onMiddleClicked() {
switch (mStatusBar.getBarState()) {
case StatusBarState.KEYGUARD:
- if (!isDozing()) {
+ if (!mDozingOnDown) {
+ EventLogTags.writeSysuiLockscreenGesture(
+ EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_TAP_UNLOCK_HINT,
+ 0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
startUnlockHintAnimation();
}
return true;
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 9e3f0f6..f227107 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -23,6 +23,7 @@ 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;
@@ -40,6 +41,7 @@ import android.app.Notification;
import android.app.PendingIntent;
import android.app.StatusBarManager;
import android.content.BroadcastReceiver;
+import android.content.ComponentCallbacks2;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -51,10 +53,10 @@ import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.PixelFormat;
import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
-import android.graphics.Xfermode;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.inputmethodservice.InputMethodService;
@@ -67,16 +69,20 @@ import android.media.session.PlaybackState;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Message;
import android.os.PowerManager;
+import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.os.UserManager;
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;
@@ -95,8 +101,8 @@ 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.accessibility.AccessibilityManager;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Animation;
@@ -115,6 +121,7 @@ import com.android.keyguard.KeyguardHostView.OnDismissAction;
import com.android.keyguard.ViewMediatorCallback;
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.R;
@@ -122,6 +129,7 @@ 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.statusbar.ActivatableNotificationView;
import com.android.systemui.statusbar.BackDropView;
import com.android.systemui.statusbar.BaseStatusBar;
@@ -140,6 +148,7 @@ 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;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
@@ -173,9 +182,10 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
- DragDownHelper.DragDownCallback, ActivityStarter {
+ DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener {
static final String TAG = "PhoneStatusBar";
public static final boolean DEBUG = BaseStatusBar.DEBUG;
public static final boolean SPEW = false;
@@ -197,8 +207,12 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
private static final int MSG_OPEN_NOTIFICATION_PANEL = 1000;
private static final int MSG_CLOSE_PANELS = 1001;
private static final int MSG_OPEN_SETTINGS_PANEL = 1002;
+ private static final int MSG_LAUNCH_TRANSITION_TIMEOUT = 1003;
// 1020-1040 reserved for BaseStatusBar
+ // Time after we abort the launch transition.
+ private static final long LAUNCH_TRANSITION_TIMEOUT_MS = 5000;
+
private static final boolean CLOSE_PANEL_WHEN_EMPTIED = true;
private static final int NOTIFICATION_PRIORITY_MULTIPLIER = 10; // see NotificationManagerService
@@ -262,6 +276,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
private UnlockMethodCache mUnlockMethodCache;
private DozeServiceHost mDozeServiceHost;
private boolean mScreenOnComingFromTouch;
+ private PointF mScreenOnTouchLocation;
int mPixelFormat;
Object mQueueLock = new Object();
@@ -354,7 +369,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
? new GestureRecorder("/sdcard/statusbar_gestures.dat")
: null;
+ private ScreenPinningRequest mScreenPinningRequest;
+
private int mNavigationIconHints = 0;
+ private HandlerThread mHandlerThread;
// ensure quick settings is disabled until the current user makes it through the setup wizard
private boolean mUserSetup = false;
@@ -406,16 +424,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
private boolean mAutohideSuspended;
private int mStatusBarMode;
private int mNavigationBarMode;
- private Boolean mScreenOn;
-
- // The second field is a bit different from the first one because it only listens to screen on/
- // screen of events from Keyguard. We need this so we don't have a race condition with the
- // broadcast. In the future, we should remove the first field altogether and rename the second
- // field.
- private boolean mScreenOnFromKeyguard;
private ViewMediatorCallback mKeyguardViewMediatorCallback;
private ScrimController mScrimController;
+ private DozeScrimController mDozeScrimController;
private final Runnable mAutohide = new Runnable() {
@Override
@@ -426,7 +438,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
}};
- private boolean mVisible;
private boolean mWaitingForKeyguardExit;
private boolean mDozing;
private boolean mScrimSrcModeEnabled;
@@ -484,6 +495,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
private boolean mLaunchTransitionFadingAway;
private ExpandableNotificationRow mDraggedDownRow;
+ // 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
@@ -581,7 +595,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
addNavigationBar();
// Lastly, call to the icon policy to install/update all the icons.
- mIconPolicy = new PhoneStatusBarPolicy(mContext, mCastController);
+ mIconPolicy = new PhoneStatusBarPolicy(mContext, mCastController, mHotspotController);
mSettingsObserver.onChange(false); // set up
mHeadsUpObserver.onChange(true); // set up
@@ -594,6 +608,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mHeadsUpObserver);
}
mUnlockMethodCache = UnlockMethodCache.getInstance(mContext);
+ mUnlockMethodCache.addListener(this);
startKeyguard();
mDozeServiceHost = new DozeServiceHost();
@@ -603,6 +618,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
setControllerUsers();
notifyUserAboutHiddenNotifications();
+
+ mScreenPinningRequest = new ScreenPinningRequest(mContext);
}
// ================================================================================
@@ -742,6 +759,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mScrimController = new ScrimController(scrimBehind, scrimInFront, mScrimSrcModeEnabled);
mScrimController.setBackDropView(mBackdrop);
mStatusBarView.setScrimController(mScrimController);
+ mDozeScrimController = new DozeScrimController(mScrimController, context);
mHeader = (StatusBarHeaderView) mStatusBarWindow.findViewById(R.id.header);
mHeader.setActivityStarter(this);
@@ -773,6 +791,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
// set the inital view visibility
setAreThereNotifications();
+ // 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
mBatteryController = new BatteryController(mContext);
@@ -791,14 +813,16 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
});
mNetworkController = new NetworkControllerImpl(mContext);
mHotspotController = new HotspotControllerImpl(mContext);
- mBluetoothController = new BluetoothControllerImpl(mContext);
+ mBluetoothController = new BluetoothControllerImpl(mContext, mHandlerThread.getLooper());
mSecurityController = new SecurityControllerImpl(mContext);
if (mContext.getResources().getBoolean(R.bool.config_showRotationLock)) {
mRotationLockController = new RotationLockControllerImpl(mContext);
}
mUserInfoController = new UserInfoController(mContext);
mVolumeComponent = getComponent(VolumeComponent.class);
- mZenModeController = mVolumeComponent.getZenController();
+ if (mVolumeComponent != null) {
+ mZenModeController = mVolumeComponent.getZenController();
+ }
mCastController = new CastControllerImpl(mContext);
final SignalClusterView signalCluster =
(SignalClusterView) mStatusBarView.findViewById(R.id.signal_cluster);
@@ -817,7 +841,12 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
signalClusterQs.setNetworkController(mNetworkController);
final boolean isAPhone = mNetworkController.hasVoiceCallingFeature();
if (isAPhone) {
- mNetworkController.addEmergencyLabelView(mHeader);
+ mNetworkController.addEmergencyListener(new NetworkControllerImpl.EmergencyListener() {
+ @Override
+ public void setEmergencyCallsOnly(boolean emergencyOnly) {
+ mHeader.setShowEmergencyCallsOnly(emergencyOnly);
+ }
+ });
}
mCarrierLabel = (TextView)mStatusBarWindow.findViewById(R.id.carrier_label);
@@ -826,21 +855,19 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
if (mShowCarrierInPanel) {
mCarrierLabel.setVisibility(mCarrierLabelVisible ? View.VISIBLE : View.INVISIBLE);
- // for mobile devices, we always show mobile connection info here (SPN/PLMN)
- // for other devices, we show whatever network is connected
- if (mNetworkController.hasMobileDataFeature()) {
- mNetworkController.addMobileLabelView(mCarrierLabel);
- } else {
- mNetworkController.addCombinedLabelView(mCarrierLabel);
- }
-
- // set up the dynamic hide/show of the label
- // TODO: uncomment, handle this for the Stack scroller aswell
-// ((NotificationRowLayout) mStackScroller)
-// .setOnSizeChangedListener(new OnSizeChangedListener() {
-// @Override
-// public void onSizeChanged(View view, int w, int h, int oldw, int oldh) {
-// updateCarrierLabelVisibility(false);
+ 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);
+ }
+ }
+ }
+ });
}
mFlashlightController = new FlashlightController(mContext);
@@ -850,8 +877,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mKeyguardBottomArea.setAccessibilityController(mAccessibilityController);
mNextAlarmController = new NextAlarmController(mContext);
mKeyguardMonitor = new KeyguardMonitor();
- mUserSwitcherController = new UserSwitcherController(mContext, mKeyguardMonitor);
-
+ if (UserSwitcherController.isUserSwitcherAvailable(UserManager.get(mContext))) {
+ mUserSwitcherController = new UserSwitcherController(mContext, mKeyguardMonitor);
+ }
mKeyguardUserSwitcher = new KeyguardUserSwitcher(mContext,
(ViewStub) mStatusBarWindow.findViewById(R.id.keyguard_user_switcher),
mKeyguardStatusBar, mNotificationPanel, mUserSwitcherController);
@@ -903,7 +931,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
filter.addAction("fake_artwork");
}
filter.addAction(ACTION_DEMO);
- context.registerReceiver(mBroadcastReceiver, filter);
+ context.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null, null);
// listen for USER_SETUP_COMPLETE setting (per-user)
resetUserSetupObserver();
@@ -965,12 +993,12 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
// accelerating the swipes
int rowDelayDecrement = 10;
int currentDelay = 140;
- int totalDelay = 0;
+ int totalDelay = 180;
int numItems = hideAnimatedList.size();
- for (int i = 0; i < numItems; i++) {
+ for (int i = numItems - 1; i >= 0; i--) {
View view = hideAnimatedList.get(i);
Runnable endRunnable = null;
- if (i == numItems - 1) {
+ if (i == 0) {
endRunnable = animationFinishAction;
}
mStackScroller.dismissViewAnimated(view, endRunnable, totalDelay, 260);
@@ -1308,6 +1336,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
// not immersive & a full-screen alert should be shown
if (DEBUG) Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent");
try {
+ EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION,
+ notification.getKey());
notification.getNotification().fullScreenIntent.send();
} catch (PendingIntent.CanceledException e) {
}
@@ -1349,16 +1379,19 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
@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);
}
@@ -1502,7 +1535,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
// 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.
mNotificationPanel.setQsExpansionEnabled(isDeviceProvisioned()
- && (!mUserSwitcherController.isSimpleUserSwitcher() || mUserSetup));
+ && (mUserSetup || mUserSwitcherController == null
+ || !mUserSwitcherController.isSimpleUserSwitcher()));
mShadeUpdates.check();
}
@@ -2131,8 +2165,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
public boolean isFalsingThresholdNeeded() {
boolean onKeyguard = getBarState() == StatusBarState.KEYGUARD;
- boolean isMethodInsecure = mUnlockMethodCache.isMethodInsecure();
- return onKeyguard && (isMethodInsecure || mDozing || mScreenOnComingFromTouch);
+ boolean isCurrentlyInsecure = mUnlockMethodCache.isCurrentlyInsecure();
+ return onKeyguard && (isCurrentlyInsecure || mDozing || mScreenOnComingFromTouch);
}
public boolean isDozing() {
@@ -2149,6 +2183,18 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
/**
+ * To be called when there's a state change in StatusBarKeyguardViewManager.
+ */
+ public void onKeyguardViewManagerStatesUpdated() {
+ logStateToEventlog();
+ }
+
+ @Override // UnlockMethodCache.OnUnlockMethodChangedListener
+ public void onUnlockMethodStateChanged() {
+ logStateToEventlog();
+ }
+
+ /**
* All changes to the status bar and notifications funnel through here and are batched.
*/
private class H extends BaseStatusBar.H {
@@ -2179,6 +2225,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
escalateHeadsUp();
setHeadsUpVisibility(false);
break;
+ case MSG_LAUNCH_TRANSITION_TIMEOUT:
+ onLaunchTransitionTimeout();
+ break;
}
}
}
@@ -2193,6 +2242,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
if (DEBUG)
Log.d(TAG, "converting a heads up to fullScreen");
try {
+ EventLog.writeEvent(EventLogTags.SYSUI_HEADS_UP_ESCALATION,
+ sbn.getKey());
notification.fullScreenIntent.send();
} catch (PendingIntent.CanceledException e) {
}
@@ -2365,8 +2416,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
// Settings are not available in setup
if (!mUserSetup) return;
- mNotificationPanel.expand();
- mNotificationPanel.openQs();
+ mNotificationPanel.expandWithQs();
if (false) postStartTracing();
}
@@ -2413,6 +2463,12 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false);
showBouncer();
disable(mDisabledUnmodified, true /* animate */);
+
+ // Trimming will happen later if Keyguard is showing - doing it here might cause a jank in
+ // the bouncer appear animation.
+ if (!mStatusBarKeyguardViewManager.isShowing()) {
+ WindowManagerGlobal.getInstance().trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
+ }
}
public boolean interceptTouchEvent(MotionEvent event) {
@@ -2599,8 +2655,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
private int barMode(int vis, int transientFlag, int translucentFlag) {
+ int lightsOutTransparent = View.SYSTEM_UI_FLAG_LOW_PROFILE | View.SYSTEM_UI_TRANSPARENT;
return (vis & transientFlag) != 0 ? MODE_SEMI_TRANSPARENT
: (vis & translucentFlag) != 0 ? MODE_TRANSLUCENT
+ : (vis & lightsOutTransparent) == lightsOutTransparent ? MODE_LIGHTS_OUT_TRANSPARENT
: (vis & View.SYSTEM_UI_TRANSPARENT) != 0 ? MODE_TRANSPARENT
: (vis & View.SYSTEM_UI_FLAG_LOW_PROFILE) != 0 ? MODE_LIGHTS_OUT
: MODE_OPAQUE;
@@ -2641,6 +2699,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
@Override
public void setInteracting(int barWindow, boolean interacting) {
+ final boolean changing = ((mInteractingWindows & barWindow) != 0) != interacting;
mInteractingWindows = interacting
? (mInteractingWindows | barWindow)
: (mInteractingWindows & ~barWindow);
@@ -2649,6 +2708,12 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
} else {
resumeSuspendedAutohide();
}
+ // manually dismiss the volume panel when interacting with the nav bar
+ if (changing && interacting && barWindow == StatusBarManager.WINDOW_NAVIGATION_BAR) {
+ if (mVolumeComponent != null) {
+ mVolumeComponent.dismissNow();
+ }
+ }
checkBarModes();
}
@@ -2955,6 +3020,11 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
if (mSecurityController != null) {
mSecurityController.dump(fd, pw, args);
}
+ pw.println("SharedPreferences:");
+ for (Map.Entry<String, ?> entry : mContext.getSharedPreferences(mContext.getPackageName(),
+ Context.MODE_PRIVATE).getAll().entrySet()) {
+ pw.print(" "); pw.print(entry.getKey()); pw.print("="); pw.println(entry.getValue());
+ }
}
private String hunStateToString(Entry entry) {
@@ -3008,6 +3078,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
}
+ float getDisplayDensity() {
+ return mDisplayMetrics.density;
+ }
+
public void startActivityDismissingKeyguard(final Intent intent, boolean onlyProvisioned,
final boolean dismissShade) {
if (onlyProvisioned && !isDeviceProvisioned()) return;
@@ -3036,7 +3110,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
});
if (dismissShade) {
- animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
+ animateCollapsePanels(
+ CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
}
return true;
}
@@ -3048,27 +3123,25 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
if (DEBUG) Log.v(TAG, "onReceive: " + intent);
String action = intent.getAction();
if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {
- int flags = CommandQueue.FLAG_EXCLUDE_NONE;
- String reason = intent.getStringExtra("reason");
- if (reason != null && reason.equals(SYSTEM_DIALOG_REASON_RECENT_APPS)) {
- flags |= CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL;
+ if (isCurrentProfile(getSendingUserId())) {
+ int flags = CommandQueue.FLAG_EXCLUDE_NONE;
+ String reason = intent.getStringExtra("reason");
+ if (reason != null && reason.equals(SYSTEM_DIALOG_REASON_RECENT_APPS)) {
+ flags |= CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL;
+ }
+ animateCollapsePanels(flags);
}
- animateCollapsePanels(flags);
}
else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
mScreenOn = false;
notifyNavigationBarScreenOn(false);
notifyHeadsUpScreenOn(false);
finishBarAnimations();
- stopNotificationLogging();
resetUserExpandedStates();
}
else if (Intent.ACTION_SCREEN_ON.equals(action)) {
mScreenOn = true;
- // work around problem where mDisplay.getRotation() is not stable while screen is off (bug 7086018)
- repositionNavigationBar();
notifyNavigationBarScreenOn(true);
- startNotificationLoggingIfScreenOnAndVisible();
}
else if (ACTION_DEMO.equals(action)) {
Bundle bundle = intent.getExtras();
@@ -3105,18 +3178,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
protected void dismissKeyguardThenExecute(final OnDismissAction action,
boolean afterKeyguardGone) {
if (mStatusBarKeyguardViewManager.isShowing()) {
- if (UnlockMethodCache.getInstance(mContext).isMethodInsecure()
- && mNotificationPanel.isLaunchTransitionRunning() && !afterKeyguardGone) {
- action.onDismiss();
- mNotificationPanel.setLaunchTransitionEndRunnable(new Runnable() {
- @Override
- public void run() {
- mStatusBarKeyguardViewManager.dismiss();
- }
- });
- } else {
- mStatusBarKeyguardViewManager.dismissWithAction(action, afterKeyguardGone);
- }
+ mStatusBarKeyguardViewManager.dismissWithAction(action, afterKeyguardGone);
} else {
action.onDismiss();
}
@@ -3138,12 +3200,15 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
updateShowSearchHoldoff();
updateRowStates();
+ mScreenPinningRequest.onConfigurationChanged();
}
@Override
public void userSwitched(int newUserId) {
+ super.userSwitched(newUserId);
if (MULTIUSER_DEBUG) mNotificationPanelDebugText.setText("USER " + newUserId);
animateCollapsePanels();
+ updatePublicMode();
updateNotifications();
resetUserSetupObserver();
setControllerUsers();
@@ -3257,14 +3322,14 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
// Visibility reporting
@Override
- protected void visibilityChanged(boolean visible) {
- mVisible = visible;
- if (visible) {
- startNotificationLoggingIfScreenOnAndVisible();
+ protected void handleVisibleToUserChanged(boolean visibleToUser) {
+ if (visibleToUser) {
+ super.handleVisibleToUserChanged(visibleToUser);
+ startNotificationLogging();
} else {
stopNotificationLogging();
+ super.handleVisibleToUserChanged(visibleToUser);
}
- super.visibilityChanged(visible);
}
private void stopNotificationLogging() {
@@ -3279,17 +3344,15 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mStackScroller.setChildLocationsChangedListener(null);
}
- private void startNotificationLoggingIfScreenOnAndVisible() {
- if (mVisible && mScreenOn) {
- mStackScroller.setChildLocationsChangedListener(mNotificationLocationsChangedListener);
- // Some transitions like mScreenOn=false -> mScreenOn=true don't
- // cause the scroller to emit child location events. Hence generate
- // one ourselves to guarantee that we're reporting visible
- // notifications.
- // (Note that in cases where the scroller does emit events, this
- // additional event doesn't break anything.)
- mNotificationLocationsChangedListener.onChildLocationsChanged(mStackScroller);
- }
+ private void startNotificationLogging() {
+ mStackScroller.setChildLocationsChangedListener(mNotificationLocationsChangedListener);
+ // Some transitions like mVisibleToUser=false -> mVisibleToUser=true don't
+ // cause the scroller to emit child location events. Hence generate
+ // one ourselves to guarantee that we're reporting visible
+ // notifications.
+ // (Note that in cases where the scroller does emit events, this
+ // additional event doesn't break anything.)
+ mNotificationLocationsChangedListener.onChildLocationsChanged(mStackScroller);
}
private void logNotificationVisibilityChanges(
@@ -3306,6 +3369,47 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
}
+ // State logging
+
+ private void logStateToEventlog() {
+ boolean isShowing = mStatusBarKeyguardViewManager.isShowing();
+ boolean isOccluded = mStatusBarKeyguardViewManager.isOccluded();
+ boolean isBouncerShowing = mStatusBarKeyguardViewManager.isBouncerShowing();
+ boolean isSecure = mUnlockMethodCache.isMethodSecure();
+ boolean isCurrentlyInsecure = mUnlockMethodCache.isCurrentlyInsecure();
+ int stateFingerprint = getLoggingFingerprint(mState,
+ isShowing,
+ isOccluded,
+ isBouncerShowing,
+ isSecure,
+ isCurrentlyInsecure);
+ if (stateFingerprint != mLastLoggedStateFingerprint) {
+ EventLogTags.writeSysuiStatusBarState(mState,
+ isShowing ? 1 : 0,
+ isOccluded ? 1 : 0,
+ isBouncerShowing ? 1 : 0,
+ isSecure ? 1 : 0,
+ isCurrentlyInsecure ? 1 : 0);
+ mLastLoggedStateFingerprint = stateFingerprint;
+ }
+ }
+
+ /**
+ * Returns a fingerprint of fields logged to eventlog
+ */
+ private static int getLoggingFingerprint(int statusBarState, boolean keyguardShowing,
+ boolean keyguardOccluded, boolean bouncerShowing, boolean secure,
+ boolean currentlyInsecure) {
+ // Reserve 8 bits for statusBarState. We'll never go higher than
+ // that, right? Riiiight.
+ return (statusBarState & 0xFF)
+ | ((keyguardShowing ? 1 : 0) << 8)
+ | ((keyguardOccluded ? 1 : 0) << 9)
+ | ((bouncerShowing ? 1 : 0) << 10)
+ | ((secure ? 1 : 0) << 11)
+ | ((currentlyInsecure ? 1 : 0) << 12);
+ }
+
//
// tracing
//
@@ -3410,6 +3514,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mWindowManager.removeViewImmediate(mNavigationBarView);
mNavigationBarView = null;
}
+ if (mHandlerThread != null) {
+ mHandlerThread.quitSafely();
+ mHandlerThread = null;
+ }
mContext.unregisterReceiver(mBroadcastReceiver);
}
@@ -3434,6 +3542,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
dispatchDemoCommand(COMMAND_ENTER, new Bundle());
}
boolean modeChange = command.equals(COMMAND_ENTER) || command.equals(COMMAND_EXIT);
+ if ((modeChange || command.equals(COMMAND_VOLUME)) && mVolumeComponent != null) {
+ mVolumeComponent.dispatchDemoCommand(command, args);
+ }
if (modeChange || command.equals(COMMAND_CLOCK)) {
dispatchDemoCommandToView(command, args, R.id.clock);
}
@@ -3497,12 +3608,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
if (mLaunchTransitionFadingAway) {
mNotificationPanel.animate().cancel();
mNotificationPanel.setAlpha(1f);
- if (mLaunchTransitionEndRunnable != null) {
- mLaunchTransitionEndRunnable.run();
- }
- mLaunchTransitionEndRunnable = null;
+ runLaunchTransitionEndRunnable();
mLaunchTransitionFadingAway = false;
}
+ mHandler.removeMessages(MSG_LAUNCH_TRANSITION_TIMEOUT);
setBarState(StatusBarState.KEYGUARD);
updateKeyguardState(false /* goingToFullShade */, false /* fromShadeLocked */);
if (!mScreenOnFromKeyguard) {
@@ -3543,6 +3652,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
*/
public void fadeKeyguardAfterLaunchTransition(final Runnable beforeFading,
Runnable endRunnable) {
+ mHandler.removeMessages(MSG_LAUNCH_TRANSITION_TIMEOUT);
mLaunchTransitionEndRunnable = endRunnable;
Runnable hideRunnable = new Runnable() {
@Override
@@ -3561,10 +3671,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
@Override
public void run() {
mNotificationPanel.setAlpha(1);
- if (mLaunchTransitionEndRunnable != null) {
- mLaunchTransitionEndRunnable.run();
- }
- mLaunchTransitionEndRunnable = null;
+ runLaunchTransitionEndRunnable();
mLaunchTransitionFadingAway = false;
}
});
@@ -3578,6 +3685,32 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
/**
+ * Starts the timeout when we try to start the affordances on Keyguard. We usually rely that
+ * Keyguard goes away via fadeKeyguardAfterLaunchTransition, however, that might not happen
+ * because the launched app crashed or something else went wrong.
+ */
+ public void startLaunchTransitionTimeout() {
+ mHandler.sendEmptyMessageDelayed(MSG_LAUNCH_TRANSITION_TIMEOUT,
+ LAUNCH_TRANSITION_TIMEOUT_MS);
+ }
+
+ private void onLaunchTransitionTimeout() {
+ Log.w(TAG, "Launch transition: Timeout!");
+ mNotificationPanel.resetViews();
+ }
+
+ private void runLaunchTransitionEndRunnable() {
+ if (mLaunchTransitionEndRunnable != null) {
+ Runnable r = mLaunchTransitionEndRunnable;
+
+ // mLaunchTransitionEndRunnable might call showKeyguard, which would execute it again,
+ // which would lead to infinite recursion. Protect against it.
+ mLaunchTransitionEndRunnable = null;
+ r.run();
+ }
+ }
+
+ /**
* @return true if we would like to stay in the shade, false if it should go away entirely
*/
public boolean hideKeyguard() {
@@ -3594,6 +3727,13 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
instantCollapseNotificationPanel();
}
updateKeyguardState(staying, false /* fromShadeLocked */);
+
+ // Keyguard state has changed, but QS is not listening anymore. Make sure to update the tile
+ // visibilities so next time we open the panel we know the correct height already.
+ if (mQSPanel != null) {
+ mQSPanel.refreshAllTiles();
+ }
+ mHandler.removeMessages(MSG_LAUNCH_TRANSITION_TIMEOUT);
return staying;
}
@@ -3627,10 +3767,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
private void updatePublicMode() {
- setLockscreenPublicMode(
- (mStatusBarKeyguardViewManager.isShowing() ||
- mStatusBarKeyguardViewManager.isOccluded())
- && mStatusBarKeyguardViewManager.isSecure());
+ setLockscreenPublicMode(mStatusBarKeyguardViewManager.isShowing()
+ && mStatusBarKeyguardViewManager.isSecure(mCurrentUserId));
}
private void updateKeyguardState(boolean goingToFullShade, boolean fromShadeLocked) {
@@ -3664,15 +3802,11 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
if (mState != StatusBarState.KEYGUARD && !mNotificationPanel.isDozing()) {
return;
}
- mNotificationPanel.setDozing(mDozing);
- if (mDozing) {
- mKeyguardBottomArea.setVisibility(View.INVISIBLE);
- mStackScroller.setDark(true, false /*animate*/);
- } else {
- mKeyguardBottomArea.setVisibility(View.VISIBLE);
- mStackScroller.setDark(false, false /*animate*/);
- }
+ boolean animate = !mDozing && mDozeScrimController.isPulsing();
+ mNotificationPanel.setDozing(mDozing, animate);
+ mStackScroller.setDark(mDozing, animate, mScreenOnTouchLocation);
mScrimController.setDozing(mDozing);
+ mDozeScrimController.setDozing(mDozing, animate);
}
public void updateStackScrollerState(boolean goingToFullShade) {
@@ -3725,7 +3859,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
public boolean onSpacePressed() {
if (mScreenOn != null && mScreenOn
&& (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED)) {
- animateCollapsePanels(0 /* flags */, true /* force */);
+ animateCollapsePanels(
+ CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL /* flags */, true /* force */);
return true;
}
return false;
@@ -3751,6 +3886,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
@Override
public void onActivated(ActivatableNotificationView view) {
+ EventLogTags.writeSysuiLockscreenGesture(
+ EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_TAP_NOTIFICATION_ACTIVATE,
+ 0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
mKeyguardIndicationController.showTransientIndication(R.string.notification_tap_again);
ActivatableNotificationView previousView = mStackScroller.getActivatedChild();
if (previousView != null) {
@@ -3763,6 +3901,17 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
* @param state The {@link StatusBarState} to set.
*/
public void setBarState(int state) {
+ // If we're visible and switched to SHADE_LOCKED (the user dragged
+ // down on the lockscreen), clear notification LED, vibration,
+ // ringing.
+ // Other transitions are covered in handleVisibleToUserChanged().
+ if (state != mState && mVisible && state == StatusBarState.SHADE_LOCKED) {
+ try {
+ mBarService.clearNotificationEffects();
+ } catch (RemoteException e) {
+ // Ignore.
+ }
+ }
mState = state;
mStatusBarWindowManager.setStatusBarState(state);
}
@@ -3779,6 +3928,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
runPostCollapseRunnables();
}
+ public void onClosingFinished() {
+ runPostCollapseRunnables();
+ }
+
public void onUnlockHintStarted() {
mKeyguardIndicationController.showTransientIndication(R.string.keyguard_unlock);
}
@@ -3798,7 +3951,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
public void onTrackingStopped(boolean expand) {
if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) {
- if (!expand && !mUnlockMethodCache.isMethodInsecure()) {
+ if (!expand && !mUnlockMethodCache.isCurrentlyInsecure()) {
showBouncer();
}
}
@@ -3816,8 +3969,12 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
// ---------------------- DragDownHelper.OnDragDownListener ------------------------------------
@Override
- public boolean onDraggedDown(View startingChild) {
+ public boolean onDraggedDown(View startingChild, int dragLengthY) {
if (hasActiveNotifications()) {
+ EventLogTags.writeSysuiLockscreenGesture(
+ EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_SWIPE_DOWN_FULL_SHADE,
+ (int) (dragLengthY / mDisplayMetrics.density),
+ 0 /* velocityDp - N/A */);
// We have notifications, go to locked shade.
goToLockedShade(startingChild);
@@ -3921,7 +4078,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
public void onScreenTurnedOff() {
mScreenOnFromKeyguard = false;
mScreenOnComingFromTouch = false;
+ mScreenOnTouchLocation = null;
mStackScroller.setAnimationsEnabled(false);
+ updateVisibleToUser();
}
public void onScreenTurnedOn() {
@@ -3929,6 +4088,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mStackScroller.setAnimationsEnabled(true);
mNotificationPanel.onScreenTurnedOn();
mNotificationPanel.setTouchDisabled(false);
+ updateVisibleToUser();
}
/**
@@ -3955,6 +4115,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
// long-pressed 'together'
if ((time - mLastLockToAppLongPress) < LOCK_TO_APP_GESTURE_TOLERENCE) {
activityManager.stopLockTaskModeOnCurrent();
+ // When exiting refresh disabled flags.
+ mNavigationBarView.setDisabledFlags(mDisabled, true);
} else if ((v.getId() == R.id.back)
&& !mNavigationBarView.getRecentsButton().isPressed()) {
// If we aren't pressing recents right now then they presses
@@ -3970,6 +4132,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
// When in accessibility mode a long press that is recents (not back)
// should stop lock task.
activityManager.stopLockTaskModeOnCurrent();
+ // When exiting refresh disabled flags.
+ mNavigationBarView.setDisabledFlags(mDisabled, true);
}
}
if (sendBackLongPress) {
@@ -4019,17 +4183,31 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
notifyUiVisibilityChanged(mSystemUiVisibility);
}
+ @Override
+ public void showScreenPinningRequest() {
+ if (mKeyguardMonitor.isShowing()) {
+ // Don't allow apps to trigger this from keyguard.
+ return;
+ }
+ // Show screen pinning request, since this comes from an app, show 'no thanks', button.
+ showScreenPinningRequest(true);
+ }
+
+ public void showScreenPinningRequest(boolean allowCancel) {
+ mScreenPinningRequest.showPrompt(allowCancel);
+ }
+
public boolean hasActiveNotifications() {
return !mNotificationData.getActiveNotifications().isEmpty();
}
- public void wakeUpIfDozing(long time, boolean fromTouch) {
- if (mDozing && mScrimController.isPulsing()) {
+ public void wakeUpIfDozing(long time, MotionEvent event) {
+ if (mDozing && mDozeScrimController.isPulsing()) {
PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
pm.wakeUp(time);
- if (fromTouch) {
- mScreenOnComingFromTouch = true;
- }
+ mScreenOnComingFromTouch = true;
+ mScreenOnTouchLocation = new PointF(event.getX(), event.getY());
+ mNotificationPanel.setTouchDisabled(false);
}
}
@@ -4069,6 +4247,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
private final H mHandler = new H();
+ // Keeps the last reported state by fireNotificationLight.
+ private boolean mNotificationLightOn;
+
@Override
public String toString() {
return "PSB.DozeServiceHost[mCallbacks=" + mCallbacks.size() + "]";
@@ -4087,6 +4268,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
public void fireNotificationLight(boolean on) {
+ mNotificationLightOn = on;
for (Callback callback : mCallbacks) {
callback.onNotificationLight(on);
}
@@ -4114,8 +4296,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
@Override
- public void pulseWhileDozing(@NonNull PulseCallback callback) {
- mHandler.obtainMessage(H.MSG_PULSE_WHILE_DOZING, callback).sendToTarget();
+ public void pulseWhileDozing(@NonNull PulseCallback callback, int reason) {
+ mHandler.obtainMessage(H.MSG_PULSE_WHILE_DOZING, reason, 0, callback).sendToTarget();
}
@Override
@@ -4128,6 +4310,11 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
return mBatteryController != null && mBatteryController.isPowerSave();
}
+ @Override
+ public boolean isNotificationLightOn() {
+ return mNotificationLightOn;
+ }
+
private void handleStartDozing(@NonNull Runnable ready) {
if (!mDozing) {
mDozing = true;
@@ -4137,8 +4324,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
ready.run();
}
- private void handlePulseWhileDozing(@NonNull PulseCallback callback) {
- mScrimController.pulse(callback);
+ private void handlePulseWhileDozing(@NonNull PulseCallback callback, int reason) {
+ mDozeScrimController.pulse(callback, reason);
}
private void handleStopDozing() {
@@ -4161,7 +4348,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
handleStartDozing((Runnable) msg.obj);
break;
case MSG_PULSE_WHILE_DOZING:
- handlePulseWhileDozing((PulseCallback) msg.obj);
+ handlePulseWhileDozing((PulseCallback) msg.obj, msg.arg1);
break;
case MSG_STOP_DOZING:
handleStopDozing();
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 47e1ab5..5c254a26 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -35,6 +35,7 @@ import com.android.internal.telephony.TelephonyIntents;
import com.android.systemui.R;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.CastController.CastDevice;
+import com.android.systemui.statusbar.policy.HotspotController;
/**
* This class contains all of the policy about which icons are installed in the status
@@ -49,6 +50,7 @@ public class PhoneStatusBarPolicy {
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";
@@ -60,6 +62,7 @@ public class PhoneStatusBarPolicy {
private final StatusBarManager mService;
private final Handler mHandler = new Handler();
private final CastController mCast;
+ private final HotspotController mHotspot;
// Assume it's all good unless we hear otherwise. We don't always seem
// to get broadcasts that it *is* there.
@@ -87,7 +90,8 @@ public class PhoneStatusBarPolicy {
action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) {
updateBluetooth();
}
- else if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) {
+ else if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION) ||
+ action.equals(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION)) {
updateVolumeZen();
}
else if (action.equals(TelephonyIntents.ACTION_SIM_STATE_CHANGED)) {
@@ -102,9 +106,10 @@ public class PhoneStatusBarPolicy {
}
};
- public PhoneStatusBarPolicy(Context context, CastController cast) {
+ public PhoneStatusBarPolicy(Context context, CastController cast, HotspotController hotspot) {
mContext = context;
mCast = cast;
+ mHotspot = hotspot;
mService = (StatusBarManager)context.getSystemService(Context.STATUS_BAR_SERVICE);
// listen for broadcasts
@@ -112,6 +117,7 @@ public class PhoneStatusBarPolicy {
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);
@@ -152,6 +158,11 @@ public class PhoneStatusBarPolicy {
mService.setIcon(SLOT_CAST, R.drawable.stat_sys_cast, 0, null);
mService.setIconVisibility(SLOT_CAST, false);
mCast.addCallback(mCastCallback);
+
+ // hotspot
+ mService.setIcon(SLOT_HOTSPOT, R.drawable.stat_sys_hotspot, 0, null);
+ mService.setIconVisibility(SLOT_HOTSPOT, mHotspot.isHotspotEnabled());
+ mHotspot.addCallback(mHotspotCallback);
}
public void setZenMode(int zen) {
@@ -221,7 +232,7 @@ public class PhoneStatusBarPolicy {
}
if (mZen != Global.ZEN_MODE_NO_INTERRUPTIONS &&
- audioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE) {
+ audioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_VIBRATE) {
volumeVisible = true;
volumeIconId = R.drawable.stat_sys_ringer_vibrate;
volumeDescription = mContext.getString(R.string.accessibility_ringer_vibrate);
@@ -300,6 +311,13 @@ public class PhoneStatusBarPolicy {
mService.setIconVisibility(SLOT_CAST, isCasting);
}
+ private final HotspotController.Callback mHotspotCallback = new HotspotController.Callback() {
+ @Override
+ public void onHotspotChanged(boolean enabled) {
+ mService.setIconVisibility(SLOT_HOTSPOT, enabled);
+ }
+ };
+
private final CastController.Callback mCastCallback = new CastController.Callback() {
@Override
public void onCastDevicesChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java
index 8520f40..fb1addf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java
@@ -57,19 +57,19 @@ public final class PhoneStatusBarTransitions extends BarTransitions {
}
private float getNonBatteryClockAlphaFor(int mode) {
- return mode == MODE_LIGHTS_OUT ? ICON_ALPHA_WHEN_LIGHTS_OUT_NON_BATTERY_CLOCK
+ return isLightsOut(mode) ? ICON_ALPHA_WHEN_LIGHTS_OUT_NON_BATTERY_CLOCK
: !isOpaque(mode) ? ICON_ALPHA_WHEN_NOT_OPAQUE
: mIconAlphaWhenOpaque;
}
private float getBatteryClockAlpha(int mode) {
- return mode == MODE_LIGHTS_OUT ? ICON_ALPHA_WHEN_LIGHTS_OUT_BATTERY_CLOCK
+ return isLightsOut(mode) ? ICON_ALPHA_WHEN_LIGHTS_OUT_BATTERY_CLOCK
: getNonBatteryClockAlphaFor(mode);
}
private boolean isOpaque(int mode) {
return !(mode == MODE_SEMI_TRANSPARENT || mode == MODE_TRANSLUCENT
- || mode == MODE_TRANSPARENT);
+ || mode == MODE_TRANSPARENT || mode == MODE_LIGHTS_OUT_TRANSPARENT);
}
@Override
@@ -94,7 +94,7 @@ public final class PhoneStatusBarTransitions extends BarTransitions {
animateTransitionTo(mBattery, newAlphaBC),
animateTransitionTo(mClock, newAlphaBC)
);
- if (mode == MODE_LIGHTS_OUT) {
+ if (isLightsOut(mode)) {
anims.setDuration(LIGHTS_OUT_DURATION);
}
anims.start();
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 6411fb8..7cbf13f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -20,7 +20,6 @@ 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;
@@ -31,7 +30,7 @@ import com.android.systemui.R;
public class PhoneStatusBarView extends PanelBar {
private static final String TAG = "PhoneStatusBarView";
private static final boolean DEBUG = PhoneStatusBar.DEBUG;
- private static final boolean DEBUG_GESTURES = true;
+ private static final boolean DEBUG_GESTURES = false;
PhoneStatusBar mBar;
@@ -152,6 +151,12 @@ public class PhoneStatusBarView extends PanelBar {
}
@Override
+ public void onClosingFinished() {
+ super.onClosingFinished();
+ mBar.onClosingFinished();
+ }
+
+ @Override
public void onTrackingStopped(PanelView panel, boolean expand) {
super.onTrackingStopped(panel, expand);
mBar.onTrackingStopped(expand);
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 2dc08d4..45a1386 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
@@ -21,6 +21,7 @@ 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;
@@ -108,7 +109,8 @@ public class QSTileHost implements QSTile.Host {
mKeyguard = keyguard;
mSecurity = security;
- final HandlerThread ht = new HandlerThread(QSTileHost.class.getSimpleName());
+ final HandlerThread ht = new HandlerThread(QSTileHost.class.getSimpleName(),
+ Process.THREAD_PRIORITY_BACKGROUND);
ht.start();
mLooper = ht.getLooper();
@@ -120,6 +122,7 @@ public class QSTileHost implements QSTile.Host {
tile.userSwitch(newUserId);
}
mSecurity.onUserSwitched(newUserId);
+ mNetwork.onUserSwitched(newUserId);
mObserver.register();
}
};
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 54adbf4..0e8a794 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -19,10 +19,8 @@ package com.android.systemui.statusbar.phone;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
-import android.annotation.NonNull;
import android.content.Context;
import android.graphics.Color;
-import android.util.Log;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.animation.AnimationUtils;
@@ -30,8 +28,6 @@ import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import com.android.systemui.R;
-import com.android.systemui.doze.DozeHost;
-import com.android.systemui.doze.DozeLog;
import com.android.systemui.statusbar.BackDropView;
import com.android.systemui.statusbar.ScrimView;
@@ -40,9 +36,6 @@ import com.android.systemui.statusbar.ScrimView;
* security method gets shown).
*/
public class ScrimController implements ViewTreeObserver.OnPreDrawListener {
- private static final String TAG = "ScrimController";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
public static final long ANIMATION_DURATION = 220;
private static final float SCRIM_BEHIND_ALPHA = 0.62f;
@@ -54,7 +47,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener {
private final ScrimView mScrimBehind;
private final ScrimView mScrimInFront;
private final UnlockMethodCache mUnlockMethodCache;
- private final DozeParameters mDozeParameters;
private boolean mKeyguardShowing;
private float mFraction;
@@ -69,12 +61,15 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener {
private long mAnimationDelay;
private Runnable mOnAnimationFinished;
private boolean mAnimationStarted;
- private boolean mDozing;
- private DozeHost.PulseCallback mPulseCallback;
private final Interpolator mInterpolator = new DecelerateInterpolator();
private final Interpolator mLinearOutSlowInInterpolator;
private BackDropView mBackDropView;
private boolean mScrimSrcEnabled;
+ private boolean mDozing;
+ private float mDozeInFrontAlpha;
+ private float mDozeBehindAlpha;
+ private float mCurrentInFrontAlpha;
+ private float mCurrentBehindAlpha;
public ScrimController(ScrimView scrimBehind, ScrimView scrimInFront, boolean scrimSrcEnabled) {
mScrimBehind = scrimBehind;
@@ -83,7 +78,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener {
mUnlockMethodCache = UnlockMethodCache.getInstance(context);
mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
android.R.interpolator.linear_out_slow_in);
- mDozeParameters = new DozeParameters(context);
mScrimSrcEnabled = scrimSrcEnabled;
}
@@ -94,7 +88,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener {
public void onTrackingStarted() {
mExpanding = true;
- mDarkenWhileDragging = !mUnlockMethodCache.isMethodInsecure();
+ mDarkenWhileDragging = !mUnlockMethodCache.isCurrentlyInsecure();
}
public void onExpandingFinished() {
@@ -131,60 +125,26 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener {
}
public void setDozing(boolean dozing) {
- if (mDozing == dozing) return;
mDozing = dozing;
- if (!mDozing) {
- cancelPulsing();
- mAnimateChange = true;
- } else {
- mAnimateChange = false;
- }
scheduleUpdate();
}
- /** When dozing, fade screen contents in and out using the front scrim. */
- public void pulse(@NonNull DozeHost.PulseCallback callback) {
- if (callback == null) {
- throw new IllegalArgumentException("callback must not be null");
- }
-
- if (!mDozing || mPulseCallback != null) {
- // Pulse suppressed.
- callback.onPulseFinished();
- return;
- }
-
- // Begin pulse. Note that it's very important that the pulse finished callback
- // be invoked when we're done so that the caller can drop the pulse wakelock.
- mPulseCallback = callback;
- mScrimInFront.post(mPulseIn);
+ public void setDozeInFrontAlpha(float alpha) {
+ mDozeInFrontAlpha = alpha;
+ updateScrimColor(mScrimInFront);
}
- public boolean isPulsing() {
- return mPulseCallback != null;
+ public void setDozeBehindAlpha(float alpha) {
+ mDozeBehindAlpha = alpha;
+ updateScrimColor(mScrimBehind);
}
- private void cancelPulsing() {
- if (DEBUG) Log.d(TAG, "Cancel pulsing");
-
- if (mPulseCallback != null) {
- mScrimInFront.removeCallbacks(mPulseIn);
- mScrimInFront.removeCallbacks(mPulseOut);
- pulseFinished();
- }
- }
-
- private void pulseStarted() {
- if (mPulseCallback != null) {
- mPulseCallback.onPulseStarted();
- }
+ public float getDozeBehindAlpha() {
+ return mDozeBehindAlpha;
}
- private void pulseFinished() {
- if (mPulseCallback != null) {
- mPulseCallback.onPulseFinished();
- mPulseCallback = null;
- }
+ public float getDozeInFrontAlpha() {
+ return mDozeInFrontAlpha;
}
private void scheduleUpdate() {
@@ -220,8 +180,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener {
} else if (mBouncerShowing) {
setScrimInFrontColor(SCRIM_IN_FRONT_ALPHA);
setScrimBehindColor(0f);
- } else if (mDozing) {
- setScrimInFrontColor(1);
} else {
float fraction = Math.max(0, Math.min(mFraction, 1));
setScrimInFrontColor(0f);
@@ -265,31 +223,49 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener {
((ValueAnimator) runningAnim).cancel();
scrim.setTag(TAG_KEY_ANIM, null);
}
- int color = Color.argb((int) (alpha * 255), 0, 0, 0);
if (mAnimateChange) {
- startScrimAnimation(scrim, color);
+ startScrimAnimation(scrim, alpha);
} else {
- scrim.setScrimColor(color);
+ setCurrentScrimAlpha(scrim, alpha);
+ updateScrimColor(scrim);
}
}
- private void startScrimAnimation(final ScrimView scrim, int targetColor) {
- int current = Color.alpha(scrim.getScrimColor());
- int target = Color.alpha(targetColor);
- if (current == targetColor) {
- return;
+ private float getDozeAlpha(View scrim) {
+ return scrim == mScrimBehind ? mDozeBehindAlpha : mDozeInFrontAlpha;
+ }
+
+ private float getCurrentScrimAlpha(View scrim) {
+ return scrim == mScrimBehind ? mCurrentBehindAlpha : mCurrentInFrontAlpha;
+ }
+
+ private void setCurrentScrimAlpha(View scrim, float alpha) {
+ if (scrim == mScrimBehind) {
+ mCurrentBehindAlpha = alpha;
+ } else {
+ mCurrentInFrontAlpha = alpha;
}
- ValueAnimator anim = ValueAnimator.ofInt(current, target);
+ }
+
+ private void updateScrimColor(ScrimView 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));
+ }
+
+ private void startScrimAnimation(final ScrimView scrim, float target) {
+ float current = getCurrentScrimAlpha(scrim);
+ ValueAnimator anim = ValueAnimator.ofFloat(current, target);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
- int value = (int) animation.getAnimatedValue();
- scrim.setScrimColor(Color.argb(value, 0, 0, 0));
+ float alpha = (float) animation.getAnimatedValue();
+ setCurrentScrimAlpha(scrim, alpha);
+ updateScrimColor(scrim);
}
});
- anim.setInterpolator(mAnimateKeyguardFadingOut
- ? mLinearOutSlowInInterpolator
- : mInterpolator);
+ anim.setInterpolator(getInterpolator());
anim.setStartDelay(mAnimationDelay);
anim.setDuration(mDurationOverride != -1 ? mDurationOverride : ANIMATION_DURATION);
anim.addListener(new AnimatorListenerAdapter() {
@@ -307,6 +283,10 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener {
mAnimationStarted = true;
}
+ private Interpolator getInterpolator() {
+ return mAnimateKeyguardFadingOut ? mLinearOutSlowInInterpolator : mInterpolator;
+ }
+
@Override
public boolean onPreDraw() {
mScrimBehind.getViewTreeObserver().removeOnPreDrawListener(this);
@@ -325,56 +305,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener {
return true;
}
- private final Runnable mPulseIn = new Runnable() {
- @Override
- public void run() {
- if (DEBUG) Log.d(TAG, "Pulse in, mDozing=" + mDozing);
- if (!mDozing) return;
- DozeLog.tracePulseStart();
- mDurationOverride = mDozeParameters.getPulseInDuration();
- mAnimationDelay = 0;
- mAnimateChange = true;
- mOnAnimationFinished = mPulseInFinished;
- setScrimColor(mScrimInFront, 0);
-
- // Signal that the pulse is ready to turn the screen on and draw.
- pulseStarted();
- }
- };
-
- private final Runnable mPulseInFinished = new Runnable() {
- @Override
- public void run() {
- if (DEBUG) Log.d(TAG, "Pulse in finished, mDozing=" + mDozing);
- if (!mDozing) return;
- mScrimInFront.postDelayed(mPulseOut, mDozeParameters.getPulseVisibleDuration());
- }
- };
-
- private final Runnable mPulseOut = new Runnable() {
- @Override
- public void run() {
- if (DEBUG) Log.d(TAG, "Pulse out, mDozing=" + mDozing);
- if (!mDozing) return;
- mDurationOverride = mDozeParameters.getPulseOutDuration();
- mAnimationDelay = 0;
- mAnimateChange = true;
- mOnAnimationFinished = mPulseOutFinished;
- setScrimColor(mScrimInFront, 1);
- }
- };
-
- private final Runnable mPulseOutFinished = new Runnable() {
- @Override
- public void run() {
- if (DEBUG) Log.d(TAG, "Pulse out finished");
- DozeLog.tracePulseFinish();
-
- // Signal that the pulse is all finished so we can turn the screen off now.
- pulseFinished();
- }
- };
-
public void setBackDropView(BackDropView backDropView) {
mBackDropView = backDropView;
mBackDropView.setOnVisibilityChangedRunnable(new Runnable() {
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 3f5cf3f..4a43c47 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SecureCameraLaunchManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SecureCameraLaunchManager.java
@@ -175,6 +175,7 @@ public class SecureCameraLaunchManager {
public void run() {
Intent intent = new Intent();
intent.setAction(CLOSE_CAMERA_ACTION_NAME);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
mContext.sendBroadcast(intent);
}
});
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 b0f3ea1..181926c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
@@ -48,6 +48,8 @@ import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.NextAlarmController;
import com.android.systemui.statusbar.policy.UserInfoController;
+import java.text.NumberFormat;
+
/**
* The view to manage the header area in the expanded status bar.
*/
@@ -300,9 +302,6 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
updateSystemIconsLayoutParams();
updateClickTargets();
updateMultiUserSwitch();
- if (mQSPanel != null) {
- mQSPanel.setExpanded(mExpanded);
- }
updateClockScale();
updateAvatarScale();
updateClockLp();
@@ -395,7 +394,8 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
@Override
public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
- mBatteryLevel.setText(getResources().getString(R.string.battery_level_template, level));
+ String percentage = NumberFormat.getPercentInstance().format((double) level / 100.0);
+ mBatteryLevel.setText(percentage);
}
@Override
@@ -777,9 +777,11 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
v.bringToFront();
v.setVisibility(VISIBLE);
}
+ if (v.hasOverlappingRendering()) {
+ v.animate().withLayer();
+ }
v.animate()
.alpha(in ? 1 : 0)
- .withLayer()
.withEndAction(new Runnable() {
@Override
public void run() {
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 55c861a..1724e70 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.phone;
+import android.content.ComponentCallbacks2;
import android.content.Context;
import android.os.Bundle;
import android.os.RemoteException;
@@ -24,6 +25,7 @@ import android.util.Slog;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
+import android.view.WindowManagerGlobal;
import com.android.internal.policy.IKeyguardShowCallback;
import com.android.internal.widget.LockPatternUtils;
@@ -108,7 +110,7 @@ public class StatusBarKeyguardViewManager {
// The keyguard might be showing (already). So we need to hide it.
mPhoneStatusBar.hideKeyguard();
- mBouncer.show();
+ mBouncer.show(true /* resetSecuritySelection */);
} else {
mPhoneStatusBar.showKeyguard();
mBouncer.hide(false /* destroyView */);
@@ -118,7 +120,7 @@ public class StatusBarKeyguardViewManager {
private void showBouncer() {
if (mShowing) {
- mBouncer.show();
+ mBouncer.show(false /* resetSecuritySelection */);
}
updateStates();
}
@@ -128,7 +130,7 @@ public class StatusBarKeyguardViewManager {
if (!afterKeyguardGone) {
mBouncer.showWithDismissAction(r);
} else {
- mBouncer.show();
+ mBouncer.show(false /* resetSecuritySelection */);
mAfterKeyguardGoneAction = r;
}
}
@@ -197,7 +199,7 @@ public class StatusBarKeyguardViewManager {
new Runnable() {
@Override
public void run() {
- mStatusBarWindowManager.setKeyguardOccluded(true);
+ mStatusBarWindowManager.setKeyguardOccluded(mOccluded);
reset();
}
});
@@ -268,6 +270,8 @@ public class StatusBarKeyguardViewManager {
public void run() {
mStatusBarWindowManager.setKeyguardFadingAway(false);
mPhoneStatusBar.finishKeyguardFadingAway();
+ WindowManagerGlobal.getInstance().trimMemory(
+ ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
}
});
} else {
@@ -299,6 +303,9 @@ public class StatusBarKeyguardViewManager {
}
}
+ /**
+ * WARNING: This method might cause Binder calls.
+ */
public boolean isSecure() {
return mBouncer.isSecure();
}
@@ -350,7 +357,7 @@ public class StatusBarKeyguardViewManager {
boolean showing = mShowing;
boolean occluded = mOccluded;
boolean bouncerShowing = mBouncer.isShowing();
- boolean bouncerDismissible = !mBouncer.needsFullscreenBouncer();
+ boolean bouncerDismissible = !mBouncer.isFullscreenBouncer();
if ((bouncerDismissible || !showing) != (mLastBouncerDismissible || !mLastShowing)
|| mFirstUpdate) {
@@ -392,6 +399,8 @@ public class StatusBarKeyguardViewManager {
mLastOccluded = occluded;
mLastBouncerShowing = bouncerShowing;
mLastBouncerDismissible = bouncerDismissible;
+
+ mPhoneStatusBar.onKeyguardViewManagerStatesUpdated();
}
public boolean onMenuPressed() {
@@ -422,4 +431,12 @@ public class StatusBarKeyguardViewManager {
public boolean isGoingToNotificationShade() {
return mPhoneStatusBar.isGoingToNotificationShade();
}
+
+ public boolean isSecure(int userId) {
+ return mBouncer.isSecure() || mLockPatternUtils.isSecure(userId);
+ }
+
+ public boolean isInputRestricted() {
+ return mViewMediatorCallback.isInputRestricted();
+ }
}
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 4053c1e..0dbdca1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
@@ -39,6 +39,7 @@ public class StatusBarWindowManager {
private final WindowManager mWindowManager;
private View mStatusBarView;
private WindowManager.LayoutParams mLp;
+ private WindowManager.LayoutParams mLpChanged;
private int mBarHeight;
private final boolean mKeyguardScreenRotation;
@@ -85,41 +86,43 @@ public class StatusBarWindowManager {
mStatusBarView = statusBarView;
mBarHeight = barHeight;
mWindowManager.addView(mStatusBarView, mLp);
+ mLpChanged = new WindowManager.LayoutParams();
+ mLpChanged.copyFrom(mLp);
}
private void applyKeyguardFlags(State state) {
if (state.keyguardShowing) {
- mLp.flags |= WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
- mLp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
+ mLpChanged.flags |= WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+ mLpChanged.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
} else {
- mLp.flags &= ~WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
- mLp.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
+ mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+ mLpChanged.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
}
}
private void adjustScreenOrientation(State state) {
if (state.isKeyguardShowingAndNotOccluded()) {
if (mKeyguardScreenRotation) {
- mLp.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_USER;
+ mLpChanged.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_USER;
} else {
- mLp.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
+ mLpChanged.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
}
} else {
- mLp.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+ mLpChanged.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
}
}
private void applyFocusableFlag(State state) {
if (state.isKeyguardShowingAndNotOccluded() && state.keyguardNeedsInput
&& state.bouncerShowing) {
- mLp.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
- mLp.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+ mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+ mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
} else if (state.isKeyguardShowingAndNotOccluded() || state.statusBarFocusable) {
- mLp.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
- mLp.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+ mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+ mLpChanged.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
} else {
- mLp.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
- mLp.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+ mLpChanged.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+ mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
}
}
@@ -127,9 +130,9 @@ public class StatusBarWindowManager {
boolean expanded = state.isKeyguardShowingAndNotOccluded() || state.statusBarExpanded
|| state.keyguardFadingAway || state.bouncerShowing;
if (expanded) {
- mLp.height = ViewGroup.LayoutParams.MATCH_PARENT;
+ mLpChanged.height = ViewGroup.LayoutParams.MATCH_PARENT;
} else {
- mLp.height = mBarHeight;
+ mLpChanged.height = mBarHeight;
}
}
@@ -141,9 +144,9 @@ public class StatusBarWindowManager {
if (state.isKeyguardShowingAndNotOccluded()
&& state.statusBarState == StatusBarState.KEYGUARD
&& !state.qsExpanded) {
- mLp.userActivityTimeout = state.keyguardUserActivityTimeout;
+ mLpChanged.userActivityTimeout = state.keyguardUserActivityTimeout;
} else {
- mLp.userActivityTimeout = -1;
+ mLpChanged.userActivityTimeout = -1;
}
}
@@ -151,9 +154,11 @@ public class StatusBarWindowManager {
if (state.isKeyguardShowingAndNotOccluded()
&& state.statusBarState == StatusBarState.KEYGUARD
&& !state.qsExpanded) {
- mLp.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_DISABLE_USER_ACTIVITY;
+ mLpChanged.inputFeatures |=
+ WindowManager.LayoutParams.INPUT_FEATURE_DISABLE_USER_ACTIVITY;
} else {
- mLp.inputFeatures &= ~WindowManager.LayoutParams.INPUT_FEATURE_DISABLE_USER_ACTIVITY;
+ mLpChanged.inputFeatures &=
+ ~WindowManager.LayoutParams.INPUT_FEATURE_DISABLE_USER_ACTIVITY;
}
}
@@ -165,7 +170,9 @@ public class StatusBarWindowManager {
applyUserActivityTimeout(state);
applyInputFeatures(state);
applyFitsSystemWindows(state);
- mWindowManager.updateViewLayout(mStatusBarView, mLp);
+ if (mLp.copyFrom(mLpChanged) != 0) {
+ mWindowManager.updateViewLayout(mStatusBarView, mLp);
+ }
}
public void setKeyguardShowing(boolean showing) {
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 f0c599d..a96f4e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -168,11 +168,12 @@ 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
if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
- mService.wakeUpIfDozing(ev.getEventTime(), true);
+ mService.wakeUpIfDozing(ev.getEventTime(), ev);
}
}
if (!intercept) {
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 dcda2c7..b89aa8f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/TrustDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/TrustDrawable.java
@@ -133,6 +133,7 @@ public class TrustDrawable extends Drawable {
if (!mAnimating) {
mAnimating = true;
updateState(true);
+ invalidateSelf();
}
}
@@ -146,18 +147,21 @@ public class TrustDrawable extends Drawable {
mState = STATE_UNSET;
mCurAlpha = 0;
mCurInnerRadius = mInnerRadiusEnter;
+ invalidateSelf();
}
}
public void setTrustManaged(boolean trustManaged) {
if (trustManaged == mTrustManaged && mState != STATE_UNSET) return;
mTrustManaged = trustManaged;
- if (mAnimating) {
- updateState(true);
- }
+ updateState(true);
}
- private void updateState(boolean animate) {
+ private void updateState(boolean allowTransientState) {
+ if (!mAnimating) {
+ return;
+ }
+
int nextState = mState;
if (mState == STATE_UNSET) {
nextState = mTrustManaged ? STATE_ENTERING : STATE_GONE;
@@ -170,7 +174,7 @@ public class TrustDrawable extends Drawable {
} else if (mState == STATE_EXITING) {
if (mTrustManaged) nextState = STATE_ENTERING;
}
- if (!animate) {
+ if (!allowTransientState) {
if (nextState == STATE_ENTERING) nextState = STATE_VISIBLE;
if (nextState == STATE_EXITING) nextState = STATE_GONE;
}
@@ -200,9 +204,8 @@ public class TrustDrawable extends Drawable {
mState = nextState;
if (mCurAnimator != null) {
mCurAnimator.start();
- } else {
- invalidateSelf();
}
+ invalidateSelf();
}
}
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 e5eef9d..5ef345b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java
@@ -36,7 +36,10 @@ public class UnlockMethodCache {
private final LockPatternUtils mLockPatternUtils;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final ArrayList<OnUnlockMethodChangedListener> mListeners = new ArrayList<>();
- private boolean mMethodInsecure;
+ /** Whether the user configured a secure unlock method (PIN, password, etc.) */
+ private boolean mSecure;
+ /** Whether the unlock method is currently insecure (insecure method or trusted environment) */
+ private boolean mCurrentlyInsecure;
private boolean mTrustManaged;
private boolean mFaceUnlockRunning;
@@ -44,7 +47,7 @@ public class UnlockMethodCache {
mLockPatternUtils = new LockPatternUtils(ctx);
mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(ctx);
KeyguardUpdateMonitor.getInstance(ctx).registerCallback(mCallback);
- updateMethodSecure(true /* updateAlways */);
+ update(true /* updateAlways */);
}
public static UnlockMethodCache getInstance(Context context) {
@@ -55,10 +58,17 @@ public class UnlockMethodCache {
}
/**
- * @return whether the current security method is secure, i. e. the bouncer will be shown
+ * @return whether the user configured a secure unlock method like PIN, password, etc.
*/
- public boolean isMethodInsecure() {
- return mMethodInsecure;
+ public boolean isMethodSecure() {
+ return mSecure;
+ }
+
+ /**
+ * @return whether the lockscreen is currently insecure, i. e. the bouncer won't be shown
+ */
+ public boolean isCurrentlyInsecure() {
+ return mCurrentlyInsecure;
}
public void addListener(OnUnlockMethodChangedListener listener) {
@@ -69,58 +79,59 @@ public class UnlockMethodCache {
mListeners.remove(listener);
}
- private void updateMethodSecure(boolean updateAlways) {
+ private void update(boolean updateAlways) {
int user = mLockPatternUtils.getCurrentUser();
- boolean methodInsecure = !mLockPatternUtils.isSecure() ||
- mKeyguardUpdateMonitor.getUserHasTrust(user);
+ boolean secure = mLockPatternUtils.isSecure();
+ boolean currentlyInsecure = !secure || mKeyguardUpdateMonitor.getUserHasTrust(user);
boolean trustManaged = mKeyguardUpdateMonitor.getUserTrustIsManaged(user);
boolean faceUnlockRunning = mKeyguardUpdateMonitor.isFaceUnlockRunning(user)
&& trustManaged;
- boolean changed = methodInsecure != mMethodInsecure || trustManaged != mTrustManaged
- || faceUnlockRunning != mFaceUnlockRunning;
+ boolean changed = secure != mSecure || currentlyInsecure != mCurrentlyInsecure ||
+ trustManaged != mTrustManaged || faceUnlockRunning != mFaceUnlockRunning;
if (changed || updateAlways) {
- mMethodInsecure = methodInsecure;
+ mSecure = secure;
+ mCurrentlyInsecure = currentlyInsecure;
mTrustManaged = trustManaged;
mFaceUnlockRunning = faceUnlockRunning;
- notifyListeners(mMethodInsecure);
+ notifyListeners();
}
}
- private void notifyListeners(boolean secure) {
+ private void notifyListeners() {
for (OnUnlockMethodChangedListener listener : mListeners) {
- listener.onMethodSecureChanged(secure);
+ listener.onUnlockMethodStateChanged();
}
}
private final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
@Override
public void onUserSwitchComplete(int userId) {
- updateMethodSecure(false /* updateAlways */);
+ update(false /* updateAlways */);
}
@Override
public void onTrustChanged(int userId) {
- updateMethodSecure(false /* updateAlways */);
+ update(false /* updateAlways */);
}
@Override
public void onTrustManagedChanged(int userId) {
- updateMethodSecure(false /* updateAlways */);
+ update(false /* updateAlways */);
}
@Override
public void onScreenTurnedOn() {
- updateMethodSecure(false /* updateAlways */);
+ update(false /* updateAlways */);
}
@Override
public void onFingerprintRecognized(int userId) {
- updateMethodSecure(false /* updateAlways */);
+ update(false /* updateAlways */);
}
@Override
public void onFaceUnlockStateChanged(boolean running, int userId) {
- updateMethodSecure(false /* updateAlways */);
+ update(false /* updateAlways */);
}
};
@@ -133,6 +144,6 @@ public class UnlockMethodCache {
}
public static interface OnUnlockMethodChangedListener {
- void onMethodSecureChanged(boolean methodSecure);
+ void onUnlockMethodStateChanged();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiAccessPointController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
index b800fbf..ad4c211 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiAccessPointController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
@@ -16,32 +16,42 @@
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.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.systemui.R;
-import com.android.systemui.statusbar.policy.NetworkController.AccessPoint;
-import com.android.systemui.statusbar.policy.NetworkController.AccessPointCallback;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
-public class WifiAccessPointController {
- private static final String TAG = "WifiAccessPointController";
- private static final boolean DEBUG = false;
+
+// 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 {
+ private static final String TAG = "AccessPointController";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ // This string extra specifies a network to open the connect dialog on, so the user can enter
+ // network credentials. This is used by quick settings for secured networks.
+ private static final String EXTRA_START_CONNECT_SSID = "wifi_start_connect_ssid";
private static final int[] ICONS = {
R.drawable.ic_qs_wifi_0,
@@ -54,52 +64,88 @@ public class WifiAccessPointController {
private final Context mContext;
private final ArrayList<AccessPointCallback> mCallbacks = new ArrayList<AccessPointCallback>();
private final WifiManager mWifiManager;
+ private final UserManager mUserManager;
private final Receiver mReceiver = new Receiver();
+ private NetworkControllerImpl mNetworkController;
private boolean mScanning;
+ private int mCurrentUser;
- public WifiAccessPointController(Context context) {
+ public AccessPointControllerImpl(Context context) {
mContext = context;
mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+ mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ mCurrentUser = ActivityManager.getCurrentUser();
+ }
+
+ void setNetworkController(NetworkControllerImpl networkController) {
+ mNetworkController = networkController;
+ }
+
+ public boolean canConfigWifi() {
+ return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_WIFI,
+ new UserHandle(mCurrentUser));
}
- public void addCallback(AccessPointCallback callback) {
+ public void onUserSwitched(int newUserId) {
+ mCurrentUser = newUserId;
+ }
+
+ @Override
+ public void addAccessPointCallback(AccessPointCallback callback) {
if (callback == null || mCallbacks.contains(callback)) return;
if (DEBUG) Log.d(TAG, "addCallback " + callback);
mCallbacks.add(callback);
mReceiver.setListening(!mCallbacks.isEmpty());
}
- public void removeCallback(AccessPointCallback callback) {
+ @Override
+ public void removeAccessPointCallback(AccessPointCallback callback) {
if (callback == null) return;
if (DEBUG) Log.d(TAG, "removeCallback " + callback);
mCallbacks.remove(callback);
mReceiver.setListening(!mCallbacks.isEmpty());
}
- public void scan() {
+ @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();
}
- public void connect(AccessPoint ap) {
- if (ap == null || ap.networkId < 0) return;
+ public boolean connect(AccessPoint ap) {
+ if (ap == null) return false;
if (DEBUG) Log.d(TAG, "connect networkId=" + ap.networkId);
- mWifiManager.connect(ap.networkId, new ActionListener() {
- @Override
- public void onSuccess() {
- if (DEBUG) Log.d(TAG, "connect success");
+ if (ap.networkId < 0) {
+ // Unknown network, need to add it.
+ if (ap.hasSecurity) {
+ Intent intent = new Intent(Settings.ACTION_WIFI_SETTINGS);
+ intent.putExtra(EXTRA_START_CONNECT_SSID, ap.ssid);
+ 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);
}
+ } else {
+ mWifiManager.connect(ap.networkId, mConnectListener);
+ }
+ return false;
+ }
- @Override
- public void onFailure(int reason) {
- if (DEBUG) Log.d(TAG, "connect failure reason=" + reason);
- }
- });
+ private void fireSettingsIntentCallback(Intent intent) {
+ for (AccessPointCallback callback : mCallbacks) {
+ callback.onSettingsActivityTriggered(intent);
+ }
}
- private void fireCallback(AccessPoint[] aps) {
+ private void fireAcccessPointsCallback(AccessPoint[] aps) {
for (AccessPointCallback callback : mCallbacks) {
callback.onAccessPointsChanged(aps);
}
@@ -110,8 +156,7 @@ public class WifiAccessPointController {
&& v.charAt(v.length() - 1) == '\"' ? v.substring(1, v.length() - 1) : v;
}
- private int getConnectedNetworkId() {
- final WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
+ private int getConnectedNetworkId(WifiInfo wifiInfo) {
return wifiInfo != null ? wifiInfo.getNetworkId() : AccessPoint.NO_NETWORK;
}
@@ -126,7 +171,8 @@ public class WifiAccessPointController {
}
private void updateAccessPoints() {
- final int connectedNetworkId = getConnectedNetworkId();
+ 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();
@@ -139,23 +185,50 @@ public class WifiAccessPointController {
}
final String ssid = scanResult.SSID;
if (TextUtils.isEmpty(ssid) || ssids.contains(ssid)) continue;
- if (!configured.containsKey(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;
- ap.iconId = ICONS[level];
- ap.isConnected = ap.networkId != AccessPoint.NO_NETWORK
- && ap.networkId == connectedNetworkId;
- ap.level = level;
+ // 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);
- fireCallback(aps.toArray(new AccessPoint[aps.size()]));
+ fireAcccessPointsCallback(aps.toArray(new AccessPoint[aps.size()]));
}
+ private final ActionListener mConnectListener = new ActionListener() {
+ @Override
+ public void onSuccess() {
+ if (DEBUG) Log.d(TAG, "connect success");
+ }
+
+ @Override
+ public void onFailure(int reason) {
+ 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) {
@@ -163,7 +236,7 @@ public class WifiAccessPointController {
}
private int score(AccessPoint ap) {
- return ap.level + (ap.isConnected ? 10 : 0);
+ return ap.level + (ap.isConnected ? 20 : 0) + (ap.isConfigured ? 10 : 0);
}
};
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 7ac2a98..63fcbc5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityContentDescriptions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityContentDescriptions.java
@@ -33,11 +33,6 @@ public class AccessibilityContentDescriptions {
R.string.accessibility_wifi_three_bars,
R.string.accessibility_wifi_signal_full
};
- static final int[] WIMAX_CONNECTION_STRENGTH = {
- R.string.accessibility_no_wimax,
- R.string.accessibility_wimax_one_bar,
- R.string.accessibility_wimax_two_bars,
- R.string.accessibility_wimax_three_bars,
- R.string.accessibility_wimax_signal_full
- };
+
+ static final int WIFI_NO_CONNECTION = R.string.accessibility_no_wifi;
}
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 80fec5b..81e1e45 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -19,26 +19,35 @@ 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.profileStateToString;
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.ArraySet;
import android.util.Log;
-import android.util.SparseBooleanArray;
+import android.util.SparseArray;
import com.android.systemui.statusbar.policy.BluetoothUtil.Profile;
@@ -50,19 +59,44 @@ import java.util.Set;
public class BluetoothControllerImpl implements BluetoothController {
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 boolean mEnabled;
private boolean mConnecting;
private BluetoothDevice mLastDevice;
- public BluetoothControllerImpl(Context context) {
+ public BluetoothControllerImpl(Context context, Looper bgLooper) {
mContext = context;
+ mHandler = new H(bgLooper);
+
final BluetoothManager bluetoothManager =
(BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
mAdapter = bluetoothManager.getAdapter();
@@ -73,7 +107,8 @@ public class BluetoothControllerImpl implements BluetoothController {
mReceiver.register();
setAdapterState(mAdapter.getState());
- updateBondedBluetoothDevices();
+ updateBondedDevices();
+ bindAllProfiles();
}
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
@@ -83,6 +118,7 @@ public class BluetoothControllerImpl implements BluetoothController {
pw.print(" mConnecting="); pw.println(mConnecting);
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);
@@ -95,7 +131,23 @@ public class BluetoothControllerImpl implements BluetoothController {
private static String infoToString(DeviceInfo info) {
return info == null ? null : ("connectionState=" +
- connectionStateToString(info.connectionState) + ",bonded=" + info.bonded);
+ connectionStateToString(CONNECTION_STATES[info.connectionStateIndex])
+ + ",bonded=" + info.bonded + ",profiles="
+ + profilesToString(info.connectedProfiles));
+ }
+
+ 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();
}
public void addStateChangedCallback(Callback cb) {
@@ -152,13 +204,14 @@ public class BluetoothControllerImpl implements BluetoothController {
paired.id = device.getAddress();
paired.tag = device;
paired.name = device.getAliasName();
- paired.state = connectionStateToPairedDeviceState(info.connectionState);
+ paired.state = connectionStateToPairedDeviceState(info.connectionStateIndex);
rt.add(paired);
}
return rt;
}
- private static int connectionStateToPairedDeviceState(int state) {
+ 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;
@@ -178,6 +231,7 @@ public class BluetoothControllerImpl implements BluetoothController {
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();
@@ -185,43 +239,35 @@ public class BluetoothControllerImpl implements BluetoothController {
Log.w(TAG, "No uuids returned, aborting " + action + " for " + deviceToString(device));
return;
}
- final SparseBooleanArray profiles = new SparseBooleanArray();
- 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 int profileState = mAdapter.getProfileConnectionState(profile);
- if (DEBUG && !profiles.get(profile)) Log.d(TAG, "Profile " + profileToString(profile)
- + " state = " + profileStateToString(profileState));
- final boolean connected = profileState == BluetoothProfile.STATE_CONNECTED;
- if (connect != connected) {
- profiles.put(profile, true);
+ 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);
- mAdapter.getProfileProxy(mContext, new ServiceListener() {
- @Override
- public void onServiceConnected(int profile, BluetoothProfile proxy) {
- if (DEBUG) Log.d(TAG, "onServiceConnected " + profileToString(profile));
- final Profile p = BluetoothUtil.getProfile(proxy);
- if (p == null) {
- Log.w(TAG, "Unable get get Profile for " + profileToString(profile));
- } else {
- final boolean ok = connect ? p.connect(device) : p.disconnect(device);
- if (DEBUG) Log.d(TAG, action + " " + profileToString(profile) + " "
- + (ok ? "succeeded" : "failed"));
- }
- }
-
- @Override
- public void onServiceDisconnected(int profile) {
- if (DEBUG) Log.d(TAG, "onServiceDisconnected " + profileToString(profile));
- }
- }, profile);
+ 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));
+ }
}
}
@@ -230,7 +276,27 @@ public class BluetoothControllerImpl implements BluetoothController {
return mLastDevice != null ? mLastDevice.getAliasName() : null;
}
- private void updateBondedBluetoothDevices() {
+ 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();
+ }
+
+ private void handleUpdateBondedDevices() {
if (mAdapter == null) return;
final Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
for (DeviceInfo info : mDeviceInfo.values()) {
@@ -251,9 +317,92 @@ public class BluetoothControllerImpl implements BluetoothController {
if (mLastDevice == null && bondedCount == 1) {
mLastDevice = lastBonded;
}
+ updateConnectionStates();
firePairedDevicesChanged();
}
+ 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);
+ }
+ }
+ 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;
+ }
+ }
+ 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);
+ }
+ }
+ 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;
+ }
+ }
+ }
+ }
+
+ 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.
+ }
+
private void firePairedDevicesChanged() {
for (Callback cb : mCallbacks) {
cb.onBluetoothPairedDevicesChanged();
@@ -283,6 +432,43 @@ public class BluetoothControllerImpl implements BluetoothController {
cb.onBluetoothStateChange(mEnabled, mConnecting);
}
+ 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 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();
+ }
+
+ @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();
@@ -290,6 +476,13 @@ public class BluetoothControllerImpl implements BluetoothController {
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);
}
@@ -297,28 +490,35 @@ public class BluetoothControllerImpl implements BluetoothController {
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)) {
- final DeviceInfo info = updateInfo(device);
+ updateInfo(device);
final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE,
ERROR);
- if (state != ERROR) {
- info.connectionState = state;
- }
mLastDevice = device;
if (DEBUG) Log.d(TAG, "ACTION_CONNECTION_STATE_CHANGED "
+ connectionStateToString(state) + " " + deviceToString(device));
- setConnecting(info.connectionState == BluetoothAdapter.STATE_CONNECTING);
+ 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);
- // we'll update all bonded devices below
+ 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);
+ }
}
- updateBondedBluetoothDevices();
}
}
@@ -329,8 +529,44 @@ public class BluetoothControllerImpl implements BluetoothController {
return info;
}
+ private class H extends Handler {
+ public H(Looper l) {
+ super(l);
+ }
+
+ 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 connectionState = BluetoothAdapter.STATE_DISCONNECTED;
+ 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
index 1b4be85..ed8ac2c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothUtil.java
@@ -36,7 +36,10 @@ public class BluetoothUtil {
if (profile == BluetoothProfile.HEADSET) return "HEADSET";
if (profile == BluetoothProfile.A2DP) return "A2DP";
if (profile == BluetoothProfile.AVRCP_CONTROLLER) return "AVRCP_CONTROLLER";
- return "UNKNOWN";
+ 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) {
@@ -106,6 +109,11 @@ public class BluetoothUtil {
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;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index 55a0bba..779ff52 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -16,12 +16,14 @@
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.content.res.TypedArray;
import android.os.Bundle;
+import android.os.UserHandle;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.format.DateFormat;
@@ -91,7 +93,8 @@ public class Clock extends TextView implements DemoMode {
filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
filter.addAction(Intent.ACTION_USER_SWITCHED);
- getContext().registerReceiver(mIntentReceiver, filter, null, getHandler());
+ getContext().registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, filter,
+ null, getHandler());
}
// NOTE: It's safe to do these after registering the receiver since the receiver always runs
@@ -142,7 +145,7 @@ public class Clock extends TextView implements DemoMode {
private final CharSequence getSmallTime() {
Context context = getContext();
- boolean is24 = DateFormat.is24HourFormat(context);
+ boolean is24 = DateFormat.is24HourFormat(context, ActivityManager.getCurrentUser());
LocaleData d = LocaleData.get(context.getResources().getConfiguration().locale);
final char MAGIC1 = '\uEF00';
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 6f021ac..33f7aff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java
@@ -308,7 +308,11 @@ public class FlashlightController {
new CameraCaptureSession.StateListener() {
@Override
public void onConfigured(CameraCaptureSession session) {
- mSession = session;
+ if (session.getDevice() == mCameraDevice) {
+ mSession = session;
+ } else {
+ session.close();
+ }
postUpdateFlashlight();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java
index 84216a4..2e3e67a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java
@@ -18,8 +18,13 @@ 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;
@@ -39,27 +44,36 @@ 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 ExpandHelper mExpandHelper;
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);
@@ -67,8 +81,12 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper.
public HeadsUpNotificationView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
- mTouchSensitivityDelay = getResources().getInteger(R.integer.heads_up_sensitivity_delay);
+ 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() {
@@ -100,8 +118,10 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper.
}
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) {
@@ -115,7 +135,7 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper.
sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
mSwipeHelper.snapChild(mContentHolder, 1f);
- mStartTouchTime = System.currentTimeMillis() + mTouchSensitivityDelay;
+ mStartTouchTime = SystemClock.elapsedRealtime() + mTouchSensitivityDelay;
mHeadsUp.setInterruption();
@@ -166,6 +186,31 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper.
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();
@@ -205,11 +250,28 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper.
int minHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
int maxHeight = getResources().getDimensionPixelSize(R.dimen.notification_max_height);
- mExpandHelper = new ExpandHelper(getContext(), this, minHeight, maxHeight);
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);
@@ -219,14 +281,18 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper.
}
@Override
+ protected void onDetachedFromWindow() {
+ mContext.getContentResolver().unregisterContentObserver(mSettingsObserver);
+ }
+
+ @Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (DEBUG) Log.v(TAG, "onInterceptTouchEvent()");
- if (System.currentTimeMillis() < mStartTouchTime) {
+ if (SystemClock.elapsedRealtime() < mStartTouchTime) {
return true;
}
return mEdgeSwipeHelper.onInterceptTouchEvent(ev)
|| mSwipeHelper.onInterceptTouchEvent(ev)
- || mExpandHelper.onInterceptTouchEvent(ev)
|| super.onInterceptTouchEvent(ev);
}
@@ -248,13 +314,12 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper.
@Override
public boolean onTouchEvent(MotionEvent ev) {
- if (System.currentTimeMillis() < mStartTouchTime) {
+ if (SystemClock.elapsedRealtime() < mStartTouchTime) {
return false;
}
mBar.resetHeadsUpDecayTimer();
return mEdgeSwipeHelper.onTouchEvent(ev)
|| mSwipeHelper.onTouchEvent(ev)
- || mExpandHelper.onTouchEvent(ev)
|| super.onTouchEvent(ev);
}
@@ -373,6 +438,10 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper.
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;
@@ -399,15 +468,12 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper.
final float dY = ev.getY() - mFirstY;
final float daX = Math.abs(ev.getX() - mFirstX);
final float daY = Math.abs(dY);
- if (!mConsuming && (4f * daX) < daY && daY > mTouchSlop) {
+ if (!mConsuming && daX < daY && daY > mTouchSlop) {
+ snooze();
if (dY > 0) {
if (DEBUG_EDGE_SWIPE) Log.d(TAG, "found an open");
mBar.animateExpandNotificationsPanel();
}
- if (dY < 0) {
- if (DEBUG_EDGE_SWIPE) Log.d(TAG, "found a close");
- mBar.onHeadsUpDismissed();
- }
mConsuming = true;
}
break;
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 63c1100..5eff5a6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
@@ -18,6 +18,7 @@ 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;
@@ -35,6 +36,13 @@ 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 final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
private final Receiver mReceiver = new Receiver();
@@ -91,20 +99,29 @@ public class HotspotControllerImpl implements HotspotController {
@Override
public void setHotspotEnabled(boolean enabled) {
final ContentResolver cr = mContext.getContentResolver();
- // This needs to be kept up to date with Settings (WifiApEnabler.setSoftapEnabled)
- // in case it is turned on in settings and off in qs (or vice versa).
- // Disable Wifi if enabling tethering.
- int wifiState = mWifiManager.getWifiState();
- if (enabled && ((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, enabled);
-
- // If needed, restore Wifi on tether disable.
- if (!enabled) {
+ // 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);
+ }
+ } 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);
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 a3765aa..6998791 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java
@@ -102,7 +102,6 @@ public class KeyButtonRipple extends Drawable {
}
}
-
@Override
public void draw(Canvas canvas) {
mSupportHardware = canvas.isHardwareAccelerated();
@@ -176,6 +175,11 @@ public class KeyButtonRipple extends Drawable {
}
@Override
+ public void jumpToCurrentState() {
+ cancelAnimations();
+ }
+
+ @Override
public boolean isStateful() {
return true;
}
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 7cc75da..b9cc0f9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
@@ -17,38 +17,27 @@
package com.android.systemui.statusbar.policy;
import android.animation.Animator;
-import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
-import android.animation.TimeInterpolator;
import android.app.ActivityManager;
import android.content.Context;
import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.CanvasProperty;
-import android.graphics.Paint;
-import android.graphics.RectF;
import android.hardware.input.InputManager;
import android.media.AudioManager;
import android.os.Bundle;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.Log;
-import android.util.MathUtils;
import android.view.HapticFeedbackConstants;
-import android.view.HardwareCanvas;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.MotionEvent;
-import android.view.RenderNodeAnimator;
import android.view.SoundEffectConstants;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.ImageView;
-import java.lang.Math;
-import java.util.ArrayList;
import com.android.systemui.R;
@@ -124,6 +113,14 @@ public class KeyButtonView extends ImageView {
}
@Override
+ protected void onWindowVisibilityChanged(int visibility) {
+ super.onWindowVisibilityChanged(visibility);
+ if (visibility != View.VISIBLE) {
+ jumpDrawablesToCurrentState();
+ }
+ }
+
+ @Override
public boolean performAccessibilityAction(int action, Bundle arguments) {
if (action == ACTION_CLICK && mCode != 0) {
sendEvent(KeyEvent.ACTION_DOWN, 0, SystemClock.uptimeMillis());
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 8f0000f..1460e5f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
@@ -19,15 +19,16 @@ package com.android.systemui.statusbar.policy;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
import android.content.Context;
import android.database.DataSetObserver;
+import android.util.AttributeSet;
import android.view.LayoutInflater;
+import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.view.animation.AnimationUtils;
-import android.widget.TextView;
+import android.widget.FrameLayout;
import com.android.keyguard.AppearAnimationUtils;
import com.android.systemui.R;
@@ -35,8 +36,6 @@ import com.android.systemui.qs.tiles.UserDetailItemView;
import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
import com.android.systemui.statusbar.phone.NotificationPanelView;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
-import com.android.systemui.statusbar.phone.StatusBarHeaderView;
-import com.android.systemui.statusbar.phone.UserAvatarView;
/**
* Manages the user switcher on the Keyguard.
@@ -46,6 +45,7 @@ public class KeyguardUserSwitcher {
private static final String TAG = "KeyguardUserSwitcher";
private static final boolean ALWAYS_ON = false;
+ private final Container mUserSwitcherContainer;
private final ViewGroup mUserSwitcher;
private final KeyguardStatusBarView mStatusBarView;
private final Adapter mAdapter;
@@ -53,24 +53,31 @@ public class KeyguardUserSwitcher {
private final KeyguardUserSwitcherScrim mBackground;
private ObjectAnimator mBgAnimator;
private UserSwitcherController mUserSwitcherController;
+ private boolean mAnimating;
public KeyguardUserSwitcher(Context context, ViewStub userSwitcher,
KeyguardStatusBarView statusBarView, NotificationPanelView panelView,
UserSwitcherController userSwitcherController) {
- if (context.getResources().getBoolean(R.bool.config_keyguardUserSwitcher) || ALWAYS_ON) {
- mUserSwitcher = (ViewGroup) userSwitcher.inflate();
+ boolean keyguardUserSwitcherEnabled =
+ context.getResources().getBoolean(R.bool.config_keyguardUserSwitcher) || ALWAYS_ON;
+ if (userSwitcherController != null && keyguardUserSwitcherEnabled) {
+ mUserSwitcherContainer = (Container) userSwitcher.inflate();
+ mUserSwitcher = (ViewGroup)
+ mUserSwitcherContainer.findViewById(R.id.keyguard_user_switcher_inner);
mBackground = new KeyguardUserSwitcherScrim(mUserSwitcher);
mUserSwitcher.setBackground(mBackground);
mStatusBarView = statusBarView;
mStatusBarView.setKeyguardUserSwitcher(this);
panelView.setKeyguardUserSwitcher(this);
- mAdapter = new Adapter(context, userSwitcherController);
+ mAdapter = new Adapter(context, userSwitcherController, this);
mAdapter.registerDataSetObserver(mDataSetObserver);
mUserSwitcherController = userSwitcherController;
mAppearAnimationUtils = new AppearAnimationUtils(context, 400, -0.5f, 0.5f,
AnimationUtils.loadInterpolator(
context, android.R.interpolator.fast_out_slow_in));
+ mUserSwitcherContainer.setKeyguardUserSwitcher(this);
} else {
+ mUserSwitcherContainer = null;
mUserSwitcher = null;
mStatusBarView = null;
mAdapter = null;
@@ -98,9 +105,10 @@ public class KeyguardUserSwitcher {
}
public void show(boolean animate) {
- if (mUserSwitcher != null && mUserSwitcher.getVisibility() != View.VISIBLE) {
+ if (mUserSwitcher != null && mUserSwitcherContainer.getVisibility() != View.VISIBLE) {
cancelAnimations();
- mUserSwitcher.setVisibility(View.VISIBLE);
+ mAdapter.refresh();
+ mUserSwitcherContainer.setVisibility(View.VISIBLE);
mStatusBarView.setKeyguardUserSwitcherShowing(true, animate);
if (animate) {
startAppearAnimation();
@@ -108,13 +116,13 @@ public class KeyguardUserSwitcher {
}
}
- public void hide(boolean animate) {
- if (mUserSwitcher != null && mUserSwitcher.getVisibility() == View.VISIBLE) {
+ private void hide(boolean animate) {
+ if (mUserSwitcher != null && mUserSwitcherContainer.getVisibility() == View.VISIBLE) {
cancelAnimations();
if (animate) {
startDisappearAnimation();
} else {
- mUserSwitcher.setVisibility(View.GONE);
+ mUserSwitcherContainer.setVisibility(View.GONE);
}
mStatusBarView.setKeyguardUserSwitcherShowing(false, animate);
}
@@ -129,6 +137,7 @@ public class KeyguardUserSwitcher {
mBgAnimator.cancel();
}
mUserSwitcher.animate().cancel();
+ mAnimating = false;
}
private void startAppearAnimation() {
@@ -139,13 +148,14 @@ public class KeyguardUserSwitcher {
}
mUserSwitcher.setClipChildren(false);
mUserSwitcher.setClipToPadding(false);
- mAppearAnimationUtils.startAppearAnimation(objects, new Runnable() {
+ mAppearAnimationUtils.startAnimation(objects, new Runnable() {
@Override
public void run() {
mUserSwitcher.setClipChildren(true);
mUserSwitcher.setClipToPadding(true);
}
});
+ mAnimating = true;
mBgAnimator = ObjectAnimator.ofInt(mBackground, "alpha", 0, 255);
mBgAnimator.setDuration(400);
mBgAnimator.setInterpolator(PhoneStatusBar.ALPHA_IN);
@@ -153,12 +163,14 @@ public class KeyguardUserSwitcher {
@Override
public void onAnimationEnd(Animator animation) {
mBgAnimator = null;
+ mAnimating = false;
}
});
mBgAnimator.start();
}
private void startDisappearAnimation() {
+ mAnimating = true;
mUserSwitcher.animate()
.alpha(0f)
.setDuration(300)
@@ -166,8 +178,9 @@ public class KeyguardUserSwitcher {
.withEndAction(new Runnable() {
@Override
public void run() {
- mUserSwitcher.setVisibility(View.GONE);
+ mUserSwitcherContainer.setVisibility(View.GONE);
mUserSwitcher.setAlpha(1f);
+ mAnimating = false;
}
});
}
@@ -198,6 +211,16 @@ public class KeyguardUserSwitcher {
}
}
+ public void hideIfNotSimple(boolean animate) {
+ if (mUserSwitcherContainer != null && !mUserSwitcherController.isSimpleUserSwitcher()) {
+ hide(animate);
+ }
+ }
+
+ boolean isAnimating() {
+ return mAnimating;
+ }
+
public final DataSetObserver mDataSetObserver = new DataSetObserver() {
@Override
public void onChanged() {
@@ -209,10 +232,13 @@ public class KeyguardUserSwitcher {
View.OnClickListener {
private Context mContext;
+ private KeyguardUserSwitcher mKeyguardUserSwitcher;
- public Adapter(Context context, UserSwitcherController controller) {
+ public Adapter(Context context, UserSwitcherController controller,
+ KeyguardUserSwitcher kgu) {
super(controller);
mContext = context;
+ mKeyguardUserSwitcher = kgu;
}
@Override
@@ -240,7 +266,37 @@ public class KeyguardUserSwitcher {
@Override
public void onClick(View v) {
- switchTo(((UserSwitcherController.UserRecord)v.getTag()));
+ UserSwitcherController.UserRecord user = (UserSwitcherController.UserRecord) v.getTag();
+ if (user.isCurrent && !user.isGuest) {
+ // Close the switcher if tapping the current user. Guest is excluded because
+ // tapping the guest user while it's current clears the session.
+ mKeyguardUserSwitcher.hideIfNotSimple(true /* animate */);
+ } else {
+ switchTo(user);
+ }
+ }
+ }
+
+ public static class Container extends FrameLayout {
+
+ private KeyguardUserSwitcher mKeyguardUserSwitcher;
+
+ public Container(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ setClipChildren(false);
+ }
+
+ public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) {
+ mKeyguardUserSwitcher = keyguardUserSwitcher;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ // Hide switcher if it didn't handle the touch event (and let the event go through).
+ if (mKeyguardUserSwitcher != null && !mKeyguardUserSwitcher.isAnimating()) {
+ mKeyguardUserSwitcher.hideIfNotSimple(true /* animate */);
+ }
+ return false;
}
}
}
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 4363037..a5fc2fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherScrim.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherScrim.java
@@ -42,7 +42,7 @@ public class KeyguardUserSwitcherScrim extends Drawable
private int mDarkColor;
private int mTop;
- private int mAlpha;
+ private int mAlpha = 255;
private Paint mRadialGradientPaint = new Paint();
private int mLayoutWidth;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileDataController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileDataControllerImpl.java
index 33d68bf..f2b2f66 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileDataController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileDataControllerImpl.java
@@ -33,17 +33,16 @@ import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.format.DateUtils;
import android.text.format.Time;
import android.util.Log;
-import com.android.systemui.statusbar.policy.NetworkController.DataUsageInfo;
-
import java.util.Date;
import java.util.Locale;
-public class MobileDataController {
+public class MobileDataControllerImpl implements NetworkController.MobileDataController {
private static final String TAG = "MobileDataController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -61,8 +60,9 @@ public class MobileDataController {
private INetworkStatsSession mSession;
private Callback mCallback;
+ private NetworkControllerImpl mNetworkController;
- public MobileDataController(Context context) {
+ public MobileDataControllerImpl(Context context) {
mContext = context;
mTelephonyManager = TelephonyManager.from(context);
mConnectivityManager = ConnectivityManager.from(context);
@@ -71,6 +71,10 @@ public class MobileDataController {
mPolicyManager = NetworkPolicyManager.from(mContext);
}
+ public void setNetworkController(NetworkControllerImpl networkController) {
+ mNetworkController = networkController;
+ }
+
private INetworkStatsSession getSession() {
if (mSession == null) {
try {
@@ -109,7 +113,9 @@ public class MobileDataController {
if (session == null) {
return warn("no stats session");
}
- final NetworkTemplate template = NetworkTemplate.buildTemplateMobileAll(subscriberId);
+ NetworkTemplate template = NetworkTemplate.buildTemplateMobileAll(subscriberId);
+ template = NetworkTemplate.normalize(template, mTelephonyManager.getMergedSubscriberIds());
+
final NetworkPolicy policy = findNetworkPolicy(template);
try {
final NetworkStatsHistory history = mSession.getHistoryForNetwork(template, FIELDS);
@@ -155,6 +161,9 @@ public class MobileDataController {
} else {
usage.warningLevel = DEFAULT_WARNING_LEVEL;
}
+ if (usage != null) {
+ usage.carrier = mNetworkController.getMobileNetworkName();
+ }
return usage;
} catch (RemoteException e) {
return warn("remote call failed");
@@ -189,6 +198,7 @@ public class MobileDataController {
}
public void setMobileDataEnabled(boolean enabled) {
+ Log.d(TAG, "setMobileDataEnabled: enabled=" + enabled);
mTelephonyManager.setDataEnabled(enabled);
if (mCallback != null) {
mCallback.onMobileDataEnabled(enabled);
@@ -207,7 +217,8 @@ public class MobileDataController {
private static String getActiveSubscriberId(Context context) {
final TelephonyManager tele = TelephonyManager.from(context);
- final String actualSubscriberId = tele.getSubscriberId();
+ final String actualSubscriberId = tele.getSubscriberId(
+ SubscriptionManager.getDefaultDataSubId());
return actualSubscriberId;
}
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 2ed9366..3cffc85 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
@@ -16,12 +16,17 @@
package com.android.systemui.statusbar.policy;
+import android.content.Intent;
+
public interface NetworkController {
boolean hasMobileDataFeature();
void addNetworkSignalChangedCallback(NetworkSignalChangedCallback cb);
void removeNetworkSignalChangedCallback(NetworkSignalChangedCallback cb);
void setWifiEnabled(boolean enabled);
+ void onUserSwitched(int newUserId);
+ AccessPointController getAccessPointController();
+ MobileDataController getMobileDataController();
public interface NetworkSignalChangedCallback {
void onWifiSignalChanged(boolean enabled, boolean connected, int wifiSignalIconId,
@@ -30,40 +35,57 @@ public interface NetworkController {
void onMobileDataSignalChanged(boolean enabled, int mobileSignalIconId,
String mobileSignalContentDescriptionId, int dataTypeIconId,
boolean activityIn, boolean activityOut,
- String dataTypeContentDescriptionId, String description, boolean noSim,
+ String dataTypeContentDescriptionId, String description,
boolean isDataTypeIconWide);
+ void onNoSimVisibleChanged(boolean visible);
void onAirplaneModeChanged(boolean enabled);
void onMobileDataEnabled(boolean enabled);
}
- void addAccessPointCallback(AccessPointCallback callback);
- void removeAccessPointCallback(AccessPointCallback callback);
- void scanForAccessPoints();
- void connect(AccessPoint ap);
- boolean isMobileDataSupported();
- boolean isMobileDataEnabled();
- void setMobileDataEnabled(boolean enabled);
- DataUsageInfo getDataUsageInfo();
+ /**
+ * Tracks changes in access points. Allows listening for changes, scanning for new APs,
+ * and connecting to new ones.
+ */
+ public interface AccessPointController {
+ void addAccessPointCallback(AccessPointCallback callback);
+ void removeAccessPointCallback(AccessPointCallback callback);
+ void scanForAccessPoints();
+ boolean connect(AccessPoint ap);
+ boolean canConfigWifi();
- public interface AccessPointCallback {
- void onAccessPointsChanged(AccessPoint[] accessPoints);
- }
+ public interface AccessPointCallback {
+ void onAccessPointsChanged(AccessPoint[] accessPoints);
+ void onSettingsActivityTriggered(Intent settingsIntent);
+ }
- public static class AccessPoint {
- public static final int NO_NETWORK = -1; // see WifiManager
+ 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 int level; // 0 - 5
+ public int networkId;
+ public int iconId;
+ public String ssid;
+ public boolean isConnected;
+ public boolean isConfigured;
+ public boolean hasSecurity;
+ public int level; // 0 - 5
+ }
}
- public static class DataUsageInfo {
- public String carrier;
- public String period;
- public long limitLevel;
- public long warningLevel;
- public long usageLevel;
+ /**
+ * Tracks mobile data support and usage.
+ */
+ public interface MobileDataController {
+ boolean isMobileDataSupported();
+ boolean isMobileDataEnabled();
+ void setMobileDataEnabled(boolean enabled);
+ DataUsageInfo getDataUsageInfo();
+
+ public static class DataUsageInfo {
+ public String carrier;
+ public String period;
+ public long limitLevel;
+ public long warningLevel;
+ public long usageLevel;
+ }
}
}
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 3625997..9a7f21e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -16,17 +16,22 @@
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;
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.net.wimax.WimaxManagerConstants;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
@@ -36,185 +41,161 @@ 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.view.View;
-import android.widget.TextView;
+import android.util.SparseArray;
+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;
-import com.android.systemui.statusbar.phone.StatusBarHeaderView;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.Collections;
+import java.util.Comparator;
+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
implements NetworkController, DemoMode {
// debug
- static final String TAG = "StatusBar.NetworkController";
- static final boolean DEBUG = false;
- static final boolean CHATTY = false; // additional diagnostics, but not logspew
-
- // telephony
- boolean mHspaDataDistinguishable;
- final TelephonyManager mPhone;
- boolean mDataConnected;
- IccCardConstants.State mSimState = IccCardConstants.State.READY;
- int mPhoneState = TelephonyManager.CALL_STATE_IDLE;
- int mDataNetType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
- int mDataState = TelephonyManager.DATA_DISCONNECTED;
- int mDataActivity = TelephonyManager.DATA_ACTIVITY_NONE;
- ServiceState mServiceState;
- SignalStrength mSignalStrength;
- int[] mDataIconList = TelephonyIcons.DATA_G[0];
- String mNetworkName;
- String mNetworkNameDefault;
- String mNetworkNameSeparator;
- int mPhoneSignalIconId;
- int mQSPhoneSignalIconId;
- int mDataDirectionIconId; // data + data direction on phones
- int mDataSignalIconId;
- int mDataTypeIconId;
- int mQSDataTypeIconId;
- int mAirplaneIconId;
- boolean mDataActive;
- boolean mNoSim;
- int mLastSignalLevel;
- boolean mShowPhoneRSSIForData = false;
- boolean mShowAtLeastThreeGees = false;
- boolean mAlwaysShowCdmaRssi = false;
-
- String mContentDescriptionPhoneSignal;
- String mContentDescriptionWifi;
- String mContentDescriptionWimax;
- String mContentDescriptionCombinedSignal;
- String mContentDescriptionDataType;
-
- // wifi
- final WifiManager mWifiManager;
- AsyncChannel mWifiChannel;
- boolean mWifiEnabled, mWifiConnected;
- int mWifiRssi, mWifiLevel;
- String mWifiSsid;
- int mWifiIconId = 0;
- int mQSWifiIconId = 0;
- int mWifiActivity = WifiManager.DATA_ACTIVITY_NONE;
-
- // bluetooth
+ 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;
+
+ private final Context mContext;
+ private final TelephonyManager mPhone;
+ private final WifiManager mWifiManager;
+ private final ConnectivityManager mConnectivityManager;
+ private final SubscriptionManager mSubscriptionManager;
+ private final boolean mHasMobileDataFeature;
+ private Config mConfig;
+
+ // Subcontrollers.
+ @VisibleForTesting
+ final WifiSignalController mWifiSignalController;
+ @VisibleForTesting
+ final Map<Integer, MobileSignalController> mMobileSignalControllers =
+ new HashMap<Integer, MobileSignalController>();
+ // When no SIMs are around at setup, and one is added later, it seems to default to the first
+ // SIM for most actions. This may be null if there aren't any SIMs around.
+ private MobileSignalController mDefaultSignalController;
+ 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 int mBluetoothTetherIconId =
- com.android.internal.R.drawable.stat_sys_tether_bluetooth;
-
- //wimax
- private boolean mWimaxSupported = false;
- private boolean mIsWimaxEnabled = false;
- private boolean mWimaxConnected = false;
- private boolean mWimaxIdle = false;
- private int mWimaxIconId = 0;
- private int mWimaxSignal = 0;
- private int mWimaxState = 0;
- private int mWimaxExtraState = 0;
-
- // data connectivity (regardless of state, can we access the internet?)
- // state of inet connection - 0 not connected, 100 connected
+ private boolean mEthernetConnected = false;
+
+ // state of inet connection
private boolean mConnected = false;
- private int mConnectedNetworkType = ConnectivityManager.TYPE_NONE;
- private String mConnectedNetworkTypeName;
- private int mLastConnectedNetworkType = ConnectivityManager.TYPE_NONE;
+ private boolean mInetCondition; // Used for Logging and demo.
- private int mInetCondition = 0;
- private int mLastInetCondition = 0;
- private static final int INET_CONDITION_THRESHOLD = 50;
+ // BitSets indicating which network transport types (e.g., TRANSPORT_WIFI, TRANSPORT_MOBILE) are
+ // connected and validated, respectively.
+ private final BitSet mConnectedTransports = new BitSet();
+ private final BitSet mValidatedTransports = new BitSet();
+ // States that don't belong to a subcontroller.
private boolean mAirplaneMode = false;
- private boolean mLastAirplaneMode = true;
-
+ private boolean mHasNoSims;
private Locale mLocale = null;
- private Locale mLastLocale = null;
-
- // our ui
- Context mContext;
- ArrayList<TextView> mCombinedLabelViews = new ArrayList<TextView>();
- ArrayList<TextView> mMobileLabelViews = new ArrayList<TextView>();
- ArrayList<TextView> mWifiLabelViews = new ArrayList<TextView>();
- ArrayList<StatusBarHeaderView> mEmergencyViews = new ArrayList<>();
- ArrayList<SignalCluster> mSignalClusters = new ArrayList<SignalCluster>();
- ArrayList<NetworkSignalChangedCallback> mSignalsChangedCallbacks =
+ // 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>();
- int mLastPhoneSignalIconId = -1;
- int mLastDataDirectionIconId = -1;
- int mLastWifiIconId = -1;
- int mLastWimaxIconId = -1;
- int mLastCombinedSignalIconId = -1;
- int mLastDataTypeIconId = -1;
- String mLastCombinedLabel = "";
-
- private boolean mHasMobileDataFeature;
+ @VisibleForTesting
+ boolean mListening;
- boolean mDataAndWifiStacked = false;
-
- public interface SignalCluster {
- void setWifiIndicators(boolean visible, int strengthIcon, String contentDescription);
- void setMobileDataIndicators(boolean visible, int strengthIcon, int typeIcon,
- String contentDescription, String typeContentDescription, boolean roaming,
- boolean isTypeIconWide);
- void setIsAirplaneMode(boolean is, int airplaneIcon);
- }
-
- private final WifiAccessPointController mAccessPoints;
- private final MobileDataController mMobileDataController;
+ // The current user ID.
+ private int mCurrentUserId;
/**
* Construct this controller object and register for updates.
*/
public NetworkControllerImpl(Context context) {
- mContext = context;
- final Resources res = context.getResources();
-
- ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService(
- Context.CONNECTIVITY_SERVICE);
- mHasMobileDataFeature = cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
+ 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();
+ }
- mShowPhoneRSSIForData = res.getBoolean(R.bool.config_showPhoneRSSIForData);
- mShowAtLeastThreeGees = res.getBoolean(R.bool.config_showMin3G);
- mAlwaysShowCdmaRssi = res.getBoolean(
- com.android.internal.R.bool.config_alwaysUseCdmaRssi);
+ @VisibleForTesting
+ NetworkControllerImpl(Context context, ConnectivityManager connectivityManager,
+ TelephonyManager telephonyManager, WifiManager wifiManager,
+ SubscriptionManager subManager, Config config,
+ AccessPointControllerImpl accessPointController,
+ MobileDataControllerImpl mobileDataController) {
+ mContext = context;
+ mConfig = config;
- // set up the default wifi icon, used when no radios have ever appeared
- updateWifiIcons();
- updateWimaxIcons();
+ mSubscriptionManager = subManager;
+ mConnectivityManager = connectivityManager;
+ mHasMobileDataFeature =
+ mConnectivityManager.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
// telephony
- mPhone = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
- mPhone.listen(mPhoneStateListener,
- PhoneStateListener.LISTEN_SERVICE_STATE
- | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS
- | PhoneStateListener.LISTEN_CALL_STATE
- | PhoneStateListener.LISTEN_DATA_CONNECTION_STATE
- | PhoneStateListener.LISTEN_DATA_ACTIVITY);
- mHspaDataDistinguishable = mContext.getResources().getBoolean(
- R.bool.config_hspa_data_distinguishable);
- mNetworkNameSeparator = mContext.getString(R.string.status_bar_network_name_separator);
- mNetworkNameDefault = mContext.getString(
- com.android.internal.R.string.lockscreen_carrier_default);
- mNetworkName = mNetworkNameDefault;
+ mPhone = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
// wifi
- mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
- Handler handler = new WifiHandler();
- mWifiChannel = new AsyncChannel();
- Messenger wifiMessenger = mWifiManager.getWifiServiceMessenger();
- if (wifiMessenger != null) {
- mWifiChannel.connect(mContext, handler, wifiMessenger);
+ mWifiManager = wifiManager;
+
+ mLocale = mContext.getResources().getConfiguration().locale;
+ mAccessPoints = accessPointController;
+ mMobileDataController = mobileDataController;
+ mMobileDataController.setNetworkController(this);
+ // TODO: Find a way to move this into MobileDataController.
+ mMobileDataController.setCallback(new MobileDataControllerImpl.Callback() {
+ @Override
+ public void onMobileDataEnabled(boolean enabled) {
+ notifyMobileDataEnabled(enabled);
+ }
+ });
+ mWifiSignalController = new WifiSignalController(mContext, mHasMobileDataFeature,
+ mSignalsChangedCallbacks, mSignalClusters, 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();
}
+ mSubscriptionManager.addOnSubscriptionsChangedListener(mSubscriptionListener);
// broadcasts
IntentFilter filter = new IntentFilter();
@@ -222,37 +203,56 @@ public class NetworkControllerImpl extends BroadcastReceiver
filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
+ 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.INET_CONDITION_ACTION);
filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- mWimaxSupported = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_wimaxEnabled);
- if(mWimaxSupported) {
- filter.addAction(WimaxManagerConstants.WIMAX_NETWORK_STATE_CHANGED_ACTION);
- filter.addAction(WimaxManagerConstants.SIGNAL_LEVEL_CHANGED_ACTION);
- filter.addAction(WimaxManagerConstants.NET_4G_STATE_CHANGED_ACTION);
+ mContext.registerReceiver(this, filter);
+ mListening = true;
+
+ updateMobileControllers();
+ }
+
+ private void unregisterListeners() {
+ mListening = false;
+ for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
+ mobileSignalController.unregisterListener();
}
- context.registerReceiver(this, filter);
+ mSubscriptionManager.removeOnSubscriptionsChangedListener(mSubscriptionListener);
+ mContext.unregisterReceiver(this);
+ }
- // AIRPLANE_MODE_CHANGED is sent at boot; we've probably already missed it
- updateAirplaneMode();
+ public int getConnectedWifiLevel() {
+ return mWifiSignalController.getState().level;
+ }
- mLastLocale = mContext.getResources().getConfiguration().locale;
- mAccessPoints = new WifiAccessPointController(mContext);
- mMobileDataController = new MobileDataController(mContext);
- mMobileDataController.setCallback(new MobileDataController.Callback() {
- @Override
- public void onMobileDataEnabled(boolean enabled) {
- notifyMobileDataEnabled(enabled);
- }
- });
+ @Override
+ public AccessPointController getAccessPointController() {
+ return mAccessPoints;
+ }
+
+ @Override
+ public MobileDataController getMobileDataController() {
+ return mMobileDataController;
+ }
+
+ 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) {
- for (NetworkSignalChangedCallback cb : mSignalsChangedCallbacks) {
- cb.onMobileDataEnabled(enabled);
+ final int length = mSignalsChangedCallbacks.size();
+ for (int i = 0; i < length; i++) {
+ mSignalsChangedCallbacks.get(i).onMobileDataEnabled(enabled);
}
}
@@ -264,34 +264,77 @@ public class NetworkControllerImpl extends BroadcastReceiver
return mPhone.getPhoneType() != TelephonyManager.PHONE_TYPE_NONE;
}
- public boolean isEmergencyOnly() {
- return (mServiceState != null && mServiceState.isEmergencyOnly());
- }
-
- public void addCombinedLabelView(TextView v) {
- mCombinedLabelViews.add(v);
+ private MobileSignalController getDataController() {
+ int dataSubId = SubscriptionManager.getDefaultDataSubId();
+ if (!SubscriptionManager.isValidSubscriptionId(dataSubId)) {
+ if (DEBUG) Log.e(TAG, "No data sim selected");
+ return mDefaultSignalController;
+ }
+ if (mMobileSignalControllers.containsKey(dataSubId)) {
+ return mMobileSignalControllers.get(dataSubId);
+ }
+ if (DEBUG) Log.e(TAG, "Cannot find controller for data sub: " + dataSubId);
+ return mDefaultSignalController;
}
- public void addMobileLabelView(TextView v) {
- mMobileLabelViews.add(v);
+ public String getMobileNetworkName() {
+ MobileSignalController controller = getDataController();
+ return controller != null ? controller.getState().networkName : "";
}
- public void addWifiLabelView(TextView v) {
- mWifiLabelViews.add(v);
+ public boolean isEmergencyOnly() {
+ int voiceSubId = SubscriptionManager.getDefaultVoiceSubId();
+ if (!SubscriptionManager.isValidSubscriptionId(voiceSubId)) {
+ for (MobileSignalController mobileSignalController :
+ mMobileSignalControllers.values()) {
+ if (!mobileSignalController.isEmergencyOnly()) {
+ return false;
+ }
+ }
+ }
+ if (mMobileSignalControllers.containsKey(voiceSubId)) {
+ return mMobileSignalControllers.get(voiceSubId).isEmergencyOnly();
+ }
+ if (DEBUG) Log.e(TAG, "Cannot find controller for voice sub: " + voiceSubId);
+ // Something is wrong, better assume we can't make calls...
+ return true;
}
- public void addEmergencyLabelView(StatusBarHeaderView v) {
- mEmergencyViews.add(v);
+ /**
+ * Emergency status may have changed (triggered by MobileSignalController),
+ * 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);
- refreshSignalCluster(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();
+ }
}
public void addNetworkSignalChangedCallback(NetworkSignalChangedCallback cb) {
mSignalsChangedCallbacks.add(cb);
- notifySignalsChangedCallbacks(cb);
+ cb.onAirplaneModeChanged(mAirplaneMode);
+ cb.onNoSimVisibleChanged(mHasNoSims);
+ mWifiSignalController.notifyListeners();
+ for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
+ mobileSignalController.notifyListeners();
+ }
}
public void removeNetworkSignalChangedCallback(NetworkSignalChangedCallback cb) {
@@ -299,26 +342,6 @@ public class NetworkControllerImpl extends BroadcastReceiver
}
@Override
- public void addAccessPointCallback(AccessPointCallback callback) {
- mAccessPoints.addCallback(callback);
- }
-
- @Override
- public void removeAccessPointCallback(AccessPointCallback callback) {
- mAccessPoints.removeCallback(callback);
- }
-
- @Override
- public void scanForAccessPoints() {
- mAccessPoints.scan();
- }
-
- @Override
- public void connect(AccessPoint ap) {
- mAccessPoints.connect(ap);
- }
-
- @Override
public void setWifiEnabled(final boolean enabled) {
new AsyncTask<Void, Void, Void>() {
@Override
@@ -326,7 +349,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
// Disable tethering if enabling Wifi
final int wifiApState = mWifiManager.getWifiApState();
if (enabled && ((wifiApState == WifiManager.WIFI_AP_STATE_ENABLING) ||
- (wifiApState == WifiManager.WIFI_AP_STATE_ENABLED))) {
+ (wifiApState == WifiManager.WIFI_AP_STATE_ENABLED))) {
mWifiManager.setWifiApEnabled(null, false);
}
@@ -337,1280 +360,1464 @@ public class NetworkControllerImpl extends BroadcastReceiver
}
@Override
- public DataUsageInfo getDataUsageInfo() {
- final DataUsageInfo info = mMobileDataController.getDataUsageInfo();
- if (info != null) {
- info.carrier = mNetworkName;
- }
- return info;
- }
-
- @Override
- public boolean isMobileDataSupported() {
- return mMobileDataController.isMobileDataSupported();
- }
-
- @Override
- public boolean isMobileDataEnabled() {
- return mMobileDataController.isMobileDataEnabled();
+ public void onUserSwitched(int newUserId) {
+ mCurrentUserId = newUserId;
+ mAccessPoints.onUserSwitched(newUserId);
+ updateConnectivity();
+ refreshCarrierLabel();
}
@Override
- public void setMobileDataEnabled(boolean enabled) {
- mMobileDataController.setMobileDataEnabled(enabled);
- }
-
- private boolean isTypeIconWide(int iconId) {
- return TelephonyIcons.ICON_LTE == iconId || TelephonyIcons.ICON_1X == iconId
- || TelephonyIcons.ICON_3G == iconId || TelephonyIcons.ICON_4G == iconId;
- }
-
- private boolean isQsTypeIconWide(int iconId) {
- return TelephonyIcons.QS_ICON_LTE == iconId || TelephonyIcons.QS_ICON_1X == iconId
- || TelephonyIcons.QS_ICON_3G == iconId || TelephonyIcons.QS_ICON_4G == iconId;
- }
-
- public void refreshSignalCluster(SignalCluster cluster) {
- if (mDemoMode) return;
- cluster.setWifiIndicators(
- // only show wifi in the cluster if connected or if wifi-only
- mWifiEnabled && (mWifiConnected || !mHasMobileDataFeature),
- mWifiIconId,
- mContentDescriptionWifi);
-
- if (mIsWimaxEnabled && mWimaxConnected) {
- // wimax is special
- cluster.setMobileDataIndicators(
- true,
- mAlwaysShowCdmaRssi ? mPhoneSignalIconId : mWimaxIconId,
- mDataTypeIconId,
- mContentDescriptionWimax,
- mContentDescriptionDataType,
- mDataTypeIconId == TelephonyIcons.ROAMING_ICON,
- false /* isTypeIconWide */ );
- } else {
- // normal mobile data
- cluster.setMobileDataIndicators(
- mHasMobileDataFeature,
- mShowPhoneRSSIForData ? mPhoneSignalIconId : mDataSignalIconId,
- mDataTypeIconId,
- mContentDescriptionPhoneSignal,
- mContentDescriptionDataType,
- mDataTypeIconId == TelephonyIcons.ROAMING_ICON,
- isTypeIconWide(mDataTypeIconId));
- }
- cluster.setIsAirplaneMode(mAirplaneMode, mAirplaneIconId);
- }
-
- void notifySignalsChangedCallbacks(NetworkSignalChangedCallback cb) {
- // only show wifi in the cluster if connected or if wifi-only
- boolean wifiEnabled = mWifiEnabled && (mWifiConnected || !mHasMobileDataFeature);
- String wifiDesc = wifiEnabled ?
- mWifiSsid : null;
- boolean wifiIn = wifiEnabled && mWifiSsid != null
- && (mWifiActivity == WifiManager.DATA_ACTIVITY_INOUT
- || mWifiActivity == WifiManager.DATA_ACTIVITY_IN);
- boolean wifiOut = wifiEnabled && mWifiSsid != null
- && (mWifiActivity == WifiManager.DATA_ACTIVITY_INOUT
- || mWifiActivity == WifiManager.DATA_ACTIVITY_OUT);
- cb.onWifiSignalChanged(mWifiEnabled, mWifiConnected, mQSWifiIconId, wifiIn, wifiOut,
- mContentDescriptionWifi, wifiDesc);
-
- boolean mobileIn = mDataConnected && (mDataActivity == TelephonyManager.DATA_ACTIVITY_INOUT
- || mDataActivity == TelephonyManager.DATA_ACTIVITY_IN);
- boolean mobileOut = mDataConnected && (mDataActivity == TelephonyManager.DATA_ACTIVITY_INOUT
- || mDataActivity == TelephonyManager.DATA_ACTIVITY_OUT);
- if (isEmergencyOnly()) {
- cb.onMobileDataSignalChanged(false, mQSPhoneSignalIconId,
- mContentDescriptionPhoneSignal, mQSDataTypeIconId, mobileIn, mobileOut,
- mContentDescriptionDataType, null, mNoSim, isQsTypeIconWide(mQSDataTypeIconId));
+ public void onReceive(Context context, Intent intent) {
+ if (CHATTY) {
+ Log.d(TAG, "onReceive: intent=" + intent);
+ }
+ final String action = intent.getAction();
+ if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE) ||
+ 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();
+ } else if (action.equals(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)) {
+ // Notify every MobileSignalController so they can know whether they are the
+ // data sim or not.
+ for (MobileSignalController controller : mMobileSignalControllers.values()) {
+ controller.handleBroadcast(intent);
+ }
+ } else if (action.equals(TelephonyIntents.ACTION_SIM_STATE_CHANGED)) {
+ // Might have different subscriptions now.
+ updateMobileControllers();
} else {
- if (mIsWimaxEnabled && mWimaxConnected) {
- // Wimax is special
- cb.onMobileDataSignalChanged(true, mQSPhoneSignalIconId,
- mContentDescriptionPhoneSignal, mQSDataTypeIconId, mobileIn, mobileOut,
- mContentDescriptionDataType, mNetworkName, mNoSim,
- isQsTypeIconWide(mQSDataTypeIconId));
+ int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ if (SubscriptionManager.isValidSubscriptionId(subId)) {
+ if (mMobileSignalControllers.containsKey(subId)) {
+ mMobileSignalControllers.get(subId).handleBroadcast(intent);
+ } else {
+ // Can't find this subscription... We must be out of date.
+ updateMobileControllers();
+ }
} else {
- // Normal mobile data
- cb.onMobileDataSignalChanged(mHasMobileDataFeature, mQSPhoneSignalIconId,
- mContentDescriptionPhoneSignal, mQSDataTypeIconId, mobileIn, mobileOut,
- mContentDescriptionDataType, mNetworkName, mNoSim,
- isQsTypeIconWide(mQSDataTypeIconId));
+ // No sub id, must be for the wifi.
+ mWifiSignalController.handleBroadcast(intent);
}
}
- cb.onAirplaneModeChanged(mAirplaneMode);
}
- public void setStackedMode(boolean stacked) {
- mDataAndWifiStacked = true;
+ @VisibleForTesting
+ void handleConfigurationChanged() {
+ for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
+ mobileSignalController.setConfiguration(mConfig);
+ }
+ refreshLocale();
+ refreshCarrierLabel();
}
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- if (action.equals(WifiManager.RSSI_CHANGED_ACTION)
- || action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)
- || action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
- updateWifiState(intent);
- refreshViews();
- } else if (action.equals(TelephonyIntents.ACTION_SIM_STATE_CHANGED)) {
- updateSimState(intent);
- updateDataIcon();
- refreshViews();
- } else 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));
- refreshViews();
- } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE) ||
- action.equals(ConnectivityManager.INET_CONDITION_ACTION)) {
- updateConnectivity(intent);
- refreshViews();
- } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
- refreshLocale();
- refreshViews();
- } else if (action.equals(Intent.ACTION_AIRPLANE_MODE_CHANGED)) {
- refreshLocale();
- updateAirplaneMode();
- refreshViews();
- } else if (action.equals(WimaxManagerConstants.NET_4G_STATE_CHANGED_ACTION) ||
- action.equals(WimaxManagerConstants.SIGNAL_LEVEL_CHANGED_ACTION) ||
- action.equals(WimaxManagerConstants.WIMAX_NETWORK_STATE_CHANGED_ACTION)) {
- updateWimaxState(intent);
- refreshViews();
+ private void updateMobileControllers() {
+ if (!mListening) {
+ return;
+ }
+ List<SubscriptionInfo> subscriptions = mSubscriptionManager.getActiveSubscriptionInfoList();
+ if (subscriptions == null) {
+ subscriptions = Collections.emptyList();
}
+ // If there have been no relevant changes to any of the subscriptions, we can leave as is.
+ if (hasCorrectMobileControllers(subscriptions)) {
+ // Even if the controllers are correct, make sure we have the right no sims state.
+ // Such as on boot, don't need any controllers, because there are no sims,
+ // but we still need to update the no sim state.
+ updateNoSims();
+ return;
+ }
+ setCurrentSubscriptions(subscriptions);
+ updateNoSims();
}
-
- // ===== Telephony ==============================================================
-
- PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
- @Override
- public void onSignalStrengthsChanged(SignalStrength signalStrength) {
- if (DEBUG) {
- Log.d(TAG, "onSignalStrengthsChanged signalStrength=" + signalStrength +
- ((signalStrength == null) ? "" : (" level=" + signalStrength.getLevel())));
- }
- mSignalStrength = signalStrength;
- updateTelephonySignalStrength();
- refreshViews();
+ @VisibleForTesting
+ protected void updateNoSims() {
+ boolean hasNoSims = mHasMobileDataFeature && mMobileSignalControllers.size() == 0;
+ if (hasNoSims != mHasNoSims) {
+ mHasNoSims = hasNoSims;
+ notifyListeners();
}
+ }
- @Override
- public void onServiceStateChanged(ServiceState state) {
- if (DEBUG) {
- Log.d(TAG, "onServiceStateChanged voiceState=" + state.getVoiceRegState()
- + " dataState=" + state.getDataRegState());
+ @VisibleForTesting
+ void setCurrentSubscriptions(List<SubscriptionInfo> subscriptions) {
+ Collections.sort(subscriptions, new Comparator<SubscriptionInfo>() {
+ @Override
+ public int compare(SubscriptionInfo lhs, SubscriptionInfo rhs) {
+ return lhs.getSimSlotIndex() == rhs.getSimSlotIndex()
+ ? lhs.getSubscriptionId() - rhs.getSubscriptionId()
+ : lhs.getSimSlotIndex() - rhs.getSimSlotIndex();
}
- mServiceState = state;
- updateTelephonySignalStrength();
- updateDataNetType();
- updateDataIcon();
- refreshViews();
- }
-
- @Override
- public void onCallStateChanged(int state, String incomingNumber) {
- if (DEBUG) {
- Log.d(TAG, "onCallStateChanged state=" + state);
+ });
+ final int length = mSignalClusters.size();
+ for (int i = 0; i < length; i++) {
+ mSignalClusters.get(i).setSubs(subscriptions);
+ }
+ mCurrentSubscriptions = subscriptions;
+
+ HashMap<Integer, MobileSignalController> cachedControllers =
+ new HashMap<Integer, MobileSignalController>(mMobileSignalControllers);
+ mMobileSignalControllers.clear();
+ final int num = subscriptions.size();
+ for (int i = 0; i < num; i++) {
+ int subId = subscriptions.get(i).getSubscriptionId();
+ // If we have a copy of this controller already reuse it, otherwise make a new one.
+ if (cachedControllers.containsKey(subId)) {
+ mMobileSignalControllers.put(subId, cachedControllers.remove(subId));
+ } else {
+ MobileSignalController controller = new MobileSignalController(mContext, mConfig,
+ mHasMobileDataFeature, mPhone, mSignalsChangedCallbacks, mSignalClusters,
+ this, subscriptions.get(i));
+ mMobileSignalControllers.put(subId, controller);
+ if (subscriptions.get(i).getSimSlotIndex() == 0) {
+ mDefaultSignalController = controller;
+ }
+ if (mListening) {
+ controller.registerListener();
+ }
}
- // In cdma, if a voice call is made, RSSI should switch to 1x.
- if (isCdma()) {
- updateTelephonySignalStrength();
- refreshViews();
+ }
+ if (mListening) {
+ for (Integer key : cachedControllers.keySet()) {
+ if (cachedControllers.get(key) == mDefaultSignalController) {
+ mDefaultSignalController = null;
+ }
+ cachedControllers.get(key).unregisterListener();
}
}
+ // There may be new MobileSignalControllers around, make sure they get the current
+ // inet condition and airplane mode.
+ pushConnectivityToSignals();
+ updateAirplaneMode(true /* force */);
+ }
- @Override
- public void onDataConnectionStateChanged(int state, int networkType) {
- if (DEBUG) {
- Log.d(TAG, "onDataConnectionStateChanged: state=" + state
- + " type=" + networkType);
+ @VisibleForTesting
+ boolean hasCorrectMobileControllers(List<SubscriptionInfo> allSubscriptions) {
+ if (allSubscriptions.size() != mMobileSignalControllers.size()) {
+ return false;
+ }
+ for (SubscriptionInfo info : allSubscriptions) {
+ if (!mMobileSignalControllers.containsKey(info.getSubscriptionId())) {
+ return false;
}
- mDataState = state;
- mDataNetType = networkType;
- updateDataNetType();
- updateDataIcon();
- refreshViews();
}
+ return true;
+ }
- @Override
- public void onDataActivity(int direction) {
- if (DEBUG) {
- Log.d(TAG, "onDataActivity: direction=" + direction);
+ private void updateAirplaneMode(boolean force) {
+ boolean airplaneMode = (Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.AIRPLANE_MODE_ON, 0) == 1);
+ if (airplaneMode != mAirplaneMode || force) {
+ mAirplaneMode = airplaneMode;
+ for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
+ mobileSignalController.setAirplaneMode(mAirplaneMode);
}
- mDataActivity = direction;
- updateDataIcon();
- refreshViews();
+ notifyListeners();
+ refreshCarrierLabel();
}
- };
+ }
- private final void updateSimState(Intent intent) {
- String stateExtra = intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE);
- if (IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(stateExtra)) {
- mSimState = IccCardConstants.State.ABSENT;
- }
- else if (IccCardConstants.INTENT_VALUE_ICC_READY.equals(stateExtra)) {
- mSimState = IccCardConstants.State.READY;
+ private void refreshLocale() {
+ Locale current = mContext.getResources().getConfiguration().locale;
+ if (!current.equals(mLocale)) {
+ mLocale = current;
+ notifyAllListeners();
}
- else if (IccCardConstants.INTENT_VALUE_ICC_LOCKED.equals(stateExtra)) {
- final String lockedReason =
- intent.getStringExtra(IccCardConstants.INTENT_KEY_LOCKED_REASON);
- if (IccCardConstants.INTENT_VALUE_LOCKED_ON_PIN.equals(lockedReason)) {
- mSimState = IccCardConstants.State.PIN_REQUIRED;
- }
- else if (IccCardConstants.INTENT_VALUE_LOCKED_ON_PUK.equals(lockedReason)) {
- mSimState = IccCardConstants.State.PUK_REQUIRED;
- }
- else {
- mSimState = IccCardConstants.State.NETWORK_LOCKED;
- }
- } else {
- mSimState = IccCardConstants.State.UNKNOWN;
+ }
+
+ /**
+ * Forces update of all callbacks on both SignalClusters and
+ * NetworkSignalChangedCallbacks.
+ */
+ private void notifyAllListeners() {
+ notifyListeners();
+ for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
+ mobileSignalController.notifyListeners();
}
- if (DEBUG) Log.d(TAG, "updateSimState: mSimState=" + mSimState);
+ mWifiSignalController.notifyListeners();
}
- private boolean isCdma() {
- return (mSignalStrength != null) && !mSignalStrength.isGsm();
+ /**
+ * Notifies listeners of changes in state of to the NetworkController, but
+ * does not notify for any info on SignalControllers, for that call
+ * 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);
+ }
}
- private boolean hasService() {
- boolean retVal;
- 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:
- retVal = false;
- break;
- case ServiceState.STATE_OUT_OF_SERVICE:
- case ServiceState.STATE_EMERGENCY_ONLY:
- retVal = mServiceState.getDataRegState() == ServiceState.STATE_IN_SERVICE;
- break;
- default:
- retVal = true;
+ /**
+ * Update the Inet conditions and what network we are connected to.
+ */
+ private void updateConnectivity() {
+ mConnectedTransports.clear();
+ mValidatedTransports.clear();
+ for (NetworkCapabilities nc :
+ mConnectivityManager.getDefaultNetworkCapabilitiesForUser(mCurrentUserId)) {
+ for (int transportType : nc.getTransportTypes()) {
+ mConnectedTransports.set(transportType);
+ if (nc.hasCapability(NET_CAPABILITY_VALIDATED)) {
+ mValidatedTransports.set(transportType);
+ }
}
- } else {
- retVal = false;
}
- if (DEBUG) Log.d(TAG, "hasService: mServiceState=" + mServiceState + " retVal=" + retVal);
- return retVal;
- }
- private void updateAirplaneMode() {
- mAirplaneMode = (Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.AIRPLANE_MODE_ON, 0) == 1);
+ if (CHATTY) {
+ Log.d(TAG, "updateConnectivity: mConnectedTransports=" + mConnectedTransports);
+ Log.d(TAG, "updateConnectivity: mValidatedTransports=" + mValidatedTransports);
+ }
+
+ mConnected = !mConnectedTransports.isEmpty();
+ mInetCondition = !mValidatedTransports.isEmpty();
+ mBluetoothTethered = mConnectedTransports.get(TRANSPORT_BLUETOOTH);
+ mEthernetConnected = mConnectedTransports.get(TRANSPORT_ETHERNET);
+
+ pushConnectivityToSignals();
}
- private void refreshLocale() {
- mLocale = mContext.getResources().getConfiguration().locale;
+ /**
+ * Pushes the current connectivity state to all SignalControllers.
+ */
+ 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);
}
- private final void updateTelephonySignalStrength() {
- if (DEBUG) {
- Log.d(TAG, "updateTelephonySignalStrength: hasService=" + hasService()
- + " ss=" + mSignalStrength);
- }
- if (!hasService()) {
- if (CHATTY) Log.d(TAG, "updateTelephonySignalStrength: !hasService()");
- mPhoneSignalIconId = R.drawable.stat_sys_signal_null;
- mQSPhoneSignalIconId = R.drawable.ic_qs_signal_no_signal;
- mDataSignalIconId = R.drawable.stat_sys_signal_null;
- mContentDescriptionPhoneSignal = mContext.getString(
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0]);
- } else {
- if (mSignalStrength == null) {
- if (CHATTY) Log.d(TAG, "updateTelephonySignalStrength: mSignalStrength == null");
- mPhoneSignalIconId = R.drawable.stat_sys_signal_null;
- mQSPhoneSignalIconId = R.drawable.ic_qs_signal_no_signal;
- mDataSignalIconId = R.drawable.stat_sys_signal_null;
- mContentDescriptionPhoneSignal = mContext.getString(
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0]);
- } else {
- int iconLevel;
- int[] iconList;
- if (isCdma() && mAlwaysShowCdmaRssi) {
- mLastSignalLevel = iconLevel = mSignalStrength.getCdmaLevel();
- if (DEBUG) {
- Log.d(TAG, "updateTelephonySignalStrength:"
- + " mAlwaysShowCdmaRssi=" + mAlwaysShowCdmaRssi
- + " set to cdmaLevel=" + mSignalStrength.getCdmaLevel()
- + " instead of level=" + mSignalStrength.getLevel());
- }
- } else {
- mLastSignalLevel = iconLevel = mSignalStrength.getLevel();
- }
+ /**
+ * Recalculate and update the carrier label.
+ */
+ void refreshCarrierLabel() {
+ Context context = mContext;
- if (isRoaming()) {
- iconList = TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH_ROAMING[mInetCondition];
- } else {
- iconList = TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH[mInetCondition];
- }
- mPhoneSignalIconId = iconList[iconLevel];
- mQSPhoneSignalIconId =
- TelephonyIcons.QS_TELEPHONY_SIGNAL_STRENGTH[mInetCondition][iconLevel];
- mContentDescriptionPhoneSignal = mContext.getString(
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[iconLevel]);
- mDataSignalIconId = TelephonyIcons.DATA_SIGNAL_STRENGTH[mInetCondition][iconLevel];
- if (DEBUG) Log.d(TAG, "updateTelephonySignalStrength: iconLevel=" + iconLevel);
- }
+ WifiSignalController.WifiState wifiState = mWifiSignalController.getState();
+ String label = "";
+ for (MobileSignalController controller : mMobileSignalControllers.values()) {
+ label = controller.getLabel(label, mConnected, mHasMobileDataFeature);
}
- }
- private int inetConditionForNetwork(int networkType) {
- return (mInetCondition == 1 && mConnectedNetworkType == networkType) ? 1 : 0;
- }
+ // 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);
+ }
- private final void updateDataNetType() {
- int inetCondition;
- mDataTypeIconId = mQSDataTypeIconId = 0;
- if (mIsWimaxEnabled && mWimaxConnected) {
- // wimax is a special 4g network not handled by telephony
- inetCondition = inetConditionForNetwork(ConnectivityManager.TYPE_WIMAX);
- mDataIconList = TelephonyIcons.DATA_4G[inetCondition];
- mDataTypeIconId = R.drawable.stat_sys_data_fully_connected_4g;
- mQSDataTypeIconId = TelephonyIcons.QS_DATA_4G[inetCondition];
- mContentDescriptionDataType = mContext.getString(
- R.string.accessibility_data_connection_4g);
- } else {
- inetCondition = inetConditionForNetwork(ConnectivityManager.TYPE_MOBILE);
- final boolean showDataTypeIcon = (inetCondition > 0);
- switch (mDataNetType) {
- case TelephonyManager.NETWORK_TYPE_UNKNOWN:
- if (!mShowAtLeastThreeGees) {
- mDataIconList = TelephonyIcons.DATA_G[inetCondition];
- mContentDescriptionDataType = "";
- break;
- } else {
- // fall through
- }
- case TelephonyManager.NETWORK_TYPE_EDGE:
- if (!mShowAtLeastThreeGees) {
- mDataIconList = TelephonyIcons.DATA_E[inetCondition];
- mDataTypeIconId = showDataTypeIcon ?
- R.drawable.stat_sys_data_fully_connected_e : 0;
- mQSDataTypeIconId = TelephonyIcons.QS_DATA_E[inetCondition];
- mContentDescriptionDataType = mContext.getString(
- R.string.accessibility_data_connection_edge);
- break;
- } else {
- // fall through
- }
- case TelephonyManager.NETWORK_TYPE_UMTS:
- mDataIconList = TelephonyIcons.DATA_3G[inetCondition];
- mDataTypeIconId = showDataTypeIcon ?
- R.drawable.stat_sys_data_fully_connected_3g : 0;
- mQSDataTypeIconId = TelephonyIcons.QS_DATA_3G[inetCondition];
- mContentDescriptionDataType = mContext.getString(
- R.string.accessibility_data_connection_3g);
- break;
- case TelephonyManager.NETWORK_TYPE_HSDPA:
- case TelephonyManager.NETWORK_TYPE_HSUPA:
- case TelephonyManager.NETWORK_TYPE_HSPA:
- case TelephonyManager.NETWORK_TYPE_HSPAP:
- if (mHspaDataDistinguishable) {
- mDataIconList = TelephonyIcons.DATA_H[inetCondition];
- mDataTypeIconId = showDataTypeIcon ?
- R.drawable.stat_sys_data_fully_connected_h : 0;
- mQSDataTypeIconId = TelephonyIcons.QS_DATA_H[inetCondition];
- mContentDescriptionDataType = mContext.getString(
- R.string.accessibility_data_connection_3_5g);
- } else {
- mDataIconList = TelephonyIcons.DATA_3G[inetCondition];
- mDataTypeIconId = showDataTypeIcon ?
- R.drawable.stat_sys_data_fully_connected_3g : 0;
- mQSDataTypeIconId = TelephonyIcons.QS_DATA_3G[inetCondition];
- mContentDescriptionDataType = mContext.getString(
- R.string.accessibility_data_connection_3g);
- }
- break;
- case TelephonyManager.NETWORK_TYPE_CDMA:
- if (!mShowAtLeastThreeGees) {
- // display 1xRTT for IS95A/B
- mDataIconList = TelephonyIcons.DATA_1X[inetCondition];
- mDataTypeIconId = showDataTypeIcon ?
- R.drawable.stat_sys_data_fully_connected_1x : 0;
- mQSDataTypeIconId = TelephonyIcons.QS_DATA_1X[inetCondition];
- mContentDescriptionDataType = mContext.getString(
- R.string.accessibility_data_connection_cdma);
- break;
- } else {
- // fall through
- }
- case TelephonyManager.NETWORK_TYPE_1xRTT:
- if (!mShowAtLeastThreeGees) {
- mDataIconList = TelephonyIcons.DATA_1X[inetCondition];
- mDataTypeIconId = showDataTypeIcon ?
- R.drawable.stat_sys_data_fully_connected_1x : 0;
- mQSDataTypeIconId = TelephonyIcons.QS_DATA_1X[inetCondition];
- mContentDescriptionDataType = mContext.getString(
- R.string.accessibility_data_connection_cdma);
- break;
- } else {
- // fall through
- }
- case TelephonyManager.NETWORK_TYPE_EVDO_0: //fall through
- case TelephonyManager.NETWORK_TYPE_EVDO_A:
- case TelephonyManager.NETWORK_TYPE_EVDO_B:
- case TelephonyManager.NETWORK_TYPE_EHRPD:
- mDataIconList = TelephonyIcons.DATA_3G[inetCondition];
- mDataTypeIconId = showDataTypeIcon ?
- R.drawable.stat_sys_data_fully_connected_3g : 0;
- mQSDataTypeIconId = TelephonyIcons.QS_DATA_3G[inetCondition];
- mContentDescriptionDataType = mContext.getString(
- R.string.accessibility_data_connection_3g);
- break;
- case TelephonyManager.NETWORK_TYPE_LTE:
- boolean show4GforLTE = mContext.getResources().getBoolean(R.bool.config_show4GForLTE);
- if (show4GforLTE) {
- mDataIconList = TelephonyIcons.DATA_4G[inetCondition];
- mDataTypeIconId = showDataTypeIcon ?
- R.drawable.stat_sys_data_fully_connected_4g : 0;
- mQSDataTypeIconId = TelephonyIcons.QS_DATA_4G[inetCondition];
- mContentDescriptionDataType = mContext.getString(
- R.string.accessibility_data_connection_4g);
- } else {
- mDataIconList = TelephonyIcons.DATA_LTE[inetCondition];
- mDataTypeIconId = showDataTypeIcon ? TelephonyIcons.ICON_LTE : 0;
- mQSDataTypeIconId = TelephonyIcons.QS_DATA_LTE[inetCondition];
- mContentDescriptionDataType = mContext.getString(
- R.string.accessibility_data_connection_lte);
- }
- break;
- default:
- if (!mShowAtLeastThreeGees) {
- mDataIconList = TelephonyIcons.DATA_G[inetCondition];
- mDataTypeIconId = showDataTypeIcon ?
- R.drawable.stat_sys_data_fully_connected_g : 0;
- mQSDataTypeIconId = TelephonyIcons.QS_DATA_G[inetCondition];
- mContentDescriptionDataType = mContext.getString(
- R.string.accessibility_data_connection_gprs);
- } else {
- mDataIconList = TelephonyIcons.DATA_3G[inetCondition];
- mDataTypeIconId = showDataTypeIcon ?
- R.drawable.stat_sys_data_fully_connected_3g : 0;
- mQSDataTypeIconId = TelephonyIcons.QS_DATA_3G[inetCondition];
- mContentDescriptionDataType = mContext.getString(
- R.string.accessibility_data_connection_3g);
- }
- break;
+ 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);
}
- if (isRoaming()) {
- mDataTypeIconId = TelephonyIcons.ROAMING_ICON;
- mQSDataTypeIconId = TelephonyIcons.QS_DATA_R[mInetCondition];
+ // 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);
}
}
- boolean isCdmaEri() {
- if (mServiceState != null) {
- final int iconIndex = mServiceState.getCdmaEriIconIndex();
- if (iconIndex != EriInfo.ROAMING_INDICATOR_OFF) {
- final int iconMode = mServiceState.getCdmaEriIconMode();
- if (iconMode == EriInfo.ROAMING_ICON_MODE_NORMAL
- || iconMode == EriInfo.ROAMING_ICON_MODE_FLASH) {
- return true;
- }
- }
- }
- return false;
+ private boolean isMobileDataConnected() {
+ MobileSignalController controller = getDataController();
+ return controller != null ? controller.getState().dataConnected : false;
}
- private boolean isRoaming() {
- if (isCdma()) {
- return isCdmaEri();
- } else {
- return mServiceState != null && mServiceState.getRoaming();
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("NetworkController state:");
+
+ pw.println(" - telephony ------");
+ pw.print(" hasVoiceCallingFeature()=");
+ pw.println(hasVoiceCallingFeature());
+
+ pw.println(" - Bluetooth ----");
+ pw.print(" mBtReverseTethered=");
+ pw.println(mBluetoothTethered);
+
+ pw.println(" - connectivity ------");
+ pw.print(" mConnectedTransports=");
+ pw.println(mConnectedTransports);
+ pw.print(" mValidatedTransports=");
+ pw.println(mValidatedTransports);
+ pw.print(" mInetCondition=");
+ pw.println(mInetCondition);
+ pw.print(" mAirplaneMode=");
+ pw.println(mAirplaneMode);
+ pw.print(" mLocale=");
+ pw.println(mLocale);
+
+ for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
+ mobileSignalController.dump(pw);
}
+ mWifiSignalController.dump(pw);
}
- private final void updateDataIcon() {
- int iconId;
- boolean visible = true;
-
- if (!isCdma()) {
- // GSM case, we have to check also the sim state
- if (mSimState == IccCardConstants.State.READY ||
- mSimState == IccCardConstants.State.UNKNOWN) {
- mNoSim = false;
- if (hasService() && mDataState == TelephonyManager.DATA_CONNECTED) {
- switch (mDataActivity) {
- case TelephonyManager.DATA_ACTIVITY_IN:
- iconId = mDataIconList[1];
- break;
- case TelephonyManager.DATA_ACTIVITY_OUT:
- iconId = mDataIconList[2];
- break;
- case TelephonyManager.DATA_ACTIVITY_INOUT:
- iconId = mDataIconList[3];
- break;
- default:
- iconId = mDataIconList[0];
- break;
+ private boolean mDemoMode;
+ private int mDemoInetCondition;
+ private WifiSignalController.WifiState mDemoWifiState;
+
+ @Override
+ public void dispatchDemoCommand(String command, Bundle args) {
+ if (!mDemoMode && command.equals(COMMAND_ENTER)) {
+ if (DEBUG) Log.d(TAG, "Entering demo mode");
+ unregisterListeners();
+ mDemoMode = true;
+ mDemoInetCondition = mInetCondition ? 1 : 0;
+ mDemoWifiState = mWifiSignalController.getState();
+ } else if (mDemoMode && command.equals(COMMAND_EXIT)) {
+ if (DEBUG) Log.d(TAG, "Exiting demo mode");
+ mDemoMode = false;
+ // Update what MobileSignalControllers, because they may change
+ // to set the number of sim slots.
+ updateMobileControllers();
+ for (MobileSignalController controller : mMobileSignalControllers.values()) {
+ controller.resetLastState();
+ }
+ mWifiSignalController.resetLastState();
+ registerListeners();
+ 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);
+ }
+ }
+ String fully = args.getString("fully");
+ if (fully != null) {
+ mDemoInetCondition = Boolean.parseBoolean(fully) ? 1 : 0;
+ mWifiSignalController.setInetCondition(mDemoInetCondition);
+ for (MobileSignalController controller : mMobileSignalControllers.values()) {
+ controller.setInetCondition(mDemoInetCondition, mDemoInetCondition);
+ }
+ }
+ String wifi = args.getString("wifi");
+ if (wifi != null) {
+ boolean show = wifi.equals("show");
+ String level = args.getString("level");
+ if (level != null) {
+ mDemoWifiState.level = level.equals("null") ? -1
+ : Math.min(Integer.parseInt(level), WifiIcons.WIFI_LEVEL_COUNT - 1);
+ mDemoWifiState.connected = mDemoWifiState.level >= 0;
+ }
+ mDemoWifiState.enabled = show;
+ mWifiSignalController.notifyListeners();
+ }
+ String sims = args.getString("sims");
+ if (sims != null) {
+ int num = Integer.parseInt(sims);
+ List<SubscriptionInfo> subs = new ArrayList<SubscriptionInfo>();
+ 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));
}
- mDataDirectionIconId = iconId;
- } else {
- iconId = 0;
- visible = false;
}
- } else {
- iconId = 0;
- mNoSim = true;
- visible = false; // no SIM? no data
+ final int n = mSignalClusters.size();
+ for (int i = 0; i < n; i++) {
+ mSignalClusters.get(i).setSubs(subs);
+ }
}
- } else {
- // CDMA case, mDataActivity can be also DATA_ACTIVITY_DORMANT
- if (hasService() && mDataState == TelephonyManager.DATA_CONNECTED) {
- switch (mDataActivity) {
- case TelephonyManager.DATA_ACTIVITY_IN:
- iconId = mDataIconList[1];
- break;
- case TelephonyManager.DATA_ACTIVITY_OUT:
- iconId = mDataIconList[2];
- break;
- case TelephonyManager.DATA_ACTIVITY_INOUT:
- iconId = mDataIconList[3];
- break;
- case TelephonyManager.DATA_ACTIVITY_DORMANT:
- default:
- iconId = mDataIconList[0];
- break;
+ 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);
}
- } else {
- iconId = 0;
- visible = false;
}
+ String mobile = args.getString("mobile");
+ if (mobile != null) {
+ boolean show = mobile.equals("show");
+ String datatype = args.getString("datatype");
+ String slotString = args.getString("slot");
+ int slot = TextUtils.isEmpty(slotString) ? 0 : Integer.parseInt(slotString);
+ // Hack to index linearly for easy use.
+ MobileSignalController controller = mMobileSignalControllers
+ .values().toArray(new MobileSignalController[0])[slot];
+ controller.getState().dataSim = datatype != null;
+ if (datatype != null) {
+ controller.getState().iconGroup =
+ datatype.equals("1x") ? TelephonyIcons.ONE_X :
+ datatype.equals("3g") ? TelephonyIcons.THREE_G :
+ datatype.equals("4g") ? TelephonyIcons.FOUR_G :
+ datatype.equals("e") ? TelephonyIcons.E :
+ datatype.equals("g") ? TelephonyIcons.G :
+ datatype.equals("h") ? TelephonyIcons.H :
+ datatype.equals("lte") ? TelephonyIcons.LTE :
+ datatype.equals("roam") ? TelephonyIcons.ROAMING :
+ TelephonyIcons.UNKNOWN;
+ }
+ int[][] icons = TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH;
+ String level = args.getString("level");
+ if (level != null) {
+ controller.getState().level = level.equals("null") ? -1
+ : Math.min(Integer.parseInt(level), icons[0].length - 1);
+ controller.getState().connected = controller.getState().level >= 0;
+ }
+ controller.getState().enabled = show;
+ controller.notifyListeners();
+ }
+ refreshCarrierLabel();
}
-
- mDataDirectionIconId = iconId;
- mDataConnected = visible;
}
- void updateNetworkName(boolean showSpn, String spn, boolean showPlmn, String plmn) {
- if (false) {
- Log.d("CarrierLabel", "updateNetworkName showSpn=" + showSpn + " spn=" + spn
- + " showPlmn=" + showPlmn + " plmn=" + plmn);
- }
- StringBuilder str = new StringBuilder();
- boolean something = false;
- if (showPlmn && plmn != null) {
- str.append(plmn);
- something = true;
- }
- if (showSpn && spn != null) {
- if (something) {
- str.append(mNetworkNameSeparator);
+ 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);
}
- str.append(spn);
- something = true;
- }
- if (something) {
- mNetworkName = str.toString();
- } else {
- mNetworkName = mNetworkNameDefault;
+ // 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
+ );
}
- }
- // ===== Wifi ===================================================================
+ @Override
+ protected WifiState cleanState() {
+ return new WifiState();
+ }
- 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(TAG, "Failed to connect to wifi");
- }
- break;
- case WifiManager.DATA_ACTIVITY_NOTIFICATION:
- if (msg.arg1 != mWifiActivity) {
- mWifiActivity = msg.arg1;
- refreshViews();
- }
- break;
- default:
- //Ignore
- break;
+ 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);
}
}
- }
- private void updateWifiState(Intent intent) {
- final String action = intent.getAction();
- if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
- mWifiEnabled = 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);
- boolean wasConnected = mWifiConnected;
- mWifiConnected = networkInfo != null && networkInfo.isConnected();
- // If Connected grab the signal strength and ssid
- if (mWifiConnected) {
- // try getting it out of the intent first
- WifiInfo info = (WifiInfo) intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO);
- if (info == null) {
- info = mWifiManager.getConnectionInfo();
- }
- if (info != null) {
- mWifiSsid = huntForSsid(info);
- } else {
- mWifiSsid = null;
+ /**
+ * 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 (!mWifiConnected) {
- mWifiSsid = 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);
}
- } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) {
- mWifiRssi = intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200);
- mWifiLevel = WifiManager.calculateSignalLevel(
- mWifiRssi, WifiIcons.WIFI_LEVEL_COUNT);
- }
- updateWifiIcons();
- }
+ notifyListenersIfNecessary();
+ }
- private void updateWifiIcons() {
- int inetCondition = inetConditionForNetwork(ConnectivityManager.TYPE_WIFI);
- if (mWifiConnected) {
- mWifiIconId = WifiIcons.WIFI_SIGNAL_STRENGTH[inetCondition][mWifiLevel];
- mQSWifiIconId = WifiIcons.QS_WIFI_SIGNAL_STRENGTH[inetCondition][mWifiLevel];
- mContentDescriptionWifi = mContext.getString(
- AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH[mWifiLevel]);
- } else {
- if (mDataAndWifiStacked) {
- mWifiIconId = 0;
- mQSWifiIconId = 0;
- } else {
- mWifiIconId = mWifiEnabled ? R.drawable.stat_sys_wifi_signal_null : 0;
- mQSWifiIconId = mWifiEnabled ? R.drawable.ic_qs_wifi_no_network : 0;
+ 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;
+ }
}
- mContentDescriptionWifi = mContext.getString(R.string.accessibility_no_wifi);
+ return null;
}
- }
- private String huntForSsid(WifiInfo info) {
- String ssid = info.getSSID();
- if (ssid != null) {
- return ssid;
+ @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();
}
- // OK, it's not in the connectionInfo; we have to go hunting for it
- List<WifiConfiguration> networks = mWifiManager.getConfiguredNetworks();
- for (WifiConfiguration net : networks) {
- if (net.networkId == info.getNetworkId()) {
- return net.SSID;
+
+ /**
+ * 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;
+ }
}
}
- return null;
- }
+ static class WifiState extends SignalController.State {
+ String ssid;
- // ===== Wimax ===================================================================
- private final void updateWimaxState(Intent intent) {
- final String action = intent.getAction();
- boolean wasConnected = mWimaxConnected;
- if (action.equals(WimaxManagerConstants.NET_4G_STATE_CHANGED_ACTION)) {
- int wimaxStatus = intent.getIntExtra(WimaxManagerConstants.EXTRA_4G_STATE,
- WimaxManagerConstants.NET_4G_STATE_UNKNOWN);
- mIsWimaxEnabled = (wimaxStatus ==
- WimaxManagerConstants.NET_4G_STATE_ENABLED);
- } else if (action.equals(WimaxManagerConstants.SIGNAL_LEVEL_CHANGED_ACTION)) {
- mWimaxSignal = intent.getIntExtra(WimaxManagerConstants.EXTRA_NEW_SIGNAL_LEVEL, 0);
- } else if (action.equals(WimaxManagerConstants.WIMAX_NETWORK_STATE_CHANGED_ACTION)) {
- mWimaxState = intent.getIntExtra(WimaxManagerConstants.EXTRA_WIMAX_STATE,
- WimaxManagerConstants.NET_4G_STATE_UNKNOWN);
- mWimaxExtraState = intent.getIntExtra(
- WimaxManagerConstants.EXTRA_WIMAX_STATE_DETAIL,
- WimaxManagerConstants.NET_4G_STATE_UNKNOWN);
- mWimaxConnected = (mWimaxState ==
- WimaxManagerConstants.WIMAX_STATE_CONNECTED);
- mWimaxIdle = (mWimaxExtraState == WimaxManagerConstants.WIMAX_IDLE);
- }
- updateDataNetType();
- updateWimaxIcons();
- }
+ @Override
+ public void copyFrom(State s) {
+ super.copyFrom(s);
+ WifiState state = (WifiState) s;
+ ssid = state.ssid;
+ }
- private void updateWimaxIcons() {
- if (mIsWimaxEnabled) {
- if (mWimaxConnected) {
- int inetCondition = inetConditionForNetwork(ConnectivityManager.TYPE_WIMAX);
- if (mWimaxIdle)
- mWimaxIconId = WimaxIcons.WIMAX_IDLE;
- else
- mWimaxIconId = WimaxIcons.WIMAX_SIGNAL_STRENGTH[inetCondition][mWimaxSignal];
- mContentDescriptionWimax = mContext.getString(
- AccessibilityContentDescriptions.WIMAX_CONNECTION_STRENGTH[mWimaxSignal]);
- } else {
- mWimaxIconId = WimaxIcons.WIMAX_DISCONNECTED;
- mContentDescriptionWimax = mContext.getString(R.string.accessibility_no_wimax);
+ @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);
}
- } else {
- mWimaxIconId = 0;
}
}
- // ===== Full or limited Internet connectivity ==================================
+ // 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);
+ }
- private void updateConnectivity(Intent intent) {
- if (CHATTY) {
- Log.d(TAG, "updateConnectivity: intent=" + intent);
+ 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);
+ }
+ }
}
- final ConnectivityManager connManager = (ConnectivityManager) mContext
- .getSystemService(Context.CONNECTIVITY_SERVICE);
- final NetworkInfo info = connManager.getActiveNetworkInfo();
-
- // Are we connected at all, by any interface?
- mConnected = info != null && info.isConnected();
- if (mConnected) {
- mConnectedNetworkType = info.getType();
- mConnectedNetworkTypeName = info.getTypeName();
- } else {
- mConnectedNetworkType = ConnectivityManager.TYPE_NONE;
- mConnectedNetworkTypeName = null;
+ public int getDataContentDescription() {
+ return getIcons().mDataContentDescription;
}
- int connectionStatus = intent.getIntExtra(ConnectivityManager.EXTRA_INET_CONDITION, 0);
-
- if (CHATTY) {
- Log.d(TAG, "updateConnectivity: networkInfo=" + info);
- Log.d(TAG, "updateConnectivity: connectionStatus=" + connectionStatus);
+ public void setAirplaneMode(boolean airplaneMode) {
+ mCurrentState.airplaneMode = airplaneMode;
+ notifyListenersIfNecessary();
}
- mInetCondition = (connectionStatus > INET_CONDITION_THRESHOLD ? 1 : 0);
-
- if (info != null && info.getType() == ConnectivityManager.TYPE_BLUETOOTH) {
- mBluetoothTethered = info.isConnected();
- } else {
- mBluetoothTethered = false;
+ 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);
}
- // We want to update all the icons, all at once, for any condition change
- updateDataNetType();
- updateWimaxIcons();
- updateDataIcon();
- updateTelephonySignalStrength();
- updateWifiIcons();
- }
+ /**
+ * 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);
+ }
- // ===== Update the views =======================================================
+ /**
+ * Produce a mapping of data network types to icon groups for simple and quick use in
+ * updateTelephony.
+ */
+ private void mapIconSets() {
+ mNetworkToIconLookup.clear();
- void refreshViews() {
- Context context = mContext;
+ 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);
- int combinedSignalIconId = 0;
- String combinedLabel = "";
- String wifiLabel = "";
- String mobileLabel = "";
- int N;
- final boolean emergencyOnly = isEmergencyOnly();
+ 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);
- if (!mHasMobileDataFeature) {
- mDataSignalIconId = mPhoneSignalIconId = 0;
- mQSPhoneSignalIconId = 0;
- mobileLabel = "";
- } else {
- // 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 (mDataConnected) {
- mobileLabel = mNetworkName;
- } else if (mConnected || emergencyOnly) {
- if (hasService() || emergencyOnly) {
- // The isEmergencyOnly test covers the case of a phone with no SIM
- mobileLabel = mNetworkName;
- } else {
- // Tablets, basically
- mobileLabel = "";
- }
+ mDefaultIcons = TelephonyIcons.G;
} else {
- mobileLabel
- = context.getString(R.string.status_bar_settings_signal_meter_disconnected);
+ 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;
}
- // Now for things that should only be shown when actually using mobile data.
- if (mDataConnected) {
- combinedSignalIconId = mDataSignalIconId;
+ 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);
- combinedLabel = mobileLabel;
- combinedSignalIconId = mDataSignalIconId; // set by updateDataIcon()
- mContentDescriptionCombinedSignal = mContentDescriptionDataType;
+ if (mConfig.show4gForLte) {
+ mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE, TelephonyIcons.FOUR_G);
+ } else {
+ mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE, TelephonyIcons.LTE);
}
}
- if (mWifiConnected) {
- if (mWifiSsid == null) {
- wifiLabel = context.getString(R.string.status_bar_settings_signal_meter_wifi_nossid);
- } else {
- wifiLabel = mWifiSsid;
- if (DEBUG) {
- wifiLabel += "xxxxXXXXxxxxXXXX";
+ @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());
+ }
+ }
- combinedLabel = wifiLabel;
- combinedSignalIconId = mWifiIconId; // set by updateWifiIcons()
- mContentDescriptionCombinedSignal = mContentDescriptionWifi;
- } else {
- if (mHasMobileDataFeature) {
- wifiLabel = "";
+ @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 {
- wifiLabel = context.getString(R.string.status_bar_settings_signal_meter_disconnected);
+ return false;
}
}
- if (mBluetoothTethered) {
- combinedLabel = mContext.getString(R.string.bluetooth_tethered);
- combinedSignalIconId = mBluetoothTetherIconId;
- mContentDescriptionCombinedSignal = mContext.getString(
- R.string.accessibility_bluetooth_tether);
+ private boolean isCdma() {
+ return (mSignalStrength != null) && !mSignalStrength.isGsm();
}
- final boolean ethernetConnected = (mConnectedNetworkType == ConnectivityManager.TYPE_ETHERNET);
- if (ethernetConnected) {
- combinedLabel = context.getString(R.string.ethernet_label);
+ public boolean isEmergencyOnly() {
+ return (mServiceState != null && mServiceState.isEmergencyOnly());
}
- if (mAirplaneMode &&
- (mServiceState == null || (!hasService() && !mServiceState.isEmergencyOnly()))) {
- // Only display the flight-mode icon if not in "emergency calls only" mode.
+ 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();
+ }
+ }
- // look again; your radios are now airplanes
- mContentDescriptionPhoneSignal = mContext.getString(
- R.string.accessibility_airplane_mode);
- mAirplaneIconId = TelephonyIcons.FLIGHT_MODE_ICON;
- mPhoneSignalIconId = mDataSignalIconId = mDataTypeIconId = mQSDataTypeIconId = 0;
- mQSPhoneSignalIconId = 0;
+ 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();
+ }
+ }
- // combined values from connected wifi take precedence over airplane mode
- if (mWifiConnected) {
- // Suppress "No internet connection." from mobile if wifi connected.
- mobileLabel = "";
+ private void updateDataSim() {
+ int defaultDataSub = SubscriptionManager.getDefaultDataSubId();
+ if (SubscriptionManager.isValidSubscriptionId(defaultDataSub)) {
+ mCurrentState.dataSim = defaultDataSub == mSubscriptionInfo.getSubscriptionId();
} else {
- if (mHasMobileDataFeature) {
- // let the mobile icon show "No internet connection."
- wifiLabel = "";
- } else {
- wifiLabel = context.getString(R.string.status_bar_settings_signal_meter_disconnected);
- combinedLabel = wifiLabel;
- }
- mContentDescriptionCombinedSignal = mContentDescriptionPhoneSignal;
- combinedSignalIconId = mDataSignalIconId;
+ // 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();
}
- else if (!mDataConnected && !mWifiConnected && !mBluetoothTethered && !mWimaxConnected && !ethernetConnected) {
- // pretty much totally disconnected
- combinedLabel = context.getString(R.string.status_bar_settings_signal_meter_disconnected);
- // On devices without mobile radios, we want to show the wifi icon
- combinedSignalIconId =
- mHasMobileDataFeature ? mDataSignalIconId : mWifiIconId;
- mContentDescriptionCombinedSignal = mHasMobileDataFeature
- ? mContentDescriptionDataType : mContentDescriptionWifi;
+ /**
+ * 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;
+ }
+ }
- int inetCondition = inetConditionForNetwork(ConnectivityManager.TYPE_MOBILE);
+ /**
+ * 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;
- mDataTypeIconId = 0;
- mQSDataTypeIconId = 0;
if (isRoaming()) {
- mDataTypeIconId = TelephonyIcons.ROAMING_ICON;
- mQSDataTypeIconId = TelephonyIcons.QS_DATA_R[mInetCondition];
+ 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();
}
- if (mDemoMode) {
- mQSWifiIconId = mDemoWifiLevel < 0 ? R.drawable.ic_qs_wifi_no_network
- : WifiIcons.QS_WIFI_SIGNAL_STRENGTH[mDemoInetCondition][mDemoWifiLevel];
- mQSPhoneSignalIconId = mDemoMobileLevel < 0 ? R.drawable.ic_qs_signal_no_signal :
- TelephonyIcons.QS_TELEPHONY_SIGNAL_STRENGTH[mDemoInetCondition][mDemoMobileLevel];
- mQSDataTypeIconId = mDemoQSDataTypeIconId;
+ @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();
}
- if (DEBUG) {
- Log.d(TAG, "refreshViews connected={"
- + (mWifiConnected?" wifi":"")
- + (mDataConnected?" data":"")
- + " } level="
- + ((mSignalStrength == null)?"??":Integer.toString(mSignalStrength.getLevel()))
- + " combinedSignalIconId=0x"
- + Integer.toHexString(combinedSignalIconId)
- + "/" + getResourceName(combinedSignalIconId)
- + " mobileLabel=" + mobileLabel
- + " wifiLabel=" + wifiLabel
- + " emergencyOnly=" + emergencyOnly
- + " combinedLabel=" + combinedLabel
- + " mAirplaneMode=" + mAirplaneMode
- + " mDataActivity=" + mDataActivity
- + " mPhoneSignalIconId=0x" + Integer.toHexString(mPhoneSignalIconId)
- + " mQSPhoneSignalIconId=0x" + Integer.toHexString(mQSPhoneSignalIconId)
- + " mDataDirectionIconId=0x" + Integer.toHexString(mDataDirectionIconId)
- + " mDataSignalIconId=0x" + Integer.toHexString(mDataSignalIconId)
- + " mDataTypeIconId=0x" + Integer.toHexString(mDataTypeIconId)
- + " mQSDataTypeIconId=0x" + Integer.toHexString(mQSDataTypeIconId)
- + " mWifiIconId=0x" + Integer.toHexString(mWifiIconId)
- + " mQSWifiIconId=0x" + Integer.toHexString(mQSWifiIconId)
- + " mBluetoothTetherIconId=0x" + Integer.toHexString(mBluetoothTetherIconId));
- }
+ @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);
+ }
- // update QS
- for (NetworkSignalChangedCallback cb : mSignalsChangedCallbacks) {
- notifySignalsChangedCallbacks(cb);
- }
+ @Override
+ public void onSignalStrengthsChanged(SignalStrength signalStrength) {
+ if (DEBUG) {
+ Log.d(mTag, "onSignalStrengthsChanged signalStrength=" + signalStrength +
+ ((signalStrength == null) ? "" : (" level=" + signalStrength.getLevel())));
+ }
+ mSignalStrength = signalStrength;
+ updateTelephony();
+ }
- if (mLastPhoneSignalIconId != mPhoneSignalIconId
- || mLastWifiIconId != mWifiIconId
- || mLastInetCondition != mInetCondition
- || mLastWimaxIconId != mWimaxIconId
- || mLastDataTypeIconId != mDataTypeIconId
- || mLastAirplaneMode != mAirplaneMode
- || mLastLocale != mLocale
- || mLastConnectedNetworkType != mConnectedNetworkType)
- {
- // NB: the mLast*s will be updated later
- for (SignalCluster cluster : mSignalClusters) {
- refreshSignalCluster(cluster);
+ @Override
+ public void onServiceStateChanged(ServiceState state) {
+ if (DEBUG) {
+ Log.d(mTag, "onServiceStateChanged voiceState=" + state.getVoiceRegState()
+ + " dataState=" + state.getDataRegState());
+ }
+ mServiceState = state;
+ updateTelephony();
}
- }
- if (mLastAirplaneMode != mAirplaneMode) {
- mLastAirplaneMode = mAirplaneMode;
- }
+ @Override
+ public void onDataConnectionStateChanged(int state, int networkType) {
+ if (DEBUG) {
+ Log.d(mTag, "onDataConnectionStateChanged: state=" + state
+ + " type=" + networkType);
+ }
+ mDataState = state;
+ mDataNetType = networkType;
+ updateTelephony();
+ }
- if (mLastLocale != mLocale) {
- mLastLocale = mLocale;
+ @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;
+ }
}
- // the phone icon on phones
- if (mLastPhoneSignalIconId != mPhoneSignalIconId) {
- mLastPhoneSignalIconId = mPhoneSignalIconId;
- }
+ static class MobileState extends SignalController.State {
+ String networkName;
+ boolean dataSim;
+ boolean dataConnected;
+ boolean isEmergency;
+ boolean airplaneMode;
+ int inetForNetwork;
- // the data icon on phones
- if (mLastDataDirectionIconId != mDataDirectionIconId) {
- mLastDataDirectionIconId = mDataDirectionIconId;
- }
+ @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;
+ }
- // the wifi icon on phones
- if (mLastWifiIconId != mWifiIconId) {
- mLastWifiIconId = mWifiIconId;
+ @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;
+ }
}
+ }
- if (mLastInetCondition != mInetCondition) {
- mLastInetCondition = mInetCondition;
+ /**
+ * Common base class for handling signal for both wifi and mobile data.
+ */
+ 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();
+ }
+ }
}
- if (mLastConnectedNetworkType != mConnectedNetworkType) {
- mLastConnectedNetworkType = mConnectedNetworkType;
+ public T getState() {
+ return mCurrentState;
}
- // the wimax icon on phones
- if (mLastWimaxIconId != mWimaxIconId) {
- mLastWimaxIconId = mWimaxIconId;
+ public int getTransportType() {
+ return mTransportType;
}
- // the combined data signal icon
- if (mLastCombinedSignalIconId != combinedSignalIconId) {
- mLastCombinedSignalIconId = combinedSignalIconId;
+
+ public void setInetCondition(int inetCondition) {
+ mCurrentState.inetCondition = inetCondition;
+ notifyListenersIfNecessary();
}
- // the data network type overlay
- if (mLastDataTypeIconId != mDataTypeIconId) {
- mLastDataTypeIconId = mDataTypeIconId;
+ /**
+ * 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);
}
- // the combinedLabel in the notification panel
- if (!mLastCombinedLabel.equals(combinedLabel)) {
- mLastCombinedLabel = combinedLabel;
- N = mCombinedLabelViews.size();
- for (int i=0; i<N; i++) {
- TextView v = mCombinedLabelViews.get(i);
- v.setText(combinedLabel);
+ /**
+ * 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;
}
- // wifi label
- N = mWifiLabelViews.size();
- for (int i=0; i<N; i++) {
- TextView v = mWifiLabelViews.get(i);
- v.setText(wifiLabel);
- if ("".equals(wifiLabel)) {
- v.setVisibility(View.GONE);
+ 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 {
- v.setVisibility(View.VISIBLE);
+ return getIcons().mQsNullState;
}
}
- // mobile label
- N = mMobileLabelViews.size();
- for (int i=0; i<N; i++) {
- TextView v = mMobileLabelViews.get(i);
- v.setText(mobileLabel);
- if ("".equals(mobileLabel)) {
- v.setVisibility(View.GONE);
+ /**
+ * 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 {
- v.setVisibility(View.VISIBLE);
+ return getIcons().mSbNullState;
}
}
- // e-call label
- N = mEmergencyViews.size();
- for (int i=0; i<N; i++) {
- StatusBarHeaderView v = mEmergencyViews.get(i);
- v.setShowEmergencyCallsOnly(emergencyOnly);
+ /**
+ * 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 dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println("NetworkController state:");
- pw.println(String.format(" %s network type %d (%s)",
- mConnected?"CONNECTED":"DISCONNECTED",
- mConnectedNetworkType, mConnectedNetworkTypeName));
- pw.println(" - telephony ------");
- pw.print(" hasVoiceCallingFeature()=");
- pw.println(hasVoiceCallingFeature());
- pw.print(" hasService()=");
- pw.println(hasService());
- pw.print(" mHspaDataDistinguishable=");
- pw.println(mHspaDataDistinguishable);
- pw.print(" mDataConnected=");
- pw.println(mDataConnected);
- pw.print(" mSimState=");
- pw.println(mSimState);
- pw.print(" mPhoneState=");
- pw.println(mPhoneState);
- pw.print(" mDataState=");
- pw.println(mDataState);
- pw.print(" mDataActivity=");
- pw.println(mDataActivity);
- pw.print(" mDataNetType=");
- pw.print(mDataNetType);
- pw.print("/");
- pw.println(TelephonyManager.getNetworkTypeName(mDataNetType));
- pw.print(" mServiceState=");
- pw.println(mServiceState);
- pw.print(" mSignalStrength=");
- pw.println(mSignalStrength);
- pw.print(" mLastSignalLevel=");
- pw.println(mLastSignalLevel);
- pw.print(" mNetworkName=");
- pw.println(mNetworkName);
- pw.print(" mNetworkNameDefault=");
- pw.println(mNetworkNameDefault);
- pw.print(" mNetworkNameSeparator=");
- pw.println(mNetworkNameSeparator.replace("\n","\\n"));
- pw.print(" mPhoneSignalIconId=0x");
- pw.print(Integer.toHexString(mPhoneSignalIconId));
- pw.print("/");
- pw.print(" mQSPhoneSignalIconId=0x");
- pw.print(Integer.toHexString(mQSPhoneSignalIconId));
- pw.print("/");
- pw.println(getResourceName(mPhoneSignalIconId));
- pw.print(" mDataDirectionIconId=");
- pw.print(Integer.toHexString(mDataDirectionIconId));
- pw.print("/");
- pw.println(getResourceName(mDataDirectionIconId));
- pw.print(" mDataSignalIconId=");
- pw.print(Integer.toHexString(mDataSignalIconId));
- pw.print("/");
- pw.println(getResourceName(mDataSignalIconId));
- pw.print(" mDataTypeIconId=");
- pw.print(Integer.toHexString(mDataTypeIconId));
- pw.print("/");
- pw.println(getResourceName(mDataTypeIconId));
- pw.print(" mQSDataTypeIconId=");
- pw.print(Integer.toHexString(mQSDataTypeIconId));
- pw.print("/");
- pw.println(getResourceName(mQSDataTypeIconId));
-
- pw.println(" - wifi ------");
- pw.print(" mWifiEnabled=");
- pw.println(mWifiEnabled);
- pw.print(" mWifiConnected=");
- pw.println(mWifiConnected);
- pw.print(" mWifiRssi=");
- pw.println(mWifiRssi);
- pw.print(" mWifiLevel=");
- pw.println(mWifiLevel);
- pw.print(" mWifiSsid=");
- pw.println(mWifiSsid);
- pw.println(String.format(" mWifiIconId=0x%08x/%s",
- mWifiIconId, getResourceName(mWifiIconId)));
- pw.println(String.format(" mQSWifiIconId=0x%08x/%s",
- mQSWifiIconId, getResourceName(mQSWifiIconId)));
- pw.print(" mWifiActivity=");
- pw.println(mWifiActivity);
-
- if (mWimaxSupported) {
- pw.println(" - wimax ------");
- pw.print(" mIsWimaxEnabled="); pw.println(mIsWimaxEnabled);
- pw.print(" mWimaxConnected="); pw.println(mWimaxConnected);
- pw.print(" mWimaxIdle="); pw.println(mWimaxIdle);
- pw.println(String.format(" mWimaxIconId=0x%08x/%s",
- mWimaxIconId, getResourceName(mWimaxIconId)));
- pw.println(String.format(" mWimaxSignal=%d", mWimaxSignal));
- pw.println(String.format(" mWimaxState=%d", mWimaxState));
- pw.println(String.format(" mWimaxExtraState=%d", mWimaxExtraState));
+ public void notifyListenersIfNecessary() {
+ if (isDirty()) {
+ saveLastState();
+ notifyListeners();
+ mNetworkController.refreshCarrierLabel();
+ }
}
- pw.println(" - Bluetooth ----");
- pw.print(" mBtReverseTethered=");
- pw.println(mBluetoothTethered);
+ /**
+ * Returns the resource if resId is not 0, and an empty string otherwise.
+ */
+ protected String getStringIfExists(int resId) {
+ return resId != 0 ? mContext.getString(resId) : "";
+ }
- pw.println(" - connectivity ------");
- pw.print(" mInetCondition=");
- pw.println(mInetCondition);
+ protected I getIcons() {
+ return (I) mCurrentState.iconGroup;
+ }
- pw.println(" - icons ------");
- pw.print(" mLastPhoneSignalIconId=0x");
- pw.print(Integer.toHexString(mLastPhoneSignalIconId));
- pw.print("/");
- pw.println(getResourceName(mLastPhoneSignalIconId));
- pw.print(" mLastDataDirectionIconId=0x");
- pw.print(Integer.toHexString(mLastDataDirectionIconId));
- pw.print("/");
- pw.println(getResourceName(mLastDataDirectionIconId));
- pw.print(" mLastWifiIconId=0x");
- pw.print(Integer.toHexString(mLastWifiIconId));
- pw.print("/");
- pw.println(getResourceName(mLastWifiIconId));
- pw.print(" mLastCombinedSignalIconId=0x");
- pw.print(Integer.toHexString(mLastCombinedSignalIconId));
- pw.print("/");
- pw.println(getResourceName(mLastCombinedSignalIconId));
- pw.print(" mLastDataTypeIconId=0x");
- pw.print(Integer.toHexString(mLastDataTypeIconId));
- pw.print("/");
- pw.println(getResourceName(mLastDataTypeIconId));
- pw.print(" mLastCombinedLabel=");
- pw.print(mLastCombinedLabel);
- pw.println("");
- }
+ /**
+ * 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);
+ }
- private String getResourceName(int resId) {
- if (resId != 0) {
- final Resources res = mContext.getResources();
- try {
- return res.getResourceName(resId);
- } catch (android.content.res.Resources.NotFoundException ex) {
- return "(unknown)";
+ 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)]);
+ }
}
- } else {
- return "(null)";
}
- }
- private boolean mDemoMode;
- private int mDemoInetCondition;
- private int mDemoWifiLevel;
- private int mDemoDataTypeIconId;
- private int mDemoQSDataTypeIconId;
- private int mDemoMobileLevel;
-
- @Override
- public void dispatchDemoCommand(String command, Bundle args) {
- if (!mDemoMode && command.equals(COMMAND_ENTER)) {
- mDemoMode = true;
- mDemoWifiLevel = mWifiLevel;
- mDemoInetCondition = mInetCondition;
- mDemoDataTypeIconId = mDataTypeIconId;
- mDemoQSDataTypeIconId = mQSDataTypeIconId;
- mDemoMobileLevel = mLastSignalLevel;
- } else if (mDemoMode && command.equals(COMMAND_EXIT)) {
- mDemoMode = false;
- for (SignalCluster cluster : mSignalClusters) {
- refreshSignalCluster(cluster);
+ /**
+ * 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;
}
- refreshViews();
- } else if (mDemoMode && command.equals(COMMAND_NETWORK)) {
- String airplane = args.getString("airplane");
- if (airplane != null) {
- boolean show = airplane.equals("show");
- for (SignalCluster cluster : mSignalClusters) {
- cluster.setIsAirplaneMode(show, TelephonyIcons.FLIGHT_MODE_ICON);
- }
+
+ @Override
+ public String toString() {
+ return "IconGroup(" + mName + ")";
}
- String fully = args.getString("fully");
- if (fully != null) {
- mDemoInetCondition = Boolean.parseBoolean(fully) ? 1 : 0;
+ }
+
+ 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;
}
- String wifi = args.getString("wifi");
- if (wifi != null) {
- boolean show = wifi.equals("show");
- String level = args.getString("level");
- if (level != null) {
- mDemoWifiLevel = level.equals("null") ? -1
- : Math.min(Integer.parseInt(level), WifiIcons.WIFI_LEVEL_COUNT - 1);
- }
- int iconId = mDemoWifiLevel < 0 ? R.drawable.stat_sys_wifi_signal_null
- : WifiIcons.WIFI_SIGNAL_STRENGTH[mDemoInetCondition][mDemoWifiLevel];
- for (SignalCluster cluster : mSignalClusters) {
- cluster.setWifiIndicators(
- show,
- iconId,
- "Demo");
+
+ @Override
+ public String toString() {
+ if (time != 0) {
+ StringBuilder builder = new StringBuilder();
+ toString(builder);
+ return builder.toString();
+ } else {
+ return "Empty " + getClass().getSimpleName();
}
- refreshViews();
}
- String mobile = args.getString("mobile");
- if (mobile != null) {
- boolean show = mobile.equals("show");
- String datatype = args.getString("datatype");
- if (datatype != null) {
- mDemoDataTypeIconId =
- datatype.equals("1x") ? TelephonyIcons.ICON_1X :
- datatype.equals("3g") ? TelephonyIcons.ICON_3G :
- datatype.equals("4g") ? TelephonyIcons.ICON_4G :
- datatype.equals("e") ? R.drawable.stat_sys_data_fully_connected_e :
- datatype.equals("g") ? R.drawable.stat_sys_data_fully_connected_g :
- datatype.equals("h") ? R.drawable.stat_sys_data_fully_connected_h :
- datatype.equals("lte") ? TelephonyIcons.ICON_LTE :
- datatype.equals("roam") ? TelephonyIcons.ROAMING_ICON :
- 0;
- mDemoQSDataTypeIconId =
- datatype.equals("1x") ? TelephonyIcons.QS_ICON_1X :
- datatype.equals("3g") ? TelephonyIcons.QS_ICON_3G :
- datatype.equals("4g") ? TelephonyIcons.QS_ICON_4G :
- datatype.equals("e") ? R.drawable.ic_qs_signal_e :
- datatype.equals("g") ? R.drawable.ic_qs_signal_g :
- datatype.equals("h") ? R.drawable.ic_qs_signal_h :
- datatype.equals("lte") ? TelephonyIcons.QS_ICON_LTE :
- datatype.equals("roam") ? R.drawable.ic_qs_signal_r :
- 0;
- }
- int[][] icons = TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH;
- String level = args.getString("level");
- if (level != null) {
- mDemoMobileLevel = level.equals("null") ? -1
- : Math.min(Integer.parseInt(level), icons[0].length - 1);
- }
- int iconId = mDemoMobileLevel < 0 ? R.drawable.stat_sys_signal_null :
- icons[mDemoInetCondition][mDemoMobileLevel];
- for (SignalCluster cluster : mSignalClusters) {
- cluster.setMobileDataIndicators(
- show,
- iconId,
- mDemoDataTypeIconId,
- "Demo",
- "Demo",
- mDemoDataTypeIconId == TelephonyIcons.ROAMING_ICON,
- isTypeIconWide(mDemoDataTypeIconId));
+
+ 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;
}
- refreshViews();
+ 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;
}
}
}
+
+ 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);
+ }
+
+ @VisibleForTesting
+ static class Config {
+ boolean showAtLeast3G = false;
+ boolean alwaysShowCdmaRssi = false;
+ boolean show4gForLte = false;
+ boolean hspaDataDistinguishable;
+
+ static Config readConfig(Context context) {
+ Config config = new Config();
+ Resources res = context.getResources();
+
+ config.showAtLeast3G = res.getBoolean(R.bool.config_showMin3G);
+ config.alwaysShowCdmaRssi =
+ res.getBoolean(com.android.internal.R.bool.config_alwaysUseCdmaRssi);
+ config.show4gForLte = res.getBoolean(R.bool.config_show4GForLTE);
+ config.hspaDataDistinguishable =
+ res.getBoolean(R.bool.config_hspa_data_distinguishable);
+ return config;
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmController.java
index 8f1f7c7..787acc5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmController.java
@@ -39,7 +39,7 @@ public class NextAlarmController extends BroadcastReceiver {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_USER_SWITCHED);
filter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
- context.registerReceiver(this, filter);
+ context.registerReceiverAsUser(this, UserHandle.ALL, filter, null, null);
updateNextAlarm();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitClockView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitClockView.java
index e7c4ede..50e3977 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitClockView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitClockView.java
@@ -16,10 +16,12 @@
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.os.UserHandle;
import android.text.format.DateFormat;
import android.util.AttributeSet;
import android.widget.LinearLayout;
@@ -42,7 +44,9 @@ public class SplitClockView extends LinearLayout {
final String action = intent.getAction();
if (Intent.ACTION_TIME_CHANGED.equals(action)
|| Intent.ACTION_TIMEZONE_CHANGED.equals(action)
- || Intent.ACTION_LOCALE_CHANGED.equals(action)) {
+ || Intent.ACTION_LOCALE_CHANGED.equals(action)
+ || Intent.ACTION_CONFIGURATION_CHANGED.equals(action)
+ || Intent.ACTION_USER_SWITCHED.equals(action)) {
updatePatterns();
}
}
@@ -57,6 +61,8 @@ public class SplitClockView extends LinearLayout {
super.onFinishInflate();
mTimeView = (TextClock) findViewById(R.id.time_view);
mAmPmView = (TextClock) findViewById(R.id.am_pm_view);
+ mTimeView.setShowCurrentUserTime(true);
+ mAmPmView.setShowCurrentUserTime(true);
}
@Override
@@ -67,7 +73,9 @@ public class SplitClockView extends LinearLayout {
filter.addAction(Intent.ACTION_TIME_CHANGED);
filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
filter.addAction(Intent.ACTION_LOCALE_CHANGED);
- getContext().registerReceiver(mIntentReceiver, filter, null, null);
+ filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
+ filter.addAction(Intent.ACTION_USER_SWITCHED);
+ getContext().registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, filter, null, null);
updatePatterns();
}
@@ -79,7 +87,8 @@ public class SplitClockView extends LinearLayout {
}
private void updatePatterns() {
- String formatString = DateFormat.getTimeFormatString(getContext());
+ String formatString = DateFormat.getTimeFormatString(getContext(),
+ ActivityManager.getCurrentUser());
int index = getAmPmPartEndIndex(formatString);
String timeString;
String amPmString;
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 1f2b918..4091619 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
@@ -17,11 +17,16 @@
package com.android.systemui.statusbar.policy;
import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.NetworkControllerImpl.MobileSignalController.MobileIconGroup;
class TelephonyIcons {
//***** Signal strength icons
+ static final int TELEPHONY_NUM_LEVELS = 5;
+
//GSM/UMTS
+ static final int TELEPHONY_NO_NETWORK = R.drawable.stat_sys_signal_null;
+
static final int[][] TELEPHONY_SIGNAL_STRENGTH = {
{ R.drawable.stat_sys_signal_0,
R.drawable.stat_sys_signal_1,
@@ -35,6 +40,8 @@ class TelephonyIcons {
R.drawable.stat_sys_signal_4_fully }
};
+ static final int QS_TELEPHONY_NO_NETWORK = R.drawable.ic_qs_signal_no_signal;
+
static final int[][] QS_TELEPHONY_SIGNAL_STRENGTH = {
{ R.drawable.ic_qs_signal_0,
R.drawable.ic_qs_signal_1,
@@ -66,8 +73,6 @@ class TelephonyIcons {
R.drawable.ic_qs_signal_r
};
- static final int[][] DATA_SIGNAL_STRENGTH = TELEPHONY_SIGNAL_STRENGTH;
-
//***** Data connection icons
//GSM/UMTS
@@ -191,6 +196,9 @@ class TelephonyIcons {
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;
static final int ICON_LTE = R.drawable.stat_sys_data_fully_connected_lte;
+ static final int ICON_G = R.drawable.stat_sys_data_fully_connected_g;
+ static final int ICON_E = R.drawable.stat_sys_data_fully_connected_e;
+ static final int ICON_H = R.drawable.stat_sys_data_fully_connected_h;
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;
@@ -199,5 +207,137 @@ class TelephonyIcons {
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 MobileIconGroup THREE_G = new MobileIconGroup(
+ "3G",
+ TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH,
+ TelephonyIcons.QS_TELEPHONY_SIGNAL_STRENGTH,
+ AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
+ 0, 0,
+ TelephonyIcons.TELEPHONY_NO_NETWORK,
+ TelephonyIcons.QS_TELEPHONY_NO_NETWORK,
+ AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
+ R.string.accessibility_data_connection_3g,
+ TelephonyIcons.ICON_3G,
+ true,
+ TelephonyIcons.QS_DATA_3G
+ );
+
+ static final MobileIconGroup UNKNOWN = new MobileIconGroup(
+ "Unknown",
+ TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH,
+ TelephonyIcons.QS_TELEPHONY_SIGNAL_STRENGTH,
+ AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
+ 0, 0,
+ TelephonyIcons.TELEPHONY_NO_NETWORK,
+ TelephonyIcons.QS_TELEPHONY_NO_NETWORK,
+ AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
+ 0, 0, false, new int[2]
+ );
+
+ static final MobileIconGroup E = new MobileIconGroup(
+ "E",
+ TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH,
+ TelephonyIcons.QS_TELEPHONY_SIGNAL_STRENGTH,
+ AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
+ 0, 0,
+ TelephonyIcons.TELEPHONY_NO_NETWORK,
+ TelephonyIcons.QS_TELEPHONY_NO_NETWORK,
+ AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
+ R.string.accessibility_data_connection_edge,
+ TelephonyIcons.ICON_E,
+ false,
+ TelephonyIcons.QS_DATA_E
+ );
+
+ static final MobileIconGroup ONE_X = new MobileIconGroup(
+ "1X",
+ TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH,
+ TelephonyIcons.QS_TELEPHONY_SIGNAL_STRENGTH,
+ AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
+ 0, 0,
+ TelephonyIcons.TELEPHONY_NO_NETWORK,
+ TelephonyIcons.QS_TELEPHONY_NO_NETWORK,
+ AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
+ R.string.accessibility_data_connection_cdma,
+ TelephonyIcons.ICON_1X,
+ true,
+ TelephonyIcons.QS_DATA_1X
+ );
+
+ static final MobileIconGroup G = new MobileIconGroup(
+ "G",
+ TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH,
+ TelephonyIcons.QS_TELEPHONY_SIGNAL_STRENGTH,
+ AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
+ 0, 0,
+ TelephonyIcons.TELEPHONY_NO_NETWORK,
+ TelephonyIcons.QS_TELEPHONY_NO_NETWORK,
+ AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
+ R.string.accessibility_data_connection_gprs,
+ TelephonyIcons.ICON_G,
+ false,
+ TelephonyIcons.QS_DATA_G
+ );
+
+ static final MobileIconGroup H = new MobileIconGroup(
+ "H",
+ TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH,
+ TelephonyIcons.QS_TELEPHONY_SIGNAL_STRENGTH,
+ AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
+ 0, 0,
+ TelephonyIcons.TELEPHONY_NO_NETWORK,
+ TelephonyIcons.QS_TELEPHONY_NO_NETWORK,
+ AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
+ R.string.accessibility_data_connection_3_5g,
+ TelephonyIcons.ICON_H,
+ false,
+ TelephonyIcons.QS_DATA_H
+ );
+
+ static final MobileIconGroup FOUR_G = new MobileIconGroup(
+ "4G",
+ TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH,
+ TelephonyIcons.QS_TELEPHONY_SIGNAL_STRENGTH,
+ AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
+ 0, 0,
+ TelephonyIcons.TELEPHONY_NO_NETWORK,
+ TelephonyIcons.QS_TELEPHONY_NO_NETWORK,
+ AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
+ R.string.accessibility_data_connection_4g,
+ TelephonyIcons.ICON_4G,
+ true,
+ TelephonyIcons.QS_DATA_4G
+ );
+
+ static final MobileIconGroup LTE = new MobileIconGroup(
+ "LTE",
+ TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH,
+ TelephonyIcons.QS_TELEPHONY_SIGNAL_STRENGTH,
+ AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
+ 0, 0,
+ TelephonyIcons.TELEPHONY_NO_NETWORK,
+ TelephonyIcons.QS_TELEPHONY_NO_NETWORK,
+ AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
+ R.string.accessibility_data_connection_lte,
+ TelephonyIcons.ICON_LTE,
+ true,
+ TelephonyIcons.QS_DATA_LTE
+ );
+
+ static final MobileIconGroup ROAMING = new MobileIconGroup(
+ "Roaming",
+ TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH_ROAMING,
+ TelephonyIcons.QS_TELEPHONY_SIGNAL_STRENGTH,
+ AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
+ 0, 0,
+ TelephonyIcons.TELEPHONY_NO_NETWORK,
+ TelephonyIcons.QS_TELEPHONY_NO_NETWORK,
+ AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
+ R.string.accessibility_data_connection_roaming,
+ TelephonyIcons.ROAMING_ICON,
+ false,
+ TelephonyIcons.QS_DATA_R
+ );
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoController.java
index d50e39f..a8d4f13 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoController.java
@@ -23,6 +23,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
+import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
@@ -131,8 +132,11 @@ public final class UserInfoController {
final int userId = userInfo.id;
final boolean isGuest = userInfo.isGuest();
final String userName = userInfo.name;
- final int avatarSize
- = mContext.getResources().getDimensionPixelSize(R.dimen.max_avatar_size);
+
+ final Resources res = mContext.getResources();
+ final int avatarSize = Math.max(
+ res.getDimensionPixelSize(R.dimen.multi_user_avatar_expanded_size),
+ res.getDimensionPixelSize(R.dimen.multi_user_avatar_keyguard_size));
final Context context = currentUserContext;
mUserInfoTask = new AsyncTask<Void, Void, Pair<String, Drawable>>() {
@@ -160,8 +164,9 @@ public final class UserInfoController {
// Try and read the display name from the local profile
final Cursor cursor = context.getContentResolver().query(
ContactsContract.Profile.CONTENT_URI, new String[] {
- ContactsContract.CommonDataKinds.Phone._ID, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME},
- null, null, null);
+ ContactsContract.CommonDataKinds.Phone._ID,
+ ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME
+ }, null, null, null);
if (cursor != null) {
try {
if (cursor.moveToFirst()) {
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 5c7909a..4ac41a1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -154,10 +154,11 @@ public class UserSwitcherController {
Bitmap picture = bitmaps.get(info.id);
if (picture == null) {
picture = mUserManager.getUserIcon(info.id);
- }
- if (picture != null) {
- picture = BitmapHelper.createCircularClip(
- picture, avatarSize, avatarSize);
+
+ if (picture != null) {
+ picture = BitmapHelper.createCircularClip(
+ picture, avatarSize, avatarSize);
+ }
}
int index = isCurrent ? 0 : records.size();
records.add(index, new UserRecord(info, picture, false /* isGuest */,
@@ -416,18 +417,6 @@ public class UserSwitcherController {
}
}
- public int getSwitchableUsers() {
- int result = 0;
- ArrayList<UserRecord> users = mController.mUsers;
- int N = users.size();
- for (int i = 0; i < N; i++) {
- if (users.get(i).info != null) {
- result++;
- }
- }
- return result;
- }
-
public Drawable getDrawable(Context context, UserRecord item) {
if (item.isAddUser) {
return context.getDrawable(R.drawable.ic_add_circle_qs);
@@ -435,6 +424,10 @@ public class UserSwitcherController {
return UserIcons.getDefaultUserIcon(item.isGuest ? UserHandle.USER_NULL : item.info.id,
/* light= */ true);
}
+
+ public void refresh() {
+ mController.refreshUsers(UserHandle.USER_NULL);
+ }
}
public static final class UserRecord {
@@ -499,6 +492,7 @@ public class UserSwitcherController {
} else {
v = (UserDetailView) convertView;
}
+ v.refreshAdapter();
return v;
}
@@ -589,4 +583,9 @@ public class UserSwitcherController {
}
}
}
+
+ public static boolean isUserSwitcherAvailable(UserManager um) {
+ return UserManager.supportsMultipleUsers() && um.isUserSwitcherEnabled();
+ }
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiIcons.java
index 49af979..c56646f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiIcons.java
@@ -45,5 +45,8 @@ class WifiIcons {
R.drawable.ic_qs_wifi_full_4 }
};
+ static final int QS_WIFI_NO_NETWORK = R.drawable.ic_qs_wifi_no_network;
+ static final int WIFI_NO_NETWORK = R.drawable.stat_sys_wifi_signal_null;
+
static final int WIFI_LEVEL_COUNT = WIFI_SIGNAL_STRENGTH[0].length;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WimaxIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WimaxIcons.java
deleted file mode 100644
index 4877828..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WimaxIcons.java
+++ /dev/null
@@ -1,27 +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 com.android.systemui.statusbar.policy.TelephonyIcons;
-
-class WimaxIcons {
- static final int[][] WIMAX_SIGNAL_STRENGTH = TelephonyIcons.DATA_SIGNAL_STRENGTH;
-
- static final int WIMAX_DISCONNECTED = WIMAX_SIGNAL_STRENGTH[0][0];
-
- static final int WIMAX_IDLE = WIMAX_DISCONNECTED; // XXX: unclear if we need a different icon
-}
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 415eb27..37ed7d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -157,8 +157,9 @@ public class ZenModeControllerImpl implements ZenModeController {
if (mRegistered) {
mContext.unregisterReceiver(mReceiver);
}
- mContext.registerReceiverAsUser(mReceiver, new UserHandle(mUserId),
- new IntentFilter(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED), null, null);
+ final IntentFilter filter = new IntentFilter(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
+ filter.addAction(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED);
+ mContext.registerReceiverAsUser(mReceiver, new UserHandle(mUserId), filter, null, null);
mRegistered = true;
mSetupObserver.register();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java
index 3c93b19..753a7f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java
@@ -33,6 +33,8 @@ public class AnimationFilter {
boolean animateHideSensitive;
boolean hasDelays;
boolean hasGoToFullShadeEvent;
+ boolean hasDarkEvent;
+ int darkAnimationOriginIndex;
public AnimationFilter animateAlpha() {
animateAlpha = true;
@@ -93,11 +95,17 @@ public class AnimationFilter {
reset();
int size = events.size();
for (int i = 0; i < size; i++) {
+ NotificationStackScrollLayout.AnimationEvent ev = events.get(i);
combineFilter(events.get(i).filter);
- if (events.get(i).animationType ==
+ if (ev.animationType ==
NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_GO_TO_FULL_SHADE) {
hasGoToFullShadeEvent = true;
}
+ if (ev.animationType ==
+ NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_DARK) {
+ hasDarkEvent = true;
+ darkAnimationOriginIndex = ev.darkAnimationOriginIndex;
+ }
}
}
@@ -126,5 +134,8 @@ public class AnimationFilter {
animateHideSensitive = false;
hasDelays = false;
hasGoToFullShadeEvent = false;
+ hasDarkEvent = false;
+ darkAnimationOriginIndex =
+ NotificationStackScrollLayout.AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE;
}
}
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 4a20406..6dcbed6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -16,10 +16,12 @@
package com.android.systemui.statusbar.stack;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.Paint;
+import android.graphics.PointF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
@@ -40,6 +42,7 @@ 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 com.android.systemui.statusbar.StackScrollerDecorView;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
import com.android.systemui.statusbar.policy.ScrollAdapter;
@@ -70,6 +73,12 @@ public class NotificationStackScrollLayout extends ViewGroup
private SwipeHelper mSwipeHelper;
private boolean mSwipingInProgress;
private int mCurrentStackHeight = Integer.MAX_VALUE;
+
+ /**
+ * mCurrentStackHeight is the actual stack height, mLastSetStackHeight is the stack height set
+ * externally from {@link #setStackHeight}
+ */
+ private float mLastSetStackHeight;
private int mOwnScrollY;
private int mMaxLayoutHeight;
@@ -84,6 +93,9 @@ public class NotificationStackScrollLayout extends ViewGroup
private int mLastMotionY;
private int mDownX;
private int mActivePointerId;
+ private boolean mTouchIsClick;
+ private float mInitialTouchX;
+ private float mInitialTouchY;
private int mSidePaddings;
private Paint mDebugPaint;
@@ -133,11 +145,13 @@ public class NotificationStackScrollLayout extends ViewGroup
private OnChildLocationsChangedListener mListener;
private OnOverscrollTopChangedListener mOverscrollTopChangedListener;
private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
+ private OnEmptySpaceClickListener mOnEmptySpaceClickListener;
private boolean mNeedsAnimation;
private boolean mTopPaddingNeedsAnimation;
private boolean mDimmedNeedsAnimation;
private boolean mHideSensitiveNeedsAnimation;
private boolean mDarkNeedsAnimation;
+ private int mDarkAnimationOriginIndex;
private boolean mActivateNeedsAnimation;
private boolean mGoToFullShadeNeedsAnimation;
private boolean mIsExpanded = true;
@@ -199,6 +213,7 @@ public class NotificationStackScrollLayout extends ViewGroup
}
};
private PhoneStatusBar mPhoneStatusBar;
+ private int[] mTempInt2 = new int[2];
public NotificationStackScrollLayout(Context context) {
this(context, null);
@@ -367,6 +382,9 @@ public class NotificationStackScrollLayout extends ViewGroup
if (childViewState == null) {
return ViewState.LOCATION_UNKNOWN;
}
+ if (childViewState.gone) {
+ return ViewState.LOCATION_GONE;
+ }
return childViewState.location;
}
@@ -445,11 +463,13 @@ public class NotificationStackScrollLayout extends ViewGroup
* @param height the new height of the stack
*/
public void setStackHeight(float height) {
+ mLastSetStackHeight = height;
setIsExpanded(height > 0.0f);
int newStackHeight = (int) height;
int minStackHeight = getMinStackHeight();
int stackHeight;
- if (newStackHeight - mTopPadding >= minStackHeight || getNotGoneChildCount() == 0) {
+ if (newStackHeight - mTopPadding - mTopPaddingOverflow >= minStackHeight
+ || getNotGoneChildCount() == 0) {
setTranslationY(mTopPaddingOverflow);
stackHeight = newStackHeight;
} else {
@@ -459,7 +479,8 @@ public class NotificationStackScrollLayout extends ViewGroup
int translationY = (newStackHeight - minStackHeight);
// A slight parallax effect is introduced in order for the stack to catch up with
// the top card.
- float partiallyThere = (float) (newStackHeight - mTopPadding) / minStackHeight;
+ float partiallyThere = (newStackHeight - mTopPadding - mTopPaddingOverflow)
+ / minStackHeight;
partiallyThere = Math.max(0, partiallyThere);
translationY += (1 - partiallyThere) * (mBottomStackPeekSize +
mCollapseSecondCardPadding);
@@ -570,10 +591,38 @@ public class NotificationStackScrollLayout extends ViewGroup
return getChildAtPosition(ev.getX(), ev.getY());
}
+ public ExpandableView getClosestChildAtRawPosition(float touchX, float touchY) {
+ getLocationOnScreen(mTempInt2);
+ float localTouchY = touchY - mTempInt2[1];
+
+ ExpandableView closestChild = null;
+ float minDist = Float.MAX_VALUE;
+
+ // find the view closest to the location, accounting for GONE views
+ final int count = getChildCount();
+ for (int childIdx = 0; childIdx < count; childIdx++) {
+ ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
+ if (slidingChild.getVisibility() == GONE
+ || slidingChild instanceof StackScrollerDecorView
+ || slidingChild == mSpeedBumpView) {
+ continue;
+ }
+ float childTop = slidingChild.getTranslationY();
+ float top = childTop + slidingChild.getClipTopAmount();
+ float bottom = childTop + slidingChild.getActualHeight();
+
+ float dist = Math.min(Math.abs(top - localTouchY), Math.abs(bottom - localTouchY));
+ if (dist < minDist) {
+ closestChild = slidingChild;
+ minDist = dist;
+ }
+ }
+ return closestChild;
+ }
+
public ExpandableView getChildAtRawPosition(float touchX, float touchY) {
- int[] location = new int[2];
- getLocationOnScreen(location);
- return getChildAtPosition(touchX - location[0], touchY - location[1]);
+ getLocationOnScreen(mTempInt2);
+ return getChildAtPosition(touchX - mTempInt2[0], touchY - mTempInt2[1]);
}
public ExpandableView getChildAtPosition(float touchX, float touchY) {
@@ -581,7 +630,9 @@ public class NotificationStackScrollLayout extends ViewGroup
final int count = getChildCount();
for (int childIdx = 0; childIdx < count; childIdx++) {
ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
- if (slidingChild.getVisibility() == GONE) {
+ if (slidingChild.getVisibility() == GONE
+ || slidingChild instanceof StackScrollerDecorView
+ || slidingChild == mSpeedBumpView) {
continue;
}
float childTop = slidingChild.getTranslationY();
@@ -687,6 +738,7 @@ public class NotificationStackScrollLayout extends ViewGroup
transformTouchEvent(ev, this, mScrollView);
return mScrollView.onTouchEvent(ev);
}
+ handleEmptySpaceClick(ev);
boolean expandWantsIt = false;
if (!mSwipingInProgress && !mOnlyScrollingInThisMotion && isScrollingEnabled()) {
if (isCancelOrUp) {
@@ -1338,7 +1390,19 @@ public class NotificationStackScrollLayout extends ViewGroup
&& initialVelocity > 0;
}
- public void updateTopPadding(float qsHeight, int scrollY, boolean animate) {
+ /**
+ * Updates the top padding of the notifications, taking {@link #getIntrinsicPadding()} into
+ * account.
+ *
+ * @param qsHeight the top padding imposed by the quick settings panel
+ * @param scrollY how much the notifications are scrolled inside the QS/notifications scroll
+ * container
+ * @param animate whether to animate the change
+ * @param ignoreIntrinsicPadding if true, {@link #getIntrinsicPadding()} is ignored and
+ * {@code qsHeight} is the final top padding
+ */
+ public void updateTopPadding(float qsHeight, int scrollY, boolean animate,
+ boolean ignoreIntrinsicPadding) {
float start = qsHeight - scrollY + mNotificationTopPadding;
float stackHeight = getHeight() - start;
int minStackHeight = getMinStackHeight();
@@ -1346,13 +1410,13 @@ public class NotificationStackScrollLayout extends ViewGroup
float overflow = minStackHeight - stackHeight;
stackHeight = minStackHeight;
start = getHeight() - stackHeight;
- setTranslationY(overflow);
mTopPaddingOverflow = overflow;
} else {
- setTranslationY(0);
mTopPaddingOverflow = 0;
}
- setTopPadding(clampPadding((int) start), animate);
+ setTopPadding(ignoreIntrinsicPadding ? (int) start : clampPadding((int) start),
+ animate);
+ setStackHeight(mLastSetStackHeight);
}
public int getNotificationTopPadding() {
@@ -1430,6 +1494,7 @@ public class NotificationStackScrollLayout extends ViewGroup
transformTouchEvent(ev, mScrollView, this);
}
initDownStates(ev);
+ handleEmptySpaceClick(ev);
boolean expandWantsIt = false;
if (!mSwipingInProgress && !mOnlyScrollingInThisMotion && isScrollingEnabled()) {
expandWantsIt = mExpandHelper.onInterceptTouchEvent(ev);
@@ -1448,11 +1513,31 @@ public class NotificationStackScrollLayout extends ViewGroup
return swipeWantsIt || scrollWantsIt || expandWantsIt || super.onInterceptTouchEvent(ev);
}
+ private void handleEmptySpaceClick(MotionEvent ev) {
+ switch (ev.getActionMasked()) {
+ case MotionEvent.ACTION_MOVE:
+ if (mTouchIsClick && (Math.abs(ev.getY() - mInitialTouchY) > mTouchSlop
+ || Math.abs(ev.getX() - mInitialTouchX) > mTouchSlop )) {
+ mTouchIsClick = false;
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ if (mPhoneStatusBar.getBarState() != StatusBarState.KEYGUARD && mTouchIsClick &&
+ isBelowLastNotification(mInitialTouchX, mInitialTouchY)) {
+ mOnEmptySpaceClickListener.onEmptySpaceClicked(mInitialTouchX, mInitialTouchY);
+ }
+ break;
+ }
+ }
+
private void initDownStates(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
mExpandedInThisMotion = false;
mOnlyScrollingInThisMotion = !mScroller.isFinished();
mDisallowScrollingInThisMotion = false;
+ mTouchIsClick = true;
+ mInitialTouchX = ev.getX();
+ mInitialTouchY = ev.getY();
}
}
@@ -1553,6 +1638,12 @@ public class NotificationStackScrollLayout extends ViewGroup
((ExpandableView) child).setOnHeightChangedListener(this);
generateAddAnimation(child, false /* fromMoreCard */);
updateAnimationState(child);
+ if (canChildBeDismissed(child)) {
+ // Make sure the dismissButton is visible and not in the animated state.
+ // We need to do this to avoid a race where a clearable notification is added after the
+ // dismiss animation is finished
+ mDismissView.showClearButton();
+ }
}
public void setAnimationsEnabled(boolean animationsEnabled) {
@@ -1759,8 +1850,9 @@ public class NotificationStackScrollLayout extends ViewGroup
private void generateDarkEvent() {
if (mDarkNeedsAnimation) {
- mAnimationEvents.add(
- new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DARK));
+ AnimationEvent ev = new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DARK);
+ ev.darkAnimationOriginIndex = mDarkAnimationOriginIndex;
+ mAnimationEvents.add(ev);
}
mDarkNeedsAnimation = false;
}
@@ -1886,7 +1978,14 @@ public class NotificationStackScrollLayout extends ViewGroup
* @return Whether the specified motion event is actually happening over the content.
*/
private boolean isInContentBounds(MotionEvent event) {
- return event.getY() < getHeight() - getEmptyBottomMargin();
+ return isInContentBounds(event.getY());
+ }
+
+ /**
+ * @return Whether a y coordinate is inside the content.
+ */
+ public boolean isInContentBounds(float y) {
+ return y < getHeight() - getEmptyBottomMargin();
}
private void setIsBeingDragged(boolean isDragged) {
@@ -1944,6 +2043,9 @@ public class NotificationStackScrollLayout extends ViewGroup
mStackScrollAlgorithm.onExpansionStopped();
if (!mIsExpanded) {
mOwnScrollY = 0;
+
+ // lets make sure nothing is in the overlay anymore
+ getOverlay().clear();
}
}
@@ -1995,6 +2097,10 @@ public class NotificationStackScrollLayout extends ViewGroup
this.mOnHeightChangedListener = mOnHeightChangedListener;
}
+ public void setOnEmptySpaceClickListener(OnEmptySpaceClickListener listener) {
+ mOnEmptySpaceClickListener = listener;
+ }
+
public void onChildAnimationFinished() {
requestChildrenUpdate();
}
@@ -2078,7 +2184,7 @@ public class NotificationStackScrollLayout extends ViewGroup
mEmptyShadeView.setInvisible();
mGoToFullShadeNeedsAnimation = true;
mGoToFullShadeDelay = delay;
- mNeedsAnimation = true;
+ mNeedsAnimation = true;
requestChildrenUpdate();
}
@@ -2109,15 +2215,46 @@ public class NotificationStackScrollLayout extends ViewGroup
/**
* See {@link AmbientState#setDark}.
*/
- public void setDark(boolean dark, boolean animate) {
+ public void setDark(boolean dark, boolean animate, @Nullable PointF touchWakeUpScreenLocation) {
mAmbientState.setDark(dark);
if (animate && mAnimationsEnabled) {
mDarkNeedsAnimation = true;
+ mDarkAnimationOriginIndex = findDarkAnimationOriginIndex(touchWakeUpScreenLocation);
mNeedsAnimation = true;
}
requestChildrenUpdate();
}
+ private int findDarkAnimationOriginIndex(@Nullable PointF screenLocation) {
+ if (screenLocation == null || screenLocation.y < mTopPadding + mTopPaddingOverflow) {
+ return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE;
+ }
+ if (screenLocation.y > getBottomMostNotificationBottom()) {
+ return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_BELOW;
+ }
+ View child = getClosestChildAtRawPosition(screenLocation.x, screenLocation.y);
+ if (child != null) {
+ return getNotGoneIndex(child);
+ } else {
+ return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE;
+ }
+ }
+
+ private int getNotGoneIndex(View child) {
+ int count = getChildCount();
+ int notGoneIndex = 0;
+ for (int i = 0; i < count; i++) {
+ View v = getChildAt(i);
+ if (child == v) {
+ return notGoneIndex;
+ }
+ if (v.getVisibility() != View.GONE) {
+ notGoneIndex++;
+ }
+ }
+ return -1;
+ }
+
public void setDismissView(DismissView dismissView) {
mDismissView = dismissView;
addView(mDismissView);
@@ -2143,8 +2280,7 @@ public class NotificationStackScrollLayout extends ViewGroup
updateContentHeight();
notifyHeightChangeListener(mDismissView);
} else {
- mEmptyShadeView.setWillBeGone(true);
- mEmptyShadeView.performVisibilityAnimation(false, new Runnable() {
+ Runnable onFinishedRunnable = new Runnable() {
@Override
public void run() {
mEmptyShadeView.setVisibility(GONE);
@@ -2152,7 +2288,14 @@ public class NotificationStackScrollLayout extends ViewGroup
updateContentHeight();
notifyHeightChangeListener(mDismissView);
}
- });
+ };
+ if (mAnimationsEnabled) {
+ mEmptyShadeView.setWillBeGone(true);
+ mEmptyShadeView.performVisibilityAnimation(false, onFinishedRunnable);
+ } else {
+ mEmptyShadeView.setInvisible();
+ onFinishedRunnable.run();
+ }
}
}
}
@@ -2172,8 +2315,7 @@ public class NotificationStackScrollLayout extends ViewGroup
updateContentHeight();
notifyHeightChangeListener(mDismissView);
} else {
- mDismissView.setWillBeGone(true);
- mDismissView.performVisibilityAnimation(false, new Runnable() {
+ Runnable dimissHideFinishRunnable = new Runnable() {
@Override
public void run() {
mDismissView.setVisibility(GONE);
@@ -2181,13 +2323,21 @@ public class NotificationStackScrollLayout extends ViewGroup
updateContentHeight();
notifyHeightChangeListener(mDismissView);
}
- });
+ };
+ if (mDismissView.isButtonVisible() && mIsExpanded && mAnimationsEnabled) {
+ mDismissView.setWillBeGone(true);
+ mDismissView.performVisibilityAnimation(false, dimissHideFinishRunnable);
+ } else {
+ dimissHideFinishRunnable.run();
+ mDismissView.showClearButton();
+ }
}
}
}
public void setDismissAllInProgress(boolean dismissAllInProgress) {
mDismissAllInProgress = dismissAllInProgress;
+ mDismissView.setDismissAllInProgress(dismissAllInProgress);
}
public boolean isDismissViewNotGone() {
@@ -2210,6 +2360,10 @@ public class NotificationStackScrollLayout extends ViewGroup
return height;
}
+ public int getEmptyShadeViewHeight() {
+ return mEmptyShadeView.getHeight();
+ }
+
public float getBottomMostNotificationBottom() {
final int count = getChildCount();
float max = 0;
@@ -2245,6 +2399,24 @@ public class NotificationStackScrollLayout extends ViewGroup
}
}
+ private boolean isBelowLastNotification(float touchX, float touchY) {
+ ExpandableView lastChildNotGone = (ExpandableView) getLastChildNotGone();
+ if (lastChildNotGone == null) {
+ return touchY > mIntrinsicPadding;
+ }
+ 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));
+ }
+ }
+
/**
* A listener that is notified when some child locations might have changed.
*/
@@ -2253,6 +2425,13 @@ public class NotificationStackScrollLayout extends ViewGroup
}
/**
+ * A listener that is notified when the empty space below the notifications is clicked on
+ */
+ public interface OnEmptySpaceClickListener {
+ public void onEmptySpaceClicked(float x, float y);
+ }
+
+ /**
* A listener that gets notified when the overscroll at the top has changed.
*/
public interface OnOverscrollTopChangedListener {
@@ -2348,7 +2527,8 @@ public class NotificationStackScrollLayout extends ViewGroup
// ANIMATION_TYPE_DARK
new AnimationFilter()
- .animateDark(),
+ .animateDark()
+ .hasDelays(),
// ANIMATION_TYPE_GO_TO_FULL_SHADE
new AnimationFilter()
@@ -2446,12 +2626,16 @@ public class NotificationStackScrollLayout extends ViewGroup
static final int ANIMATION_TYPE_VIEW_RESIZE = 12;
static final int ANIMATION_TYPE_EVERYTHING = 13;
+ static final int DARK_ANIMATION_ORIGIN_INDEX_ABOVE = -1;
+ static final int DARK_ANIMATION_ORIGIN_INDEX_BELOW = -2;
+
final long eventStartTime;
final View changingView;
final int animationType;
final AnimationFilter filter;
final long length;
View viewAfterChangingView;
+ int darkAnimationOriginIndex;
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 853628e..ddc4251 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -200,15 +200,25 @@ public class StackScrollAlgorithm {
// apply clipping and shadow
float newNotificationEnd = newYTranslation + newHeight;
- // In the unlocked shade we have to clip a little bit higher because of the rounded
- // corners of the notifications.
- float clippingCorrection = state.dimmed ? 0 : mRoundedRectCornerRadius * state.scale;
-
- // When the previous notification is swiped, we don't clip the content to the
- // bottom of it.
- float clipHeight = previousNotificationIsSwiped
- ? newHeight
- : newNotificationEnd - (previousNotificationEnd - clippingCorrection);
+ float clipHeight;
+ if (previousNotificationIsSwiped) {
+ // When the previous notification is swiped, we don't clip the content to the
+ // bottom of it.
+ clipHeight = newHeight;
+ } else {
+ clipHeight = newNotificationEnd - previousNotificationEnd;
+ clipHeight = Math.max(0.0f, clipHeight);
+ if (clipHeight != 0.0f) {
+
+ // In the unlocked shade we have to clip a little bit higher because of the rounded
+ // corners of the notifications, but only if we are not fully overlapped by
+ // the top card.
+ float clippingCorrection = state.dimmed
+ ? 0
+ : mRoundedRectCornerRadius * state.scale;
+ clipHeight += clippingCorrection;
+ }
+ }
updateChildClippingAndBackground(state, newHeight, clipHeight,
newHeight - (previousNotificationStart - newYTranslation));
@@ -669,7 +679,11 @@ public class StackScrollAlgorithm {
StackScrollState.ViewState childViewState = resultState.getViewStateForView(child);
if (i < algorithmState.itemsInTopStack) {
float stackIndex = algorithmState.itemsInTopStack - i;
- stackIndex = Math.min(stackIndex, MAX_ITEMS_IN_TOP_STACK + 2);
+
+ // Ensure that the topmost item is a little bit higher than the rest when fully
+ // scrolled, to avoid drawing errors when swiping it out
+ float max = MAX_ITEMS_IN_TOP_STACK + (i == 0 ? 2.5f : 2);
+ stackIndex = Math.min(stackIndex, max);
if (i == 0 && algorithmState.itemsInTopStack < 2.0f) {
// We only have the top item and an additional item in the top stack,
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 0967ecd..0b1ce8f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
@@ -119,9 +119,7 @@ public class StackScrollState {
}
// apply alpha
- if (!becomesInvisible) {
- child.setAlpha(newAlpha);
- }
+ child.setAlpha(newAlpha);
}
// apply visibility
@@ -156,7 +154,7 @@ public class StackScrollState {
child.setDimmed(state.dimmed, false /* animate */);
// apply dark
- child.setDark(state.dark, false /* animate */);
+ child.setDark(state.dark, false /* animate */, 0 /* delay */);
// apply hiding sensitive
child.setHideSensitive(
@@ -236,6 +234,8 @@ public class StackScrollState {
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;
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 433357e..b027787 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
@@ -46,6 +46,7 @@ public class StackStateAnimator {
public static final int ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING = 80;
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;
private static final int TAG_ANIMATOR_TRANSLATION_Y = R.id.translation_y_animator_tag;
@@ -161,11 +162,12 @@ public class StackStateAnimator {
boolean scaleChanging = child.getScaleX() != viewState.scale;
boolean alphaChanging = alpha != child.getAlpha();
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;
+ alphaChanging || heightChanging || topInsetChanging || darkChanging;
boolean noAnimation = wasAdded;
long delay = 0;
long duration = mCurrentLength;
@@ -242,7 +244,7 @@ public class StackStateAnimator {
&& !noAnimation);
// start dark animation
- child.setDark(viewState.dark, mAnimationFilter.animateDark && !noAnimation);
+ child.setDark(viewState.dark, mAnimationFilter.animateDark && !noAnimation, delay);
// apply speed bump state
child.setBelowSpeedBump(viewState.belowSpeedBump);
@@ -262,6 +264,9 @@ public class StackStateAnimator {
private long calculateChildAnimationDelay(StackScrollState.ViewState viewState,
StackScrollState finalState) {
+ if (mAnimationFilter.hasDarkEvent) {
+ return calculateDelayDark(viewState);
+ }
if (mAnimationFilter.hasGoToFullShadeEvent) {
return calculateDelayGoToFullShade(viewState);
}
@@ -309,6 +314,20 @@ public class StackStateAnimator {
return minDelay;
}
+ private long calculateDelayDark(StackScrollState.ViewState viewState) {
+ int referenceIndex;
+ if (mAnimationFilter.darkAnimationOriginIndex ==
+ NotificationStackScrollLayout.AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE) {
+ referenceIndex = 0;
+ } else if (mAnimationFilter.darkAnimationOriginIndex ==
+ NotificationStackScrollLayout.AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_BELOW) {
+ referenceIndex = mHostLayout.getNotGoneChildCount() - 1;
+ } else {
+ referenceIndex = mAnimationFilter.darkAnimationOriginIndex;
+ }
+ return Math.abs(referenceIndex - viewState.notGoneIndex) * ANIMATION_DELAY_PER_ELEMENT_DARK;
+ }
+
private long calculateDelayGoToFullShade(StackScrollState.ViewState viewState) {
float index = viewState.notGoneIndex;
index = (float) Math.pow(index, 0.7f);
@@ -477,6 +496,7 @@ public class StackStateAnimator {
if (newEndValue == 0 && !mWasCancelled) {
child.setVisibility(View.INVISIBLE);
}
+ // remove the tag when the animation is finished
child.setTag(TAG_ANIMATOR_ALPHA, null);
child.setTag(TAG_START_ALPHA, null);
child.setTag(TAG_END_ALPHA, null);
@@ -498,13 +518,7 @@ public class StackStateAnimator {
animator.setStartDelay(delay);
}
animator.addListener(getGlobalAnimationFinishedListener());
- // remove the tag when the animation is finished
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- }
- });
startAnimator(animator);
child.setTag(TAG_ANIMATOR_ALPHA, animator);
child.setTag(TAG_START_ALPHA, child.getAlpha());
@@ -799,6 +813,11 @@ public class StackStateAnimator {
mHostLayout.getOverlay().remove(changingView);
}
});
+ } 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);
}
mNewEvents.add(event);
}
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 1b6a9e1..08732e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
@@ -178,4 +178,8 @@ public class TvStatusBar extends BaseStatusBar {
@Override
public void onActivationReset(ActivatableNotificationView view) {
}
+
+ @Override
+ public void showScreenPinningRequest() {
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/IconPulser.java b/packages/SystemUI/src/com/android/systemui/volume/IconPulser.java
new file mode 100644
index 0000000..9438af1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/IconPulser.java
@@ -0,0 +1,48 @@
+/*
+ * 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.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.content.Context;
+import android.view.View;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+
+public class IconPulser {
+ private static final float PULSE_SCALE = 1.1f;
+
+ private final Interpolator mFastOutSlowInInterpolator;
+
+ public IconPulser(Context context) {
+ mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
+ android.R.interpolator.fast_out_slow_in);
+ }
+
+ public void start(final View target) {
+ if (target == null || target.getScaleX() != 1) return; // n/a, or already running
+ target.animate().cancel();
+ target.animate().scaleX(PULSE_SCALE).scaleY(PULSE_SCALE)
+ .setInterpolator(mFastOutSlowInInterpolator)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ target.animate().scaleX(1).scaleY(1).setListener(null);
+ }
+ });
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/SegmentedButtons.java b/packages/SystemUI/src/com/android/systemui/volume/SegmentedButtons.java
index f7f5047..2f02f7c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/SegmentedButtons.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/SegmentedButtons.java
@@ -17,7 +17,6 @@
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,8 +29,6 @@ import com.android.systemui.R;
import java.util.Objects;
public class SegmentedButtons extends LinearLayout {
- private static final Typeface MEDIUM = Typeface.create("sans-serif-medium", Typeface.NORMAL);
- private static final Typeface BOLD = Typeface.create("sans-serif", Typeface.BOLD);
private static final int LABEL_RES_KEY = R.id.label;
private final Context mContext;
@@ -63,15 +60,17 @@ public class SegmentedButtons extends LinearLayout {
final Object tag = c.getTag();
final boolean selected = Objects.equals(mSelectedValue, tag);
c.setSelected(selected);
- c.setTypeface(selected ? BOLD : MEDIUM);
+ c.getCompoundDrawables()[1].setTint(mContext.getResources().getColor(selected
+ ? R.color.segmented_button_selected : R.color.segmented_button_unselected));
}
fireOnSelected();
}
- public void addButton(int labelResId, Object value) {
+ public void addButton(int labelResId, int iconResId, 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);
final LayoutParams lp = (LayoutParams) b.getLayoutParams();
if (getChildCount() == 0) {
lp.leftMargin = lp.rightMargin = 0; // first button has no margin
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java
index 3c186c2..e3f8f3d 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java
@@ -16,8 +16,10 @@
package com.android.systemui.volume;
+import com.android.systemui.DemoMode;
import com.android.systemui.statusbar.policy.ZenModeController;
-public interface VolumeComponent {
+public interface VolumeComponent extends DemoMode {
ZenModeController getZenController();
+ void dismissNow();
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
index 360dee5..acdcfc1 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
@@ -16,6 +16,9 @@
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;
@@ -42,6 +45,8 @@ 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;
@@ -59,12 +64,15 @@ 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;
@@ -76,7 +84,7 @@ import java.io.PrintWriter;
*
* @hide
*/
-public class VolumePanel extends Handler {
+public class VolumePanel extends Handler implements DemoMode {
private static final String TAG = "VolumePanel";
private static boolean LOGD = Log.isLoggable(TAG, Log.DEBUG);
@@ -116,6 +124,7 @@ public class VolumePanel extends Handler {
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;
@@ -129,6 +138,8 @@ public class VolumePanel extends Handler {
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;
@@ -142,6 +153,7 @@ public class VolumePanel extends Handler {
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;
@@ -158,6 +170,8 @@ public class VolumePanel extends Handler {
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;
@@ -166,22 +180,24 @@ public class VolumePanel extends Handler {
/** 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,
- R.drawable.ic_audio_bt,
- R.drawable.ic_audio_bt,
+ 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_vibrate,
+ com.android.systemui.R.drawable.ic_ringer_mute,
false),
VoiceStream(AudioManager.STREAM_VOICE_CALL,
R.string.volume_icon_description_incall,
- R.drawable.ic_audio_phone,
- R.drawable.ic_audio_phone,
+ 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,
@@ -196,7 +212,7 @@ public class VolumePanel extends Handler {
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_vibrate,
+ com.android.systemui.R.drawable.ic_ringer_mute,
true),
// for now, use media resources for master volume
MasterStream(STREAM_MASTER,
@@ -206,8 +222,8 @@ public class VolumePanel extends Handler {
false),
RemoteStream(STREAM_REMOTE_MUSIC,
R.string.volume_icon_description_media, //FIXME should have its own description
- R.drawable.ic_media_route_on_holo_dark,
- R.drawable.ic_media_route_disabled_holo_dark,
+ com.android.systemui.R.drawable.ic_audio_remote,
+ com.android.systemui.R.drawable.ic_audio_remote,
false);// will be dynamically updated
int streamType;
@@ -246,6 +262,8 @@ public class VolumePanel extends Handler {
ImageView icon;
SeekBar seekbarView;
TextView suppressorView;
+ View divider;
+ ImageView secondaryIcon;
int iconRes;
int iconMuteRes;
int iconSuppressedRes;
@@ -254,6 +272,7 @@ public class VolumePanel extends Handler {
// Synchronize when accessing this
private ToneGenerator mToneGenerators[];
private Vibrator mVibrator;
+ private boolean mHasVibrator;
private static AlertDialog sSafetyWarning;
private static Object sSafetyWarningLock = new Object();
@@ -339,6 +358,8 @@ public class VolumePanel extends Handler {
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();
@@ -381,6 +402,8 @@ public class VolumePanel extends Handler {
mActiveStreamType = -1;
mAudioManager.forceVolumeControlStream(mActiveStreamType);
setZenPanelVisible(false);
+ mDemoIcon = 0;
+ mSecondaryIconTransition.cancel();
}
});
@@ -418,10 +441,12 @@ public class VolumePanel extends Handler {
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);
}
@@ -453,8 +478,10 @@ public class VolumePanel extends Handler {
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);
@@ -483,6 +510,9 @@ public class VolumePanel extends Handler {
pw.println();
}
}
+ if (mZenPanel != null) {
+ mZenPanel.dump(fd, pw, args);
+ }
}
private void initZenModePanel() {
@@ -518,6 +548,7 @@ public class VolumePanel extends Handler {
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
@@ -526,7 +557,12 @@ public class VolumePanel extends Handler {
if (AudioManager.RINGER_MODE_CHANGED_ACTION.equals(action)) {
removeMessages(MSG_RINGER_MODE_CHANGED);
- sendMessage(obtainMessage(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)) {
@@ -604,10 +640,12 @@ public class VolumePanel extends Handler {
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;
@@ -620,22 +658,42 @@ public class VolumePanel extends Handler {
sc.iconRes = streamRes.iconRes;
sc.iconMuteRes = streamRes.iconMuteRes;
sc.icon.setImageResource(sc.iconRes);
- sc.icon.setClickable(isNotificationOrRing(streamType));
- if (sc.icon.isClickable()) {
- sc.icon.setSoundEffectsEnabled(false);
- sc.icon.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- resetTimeout();
- toggle(sc);
- }
- });
+ 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);
@@ -645,12 +703,13 @@ public class VolumePanel extends Handler {
}
}
- private void toggle(StreamControl sc) {
- if (mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_NORMAL) {
- mAudioManager.setRingerMode(AudioManager.RINGER_MODE_VIBRATE);
+ 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.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+ mAudioManager.setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL);
postVolumeChanged(sc.streamType, AudioManager.FLAG_PLAY_SOUND);
}
}
@@ -666,7 +725,7 @@ public class VolumePanel extends Handler {
mSliderPanel.addView(active.group);
mActiveStreamType = activeStreamType;
active.group.setVisibility(View.VISIBLE);
- updateSlider(active);
+ updateSlider(active, true /*forceReloadIcon*/);
updateTimeoutDelay();
updateZenPanelVisible();
}
@@ -674,7 +733,7 @@ public class VolumePanel extends Handler {
private void updateSliderProgress(StreamControl sc, int progress) {
final boolean isRinger = isNotificationOrRing(sc.streamType);
- if (isRinger && mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_SILENT) {
+ if (isRinger && mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_SILENT) {
progress = mLastRingerProgress;
}
if (progress < 0) {
@@ -687,30 +746,38 @@ public class VolumePanel extends Handler {
}
private void updateSliderIcon(StreamControl sc, boolean muted) {
+ ComponentName suppressor = null;
if (isNotificationOrRing(sc.streamType)) {
- int ringerMode = mAudioManager.getRingerMode();
+ suppressor = mNotificationEffectsSuppressor;
+ int ringerMode = mAudioManager.getRingerModeInternal();
if (ringerMode == AudioManager.RINGER_MODE_SILENT) {
ringerMode = mLastRingerMode;
} else {
mLastRingerMode = ringerMode;
}
- muted = ringerMode == AudioManager.RINGER_MODE_VIBRATE;
+ if (mHasVibrator) {
+ muted = ringerMode == AudioManager.RINGER_MODE_VIBRATE;
+ } else {
+ muted = false;
+ }
}
- sc.icon.setImageResource(muted ? sc.iconMuteRes : sc.iconRes);
+ sc.icon.setImageResource(mDemoIcon != 0 ? mDemoIcon
+ : suppressor != null ? sc.iconSuppressedRes
+ : muted ? sc.iconMuteRes
+ : sc.iconRes);
}
- private void updateSliderSupressor(StreamControl sc) {
+ private void updateSliderSuppressor(StreamControl sc) {
final ComponentName suppressor = isNotificationOrRing(sc.streamType)
- ? mZenController.getEffectsSuppressor() : null;
+ ? 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(com.android.systemui.R.string.muted_by,
+ sc.suppressorView.setText(mContext.getString(R.string.muted_by,
getSuppressorCaption(suppressor)));
- sc.icon.setImageResource(sc.iconSuppressedRes);
}
}
@@ -734,14 +801,15 @@ public class VolumePanel extends Handler {
}
/** Update the mute and progress state of a slider */
- private void updateSlider(StreamControl sc) {
+ private void updateSlider(StreamControl sc, boolean forceReloadIcon) {
updateSliderProgress(sc, -1);
final boolean muted = isMuted(sc.streamType);
- // Force reloading the image resource
- sc.icon.setImageDrawable(null);
+ if (forceReloadIcon) {
+ sc.icon.setImageDrawable(null);
+ }
updateSliderIcon(sc, muted);
updateSliderEnabled(sc, muted, false);
- updateSliderSupressor(sc);
+ updateSliderSuppressor(sc);
}
private void updateSliderEnabled(final StreamControl sc, boolean muted, boolean fixedVolume) {
@@ -751,13 +819,18 @@ public class VolumePanel extends Handler {
// 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 && mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_SILENT) {
+ } 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() && muted) ||
+ (sc.streamType != mAudioManager.getMasterStreamType() && !isRinger && muted) ||
(sSafetyWarning != null)) {
sc.seekbarView.setEnabled(false);
} else {
@@ -769,7 +842,7 @@ public class VolumePanel extends Handler {
if (isRinger && wasEnabled != sc.seekbarView.isEnabled()) {
if (sc.seekbarView.isEnabled()) {
sc.group.setOnTouchListener(null);
- sc.icon.setClickable(true);
+ sc.icon.setClickable(mHasVibrator);
} else {
final View.OnTouchListener showHintOnTouch = new View.OnTouchListener() {
@Override
@@ -790,6 +863,16 @@ public class VolumePanel extends Handler {
}
}
+ 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;
@@ -800,7 +883,8 @@ public class VolumePanel extends Handler {
}
private void updateTimeoutDelay() {
- mTimeoutDelay = sSafetyWarning != null ? TIMEOUT_DELAY_SAFETY_WARNING
+ 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
@@ -826,11 +910,18 @@ public class VolumePanel extends Handler {
}
}
- public void updateStates() {
+ private void updateStates() {
final int count = mSliderPanel.getChildCount();
for (int i = 0; i < count; i++) {
StreamControl sc = (StreamControl) mSliderPanel.getChildAt(i).getTag();
- updateSlider(sc);
+ updateSlider(sc, true /*forceReloadIcon*/);
+ }
+ }
+
+ private void updateActiveSlider() {
+ final StreamControl active = mStreamControls.get(mActiveStreamType);
+ if (active != null) {
+ updateSlider(active, false /*forceReloadIcon*/);
}
}
@@ -916,6 +1007,14 @@ public class VolumePanel extends Handler {
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
@@ -923,7 +1022,8 @@ public class VolumePanel extends Handler {
*/
protected void onVolumeChanged(int streamType, int flags) {
- if (LOGD) Log.d(mTag, "onVolumeChanged(streamType: " + streamType + ", flags: " + flags + ")");
+ if (LOGD) Log.d(mTag, "onVolumeChanged(streamType: " + streamToString(streamType)
+ + ", flags: " + flagsToString(flags) + ")");
if ((flags & AudioManager.FLAG_SHOW_UI) != 0) {
synchronized (this) {
@@ -952,7 +1052,8 @@ public class VolumePanel extends Handler {
protected void onMuteChanged(int streamType, int flags) {
- if (LOGD) Log.d(mTag, "onMuteChanged(streamType: " + streamType + ", flags: " + flags + ")");
+ if (LOGD) Log.d(mTag, "onMuteChanged(streamType: " + streamToString(streamType)
+ + ", flags: " + flagsToString(flags) + ")");
StreamControl sc = mStreamControls.get(streamType);
if (sc != null) {
@@ -968,8 +1069,8 @@ public class VolumePanel extends Handler {
mRingIsSilent = false;
if (LOGD) {
- Log.d(mTag, "onShowVolumeChanged(streamType: " + streamType
- + ", flags: " + flags + "), index: " + index);
+ Log.d(mTag, "onShowVolumeChanged(streamType: " + streamToString(streamType)
+ + ", flags: " + flagsToString(flags) + "), index: " + index);
}
// get max volume for progress bar
@@ -980,7 +1081,6 @@ public class VolumePanel extends Handler {
switch (streamType) {
case AudioManager.STREAM_RING: {
-// setRingerIcon();
Uri ringuri = RingtoneManager.getActualDefaultRingtoneUri(
mContext, RingtoneManager.TYPE_RINGTONE);
if (ringuri == null) {
@@ -995,7 +1095,7 @@ public class VolumePanel extends Handler {
(AudioManager.DEVICE_OUT_BLUETOOTH_A2DP |
AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES |
AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER)) != 0) {
- setMusicIcon(R.drawable.ic_audio_bt, R.drawable.ic_audio_bt_mute);
+ setMusicIcon(IC_AUDIO_BT, IC_AUDIO_BT_MUTE);
} else {
setMusicIcon(IC_AUDIO_VOL, IC_AUDIO_VOL_MUTE);
}
@@ -1073,14 +1173,25 @@ public class VolumePanel extends Handler {
sc.seekbarView.setMax(max);
}
updateSliderProgress(sc, index);
- updateSliderEnabled(sc, isMuted(streamType),
- (flags & AudioManager.FLAG_FIXED_VOLUME) != 0);
+ 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
- mAudioManager.forceVolumeControlStream(stream);
+ if (stream != STREAM_MASTER) {
+ mAudioManager.forceVolumeControlStream(stream);
+ }
mDialog.show();
if (mCallback != null) {
mCallback.onVisible(true);
@@ -1091,15 +1202,20 @@ public class VolumePanel extends Handler {
// Do a little vibrate if applicable (only when going into vibrate mode)
if ((streamType != STREAM_REMOTE_MUSIC) &&
((flags & AudioManager.FLAG_VIBRATE) != 0) &&
- mAudioManager.isStreamAffectedByRingerMode(streamType) &&
- mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE) {
+ isNotificationOrRing(streamType) &&
+ mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_VIBRATE) {
sendMessageDelayed(obtainMessage(MSG_VIBRATE), VIBRATE_DELAY);
}
- // Pulse the slider icon if an adjustment was suppressed due to silent mode.
+ // 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() {
@@ -1143,16 +1259,17 @@ public class VolumePanel extends Handler {
protected void onVibrate() {
// Make sure we ended up in vibrate ringer mode
- if (mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_VIBRATE) {
+ if (mAudioManager.getRingerModeInternal() != AudioManager.RINGER_MODE_VIBRATE) {
return;
}
-
- mVibrator.vibrate(VIBRATE_DURATION, VIBRATION_ATTRIBUTES);
+ 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: " + flags
- + ")");
+ if (LOGD) Log.d(mTag, "onRemoteVolumeChanged(controller:" + controller + ", flags: "
+ + flagsToString(flags) + ")");
if (((flags & AudioManager.FLAG_SHOW_UI) != 0) || isShowing()) {
synchronized (this) {
@@ -1343,9 +1460,10 @@ public class VolumePanel extends Handler {
}
case MSG_RINGER_MODE_CHANGED:
+ case MSG_INTERNAL_RINGER_MODE_CHANGED:
case MSG_NOTIFICATION_EFFECTS_SUPPRESSOR_CHANGED: {
if (isShowing()) {
- updateStates();
+ updateActiveSlider();
}
break;
}
@@ -1406,6 +1524,22 @@ public class VolumePanel extends Handler {
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) {
@@ -1432,10 +1566,11 @@ public class VolumePanel extends Handler {
public void onZenAvailableChanged(boolean available) {
obtainMessage(MSG_ZEN_MODE_AVAILABLE_CHANGED, available ? 1 : 0, 0).sendToTarget();
}
+
@Override
public void onEffectsSupressorChanged() {
- obtainMessage(MSG_NOTIFICATION_EFFECTS_SUPPRESSOR_CHANGED,
- mZenController.getEffectsSuppressor()).sendToTarget();
+ mNotificationEffectsSuppressor = mZenController.getEffectsSuppressor();
+ sendEmptyMessage(MSG_NOTIFICATION_EFFECTS_SUPPRESSOR_CHANGED);
}
};
@@ -1445,6 +1580,82 @@ public class VolumePanel extends Handler {
}
};
+ 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();
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
index 0586a83..7102c2a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
@@ -1,7 +1,6 @@
package com.android.systemui.volume;
import android.content.Context;
-import android.content.Intent;
import android.content.res.Configuration;
import android.database.ContentObserver;
import android.media.AudioManager;
@@ -11,13 +10,11 @@ import android.media.session.ISessionController;
import android.media.session.MediaController;
import android.media.session.MediaSessionManager;
import android.net.Uri;
-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.WindowManagerGlobal;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
@@ -53,6 +50,7 @@ public class VolumeUI extends SystemUI {
private final Handler mHandler = new Handler();
+ private boolean mEnabled;
private AudioManager mAudioManager;
private MediaSessionManager mMediaSessionManager;
private VolumeController mVolumeController;
@@ -63,6 +61,8 @@ public class VolumeUI extends SystemUI {
@Override
public void start() {
+ mEnabled = mContext.getResources().getBoolean(R.bool.enable_volume_ui);
+ if (!mEnabled) return;
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
mMediaSessionManager = (MediaSessionManager) mContext
.getSystemService(Context.MEDIA_SESSION_SERVICE);
@@ -84,6 +84,7 @@ public class VolumeUI extends SystemUI {
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.print("mEnabled="); pw.println(mEnabled);
if (mPanel != null) {
mPanel.dump(fd, pw, args);
}
@@ -177,13 +178,23 @@ public class VolumeUI extends SystemUI {
@Override
public void dismiss() throws RemoteException {
- mPanel.postDismiss(0);
+ dismissNow();
}
@Override
public ZenModeController getZenController() {
return mPanel.getZenController();
}
+
+ @Override
+ public void dispatchDemoCommand(String command, Bundle args) {
+ mPanel.dispatchDemoCommand(command, args);
+ }
+
+ @Override
+ public void dismissNow() {
+ mPanel.postDismiss(0);
+ }
}
private final class RemoteVolumeController extends IRemoteVolumeController.Stub {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
index ea431ae..d40a2c0 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
@@ -16,14 +16,17 @@
package com.android.systemui.volume;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
+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.net.Uri;
+import android.os.AsyncTask;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -31,11 +34,14 @@ import android.provider.Settings;
import android.provider.Settings.Global;
import android.service.notification.Condition;
import android.service.notification.ZenModeConfig;
+import android.text.TextUtils;
+import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.Log;
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;
@@ -48,6 +54,8 @@ import android.widget.TextView;
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.Objects;
@@ -65,9 +73,7 @@ public class ZenModePanel extends LinearLayout {
private static final int MAX_BUCKET_MINUTES = MINUTE_BUCKETS[MINUTE_BUCKETS.length - 1];
private static final int DEFAULT_BUCKET_INDEX = Arrays.binarySearch(MINUTE_BUCKETS, 60);
private static final int FOREVER_CONDITION_INDEX = 0;
- private static final int TIME_CONDITION_INDEX = 1;
- private static final int FIRST_CONDITION_INDEX = 2;
- private static final float SILENT_HINT_PULSE_SCALE = 1.1f;
+ private static final int COUNTDOWN_CONDITION_INDEX = 1;
public static final Intent ZEN_SETTINGS = new Intent(Settings.ACTION_ZEN_MODE_SETTINGS);
@@ -75,10 +81,16 @@ public class ZenModePanel extends LinearLayout {
private final LayoutInflater mInflater;
private final H mHandler = new H();
private final Prefs mPrefs;
- private final Interpolator mFastOutSlowInInterpolator;
+ private final IconPulser mIconPulser;
private final int mSubheadWarningColor;
private final int mSubheadColor;
- private final ZenToast mZenToast;
+ 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 String mTag = TAG + "/" + Integer.toHexString(System.identityHashCode(this));
@@ -96,9 +108,10 @@ public class ZenModePanel extends LinearLayout {
private String mExitConditionText;
private int mBucketIndex = -1;
private boolean mExpanded;
- private boolean mHidden = false;
+ private boolean mHidden;
private int mSessionZen;
private int mAttachedZen;
+ private boolean mAttached;
private Condition mSessionExitCondition;
private Condition[] mConditions;
private Condition mTimeCondition;
@@ -108,26 +121,53 @@ public class ZenModePanel extends LinearLayout {
mContext = context;
mPrefs = new Prefs();
mInflater = LayoutInflater.from(mContext.getApplicationContext());
- mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(mContext,
- android.R.interpolator.fast_out_slow_in);
+ mIconPulser = new IconPulser(mContext);
final Resources res = mContext.getResources();
mSubheadWarningColor = res.getColor(R.color.system_warning_color);
mSubheadColor = res.getColor(R.color.qs_subhead);
- mZenToast = new ZenToast(mContext);
+ 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();
if (DEBUG) Log.d(mTag, "new ZenModePanel");
}
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("ZenModePanel state:");
+ pw.print(" mCountdownConditionSupported="); pw.println(mCountdownConditionSupported);
+ pw.print(" mMaxConditions="); pw.println(mMaxConditions);
+ pw.print(" mRequestingConditions="); pw.println(mRequestingConditions);
+ pw.print(" mAttached="); pw.println(mAttached);
+ pw.print(" mHidden="); pw.println(mHidden);
+ pw.print(" mExpanded="); pw.println(mExpanded);
+ pw.print(" mSessionZen="); pw.println(mSessionZen);
+ pw.print(" mAttachedZen="); pw.println(mAttachedZen);
+ mTransitionHelper.dump(fd, pw, args);
+ }
+
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mZenButtons = (SegmentedButtons) findViewById(R.id.zen_buttons);
- mZenButtons.addButton(R.string.interruption_level_none, Global.ZEN_MODE_NO_INTERRUPTIONS);
- mZenButtons.addButton(R.string.interruption_level_priority,
+ mZenButtons.addButton(R.string.interruption_level_none, R.drawable.ic_zen_none,
+ Global.ZEN_MODE_NO_INTERRUPTIONS);
+ mZenButtons.addButton(R.string.interruption_level_priority, R.drawable.ic_zen_important,
Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS);
- mZenButtons.addButton(R.string.interruption_level_all, Global.ZEN_MODE_OFF);
+ 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);
@@ -152,18 +192,37 @@ public class ZenModePanel extends LinearLayout {
Interaction.register(mMoreSettings, mInteractionCallback);
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));
+ }
+
+ setLayoutTransition(newLayoutTransition(mTransitionHelper));
+ }
+
+ 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);
+ }
+ return transition;
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (DEBUG) Log.d(mTag, "onAttachedToWindow");
- mZenToast.hide();
+ mAttached = true;
mAttachedZen = getSelectedZen(-1);
mSessionZen = mAttachedZen;
- mSessionExitCondition = copy(mExitCondition);
+ mTransitionHelper.clear();
+ setSessionExitCondition(copy(mExitCondition));
refreshExitConditionText();
updateWidgets();
+ setRequestingConditions(!mHidden);
}
@Override
@@ -171,15 +230,26 @@ public class ZenModePanel extends LinearLayout {
super.onDetachedFromWindow();
if (DEBUG) Log.d(mTag, "onDetachedFromWindow");
checkForAttachedZenChange();
+ mAttached = false;
mAttachedZen = -1;
mSessionZen = -1;
- mSessionExitCondition = null;
+ setSessionExitCondition(null);
setExpanded(false);
+ setRequestingConditions(false);
+ mTransitionHelper.clear();
+ }
+
+ private void setSessionExitCondition(Condition condition) {
+ if (Objects.equals(condition, mSessionExitCondition)) return;
+ if (DEBUG) Log.d(mTag, "mSessionExitCondition=" + getConditionId(condition));
+ mSessionExitCondition = condition;
}
public void setHidden(boolean hidden) {
if (mHidden == hidden) return;
+ if (DEBUG) Log.d(mTag, "hidden=" + hidden);
mHidden = hidden;
+ setRequestingConditions(mAttached && !mHidden);
updateWidgets();
}
@@ -191,28 +261,31 @@ public class ZenModePanel extends LinearLayout {
if (selectedZen == Global.ZEN_MODE_NO_INTERRUPTIONS) {
mPrefs.trackNoneSelected();
}
- if (selectedZen == Global.ZEN_MODE_NO_INTERRUPTIONS
- || selectedZen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
- mZenToast.show(selectedZen);
- }
}
}
private void setExpanded(boolean expanded) {
if (expanded == mExpanded) return;
mExpanded = expanded;
+ if (mExpanded) {
+ ensureSelection();
+ }
updateWidgets();
- setRequestingConditions(mExpanded);
fireExpanded();
}
/** Start or stop requesting relevant zen mode exit conditions */
- private void setRequestingConditions(boolean requesting) {
+ private void setRequestingConditions(final boolean requesting) {
if (mRequestingConditions == requesting) return;
if (DEBUG) Log.d(mTag, "setRequestingConditions " + requesting);
mRequestingConditions = requesting;
if (mController != null) {
- mController.requestConditions(mRequestingConditions);
+ AsyncTask.execute(new Runnable() {
+ @Override
+ public void run() {
+ mController.requestConditions(requesting);
+ }
+ });
}
if (mRequestingConditions) {
mTimeCondition = parseExistingTimeCondition(mExitCondition);
@@ -220,13 +293,14 @@ public class ZenModePanel extends LinearLayout {
mBucketIndex = -1;
} else {
mBucketIndex = DEFAULT_BUCKET_INDEX;
- mTimeCondition = ZenModeConfig.toTimeCondition(MINUTE_BUCKETS[mBucketIndex]);
+ mTimeCondition = ZenModeConfig.toTimeCondition(mContext,
+ MINUTE_BUCKETS[mBucketIndex], ActivityManager.getCurrentUser());
}
if (DEBUG) Log.d(mTag, "Initial bucket index: " + mBucketIndex);
mConditions = null; // reset conditions
handleUpdateConditions();
} else {
- mZenConditions.removeAllViews();
+ hideAllConditions();
}
}
@@ -237,7 +311,7 @@ public class ZenModePanel extends LinearLayout {
mSessionZen = getSelectedZen(-1);
handleUpdateZen(mController.getZen());
if (DEBUG) Log.d(mTag, "init mExitCondition=" + mExitCondition);
- mZenConditions.removeAllViews();
+ hideAllConditions();
mController.addCallback(mZenCallback);
}
@@ -246,8 +320,9 @@ public class ZenModePanel extends LinearLayout {
}
private void setExitCondition(Condition exitCondition) {
- if (sameConditionId(mExitCondition, exitCondition)) return;
+ if (Objects.equals(mExitCondition, exitCondition)) return;
mExitCondition = exitCondition;
+ if (DEBUG) Log.d(mTag, "mExitCondition=" + getConditionId(mExitCondition));
refreshExitConditionText();
updateWidgets();
}
@@ -265,12 +340,11 @@ public class ZenModePanel extends LinearLayout {
}
private void refreshExitConditionText() {
- final String forever = mContext.getString(com.android.internal.R.string.zen_mode_forever);
if (mExitCondition == null) {
- mExitConditionText = forever;
- } else if (ZenModeConfig.isValidCountdownConditionId(mExitCondition.id)) {
+ mExitConditionText = foreverSummary();
+ } else if (isCountdown(mExitCondition)) {
final Condition condition = parseExistingTimeCondition(mExitCondition);
- mExitConditionText = condition != null ? condition.summary : forever;
+ mExitConditionText = condition != null ? condition.summary : foreverSummary();
} else {
mExitConditionText = mExitCondition.summary;
}
@@ -284,16 +358,7 @@ public class ZenModePanel extends LinearLayout {
if (DEBUG) Log.d(mTag, "showSilentHint");
if (mZenButtons == null || mZenButtons.getChildCount() == 0) return;
final View noneButton = mZenButtons.getChildAt(0);
- if (noneButton.getScaleX() != 1) return; // already running
- noneButton.animate().cancel();
- noneButton.animate().scaleX(SILENT_HINT_PULSE_SCALE).scaleY(SILENT_HINT_PULSE_SCALE)
- .setInterpolator(mFastOutSlowInInterpolator)
- .setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- noneButton.animate().scaleX(1).scaleY(1).setListener(null);
- }
- });
+ mIconPulser.start(noneButton);
}
private void handleUpdateZen(int zen) {
@@ -303,6 +368,24 @@ public class ZenModePanel extends LinearLayout {
}
mZenButtons.setSelectedValue(zen);
updateWidgets();
+ handleUpdateConditions();
+ if (mExpanded) {
+ final Condition selected = getSelectedCondition();
+ if (!Objects.equals(mExitCondition, selected)) {
+ select(selected);
+ }
+ }
+ }
+
+ private Condition getSelectedCondition() {
+ final int N = getVisibleConditions();
+ for (int i = 0; i < N; i++) {
+ final ConditionTag tag = getConditionTagAt(i);
+ if (tag != null && tag.rb.isChecked()) {
+ return tag.condition;
+ }
+ }
+ return null;
}
private int getSelectedZen(int defValue) {
@@ -311,6 +394,10 @@ public class ZenModePanel extends LinearLayout {
}
private void updateWidgets() {
+ if (mTransitionHelper.isTransitioning()) {
+ mTransitionHelper.pendingUpdateWidgets();
+ 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;
@@ -339,109 +426,183 @@ public class ZenModePanel extends LinearLayout {
if (condition == null) return null;
final long time = ZenModeConfig.tryParseCountdownConditionId(condition.id);
if (time == 0) return null;
- final long span = time - System.currentTimeMillis();
+ final long now = System.currentTimeMillis();
+ final long span = time - now;
if (span <= 0 || span > MAX_BUCKET_MINUTES * MINUTES_MS) return null;
- return ZenModeConfig.toTimeCondition(time, Math.round(span / (float) MINUTES_MS));
+ return ZenModeConfig.toTimeCondition(mContext,
+ time, Math.round(span / (float) MINUTES_MS), now, ActivityManager.getCurrentUser());
}
private void handleUpdateConditions(Condition[] conditions) {
+ conditions = trimConditions(conditions);
+ if (Arrays.equals(conditions, mConditions)) {
+ final int count = mConditions == null ? 0 : mConditions.length;
+ if (DEBUG) Log.d(mTag, "handleUpdateConditions unchanged conditionCount=" + count);
+ return;
+ }
mConditions = conditions;
handleUpdateConditions();
}
+ private Condition[] trimConditions(Condition[] conditions) {
+ if (conditions == null || conditions.length <= mMaxOptionalConditions) {
+ // no need to trim
+ return conditions;
+ }
+ // look for current exit condition, ensure it is included if found
+ int found = -1;
+ for (int i = 0; i < conditions.length; i++) {
+ final Condition c = conditions[i];
+ if (mSessionExitCondition != null && sameConditionId(mSessionExitCondition, c)) {
+ found = i;
+ break;
+ }
+ }
+ final Condition[] rt = Arrays.copyOf(conditions, mMaxOptionalConditions);
+ if (found >= mMaxOptionalConditions) {
+ // found after the first N, promote to the end of the first N
+ rt[mMaxOptionalConditions - 1] = conditions[found];
+ }
+ return rt;
+ }
+
private void handleUpdateConditions() {
+ if (mTransitionHelper.isTransitioning()) {
+ mTransitionHelper.pendingUpdateConditions();
+ return;
+ }
final int conditionCount = mConditions == null ? 0 : mConditions.length;
if (DEBUG) Log.d(mTag, "handleUpdateConditions conditionCount=" + conditionCount);
- for (int i = mZenConditions.getChildCount() - 1; i >= FIRST_CONDITION_INDEX; i--) {
- mZenConditions.removeViewAt(i);
- }
// forever
- bind(null, mZenConditions.getChildAt(FOREVER_CONDITION_INDEX));
+ bind(forever(), mZenConditions.getChildAt(FOREVER_CONDITION_INDEX));
// countdown
- bind(mTimeCondition, mZenConditions.getChildAt(TIME_CONDITION_INDEX));
+ if (mCountdownConditionSupported && mTimeCondition != null) {
+ bind(mTimeCondition, mZenConditions.getChildAt(COUNTDOWN_CONDITION_INDEX));
+ }
// provider conditions
- boolean foundDowntime = false;
for (int i = 0; i < conditionCount; i++) {
- bind(mConditions[i], mZenConditions.getChildAt(FIRST_CONDITION_INDEX + i));
- foundDowntime |= isDowntime(mConditions[i]);
+ bind(mConditions[i], mZenConditions.getChildAt(mFirstConditionIndex + i));
}
- // ensure downtime exists, if active
- if (isDowntime(mSessionExitCondition) && !foundDowntime) {
- bind(mSessionExitCondition, null);
+ // hide the rest
+ for (int i = mZenConditions.getChildCount() - 1; i > mFirstConditionIndex + conditionCount;
+ i--) {
+ mZenConditions.getChildAt(i).setVisibility(GONE);
}
// ensure something is selected
- checkForDefault();
+ if (mExpanded) {
+ ensureSelection();
+ }
}
- private static boolean isDowntime(Condition c) {
- return ZenModeConfig.isValidDowntimeConditionId(getConditionId(c));
+ private Condition forever() {
+ return new Condition(mForeverId, foreverSummary(), "", "", 0 /*icon*/, Condition.STATE_TRUE,
+ 0 /*flags*/);
+ }
+
+ private String foreverSummary() {
+ return mContext.getString(com.android.internal.R.string.zen_mode_forever);
}
private ConditionTag getConditionTagAt(int index) {
return (ConditionTag) mZenConditions.getChildAt(index).getTag();
}
- private void checkForDefault() {
+ private int getVisibleConditions() {
+ int rt = 0;
+ final int N = mZenConditions.getChildCount();
+ for (int i = 0; i < N; i++) {
+ rt += mZenConditions.getChildAt(i).getVisibility() == VISIBLE ? 1 : 0;
+ }
+ return rt;
+ }
+
+ private void hideAllConditions() {
+ final int N = mZenConditions.getChildCount();
+ for (int i = 0; i < N; i++) {
+ mZenConditions.getChildAt(i).setVisibility(GONE);
+ }
+ }
+
+ private void ensureSelection() {
// are we left without anything selected? if so, set a default
- for (int i = 0; i < mZenConditions.getChildCount(); i++) {
- if (getConditionTagAt(i).rb.isChecked()) {
- if (DEBUG) Log.d(mTag, "Not selecting a default, checked="
- + getConditionTagAt(i).condition);
+ final int visibleConditions = getVisibleConditions();
+ if (visibleConditions == 0) return;
+ for (int i = 0; i < visibleConditions; i++) {
+ final ConditionTag tag = getConditionTagAt(i);
+ if (tag != null && tag.rb.isChecked()) {
+ if (DEBUG) Log.d(mTag, "Not selecting a default, checked=" + tag.condition);
return;
}
}
+ final ConditionTag foreverTag = getConditionTagAt(FOREVER_CONDITION_INDEX);
+ if (foreverTag == null) return;
if (DEBUG) Log.d(mTag, "Selecting a default");
final int favoriteIndex = mPrefs.getMinuteIndex();
- if (favoriteIndex == -1) {
- getConditionTagAt(FOREVER_CONDITION_INDEX).rb.setChecked(true);
+ if (favoriteIndex == -1 || !mCountdownConditionSupported) {
+ foreverTag.rb.setChecked(true);
} else {
- mTimeCondition = ZenModeConfig.toTimeCondition(MINUTE_BUCKETS[favoriteIndex]);
+ mTimeCondition = ZenModeConfig.toTimeCondition(mContext,
+ MINUTE_BUCKETS[favoriteIndex], ActivityManager.getCurrentUser());
mBucketIndex = favoriteIndex;
- bind(mTimeCondition, mZenConditions.getChildAt(TIME_CONDITION_INDEX));
- getConditionTagAt(TIME_CONDITION_INDEX).rb.setChecked(true);
+ bind(mTimeCondition, mZenConditions.getChildAt(COUNTDOWN_CONDITION_INDEX));
+ getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.setChecked(true);
}
}
private void handleExitConditionChanged(Condition exitCondition) {
setExitCondition(exitCondition);
if (DEBUG) Log.d(mTag, "handleExitConditionChanged " + mExitCondition);
- final int N = mZenConditions.getChildCount();
+ final int N = getVisibleConditions();
for (int i = 0; i < N; i++) {
final ConditionTag tag = getConditionTagAt(i);
- tag.rb.setChecked(sameConditionId(tag.condition, mExitCondition));
+ if (tag != null) {
+ if (sameConditionId(tag.condition, mExitCondition)) {
+ bind(exitCondition, mZenConditions.getChildAt(i));
+ }
+ }
}
}
- private void bind(final Condition condition, View convertView) {
- final boolean enabled = condition == null || condition.state == Condition.STATE_TRUE;
- final View row;
- if (convertView == null) {
- row = mInflater.inflate(R.layout.zen_mode_condition, this, false);
- if (DEBUG) Log.d(mTag, "Adding new condition view for: " + condition);
- mZenConditions.addView(row);
- } else {
- row = convertView;
- }
+ private boolean isCountdown(Condition c) {
+ return c != null && ZenModeConfig.isValidCountdownConditionId(c.id);
+ }
+
+ private boolean isForever(Condition c) {
+ return c != null && mForeverId.equals(c.id);
+ }
+
+ private void bind(final Condition condition, final View row) {
+ if (condition == null) throw new IllegalArgumentException("condition must not be null");
+ final boolean enabled = condition.state == Condition.STATE_TRUE;
final ConditionTag tag =
row.getTag() != null ? (ConditionTag) row.getTag() : new ConditionTag();
row.setTag(tag);
+ final boolean first = tag.rb == null;
if (tag.rb == null) {
tag.rb = (RadioButton) row.findViewById(android.R.id.checkbox);
}
tag.condition = condition;
+ final Uri conditionId = getConditionId(tag.condition);
+ if (DEBUG) Log.d(mTag, "bind i=" + mZenConditions.indexOfChild(row) + " first=" + first
+ + " condition=" + conditionId);
tag.rb.setEnabled(enabled);
- if (sameConditionId(mSessionExitCondition, tag.condition)) {
- tag.rb.setChecked(true);
+ final boolean checked = (mSessionExitCondition != null
+ || mAttachedZen != Global.ZEN_MODE_OFF)
+ && (sameConditionId(mSessionExitCondition, tag.condition)
+ || isCountdown(mSessionExitCondition) && isCountdown(tag.condition));
+ if (checked != tag.rb.isChecked()) {
+ if (DEBUG) Log.d(mTag, "bind checked=" + checked + " condition=" + conditionId);
+ tag.rb.setChecked(checked);
}
tag.rb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (mExpanded && isChecked) {
- if (DEBUG) Log.d(mTag, "onCheckedChanged " + tag.condition);
- final int N = mZenConditions.getChildCount();
+ if (DEBUG) Log.d(mTag, "onCheckedChanged " + conditionId);
+ final int N = getVisibleConditions();
for (int i = 0; i < N; i++) {
- ConditionTag childTag = getConditionTagAt(i);
- if (childTag == tag) continue;
+ final ConditionTag childTag = getConditionTagAt(i);
+ if (childTag == null || childTag == tag) continue;
childTag.rb.setChecked(false);
}
select(tag.condition);
@@ -450,16 +611,27 @@ public class ZenModePanel extends LinearLayout {
}
});
- if (tag.title == null) {
- tag.title = (TextView) row.findViewById(android.R.id.title);
+ if (tag.lines == null) {
+ tag.lines = row.findViewById(android.R.id.content);
+ }
+ if (tag.line1 == null) {
+ tag.line1 = (TextView) row.findViewById(android.R.id.text1);
}
- if (condition == null) {
- tag.title.setText(mContext.getString(com.android.internal.R.string.zen_mode_forever));
+ if (tag.line2 == null) {
+ tag.line2 = (TextView) row.findViewById(android.R.id.text2);
+ }
+ final String line1 = !TextUtils.isEmpty(condition.line1) ? condition.line1
+ : condition.summary;
+ final String line2 = condition.line2;
+ tag.line1.setText(line1);
+ if (TextUtils.isEmpty(line2)) {
+ tag.line2.setVisibility(GONE);
} else {
- tag.title.setText(condition.summary);
+ tag.line2.setVisibility(VISIBLE);
+ tag.line2.setText(line2);
}
- tag.title.setEnabled(enabled);
- tag.title.setAlpha(enabled ? 1 : .4f);
+ tag.lines.setEnabled(enabled);
+ tag.lines.setAlpha(enabled ? 1 : .4f);
final ImageView button1 = (ImageView) row.findViewById(android.R.id.button1);
button1.setOnClickListener(new OnClickListener() {
@@ -476,38 +648,42 @@ public class ZenModePanel extends LinearLayout {
onClickTimeButton(row, tag, true /*up*/);
}
});
- tag.title.setOnClickListener(new OnClickListener() {
+ tag.lines.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
tag.rb.setChecked(true);
}
});
- final long time = ZenModeConfig.tryParseCountdownConditionId(getConditionId(tag.condition));
+ final long time = ZenModeConfig.tryParseCountdownConditionId(conditionId);
if (time > 0) {
+ button1.setVisibility(VISIBLE);
+ button2.setVisibility(VISIBLE);
if (mBucketIndex > -1) {
button1.setEnabled(mBucketIndex > 0);
button2.setEnabled(mBucketIndex < MINUTE_BUCKETS.length - 1);
} else {
final long span = time - System.currentTimeMillis();
button1.setEnabled(span > MIN_BUCKET_MINUTES * MINUTES_MS);
- final Condition maxCondition = ZenModeConfig.toTimeCondition(MAX_BUCKET_MINUTES);
+ final Condition maxCondition = ZenModeConfig.toTimeCondition(mContext,
+ MAX_BUCKET_MINUTES, ActivityManager.getCurrentUser());
button2.setEnabled(!Objects.equals(condition.summary, maxCondition.summary));
}
button1.setAlpha(button1.isEnabled() ? 1f : .5f);
button2.setAlpha(button2.isEnabled() ? 1f : .5f);
} else {
- button1.setVisibility(View.GONE);
- button2.setVisibility(View.GONE);
+ button1.setVisibility(GONE);
+ button2.setVisibility(GONE);
}
// wire up interaction callbacks for newly-added condition rows
- if (convertView == null) {
+ if (first) {
Interaction.register(tag.rb, mInteractionCallback);
- Interaction.register(tag.title, mInteractionCallback);
+ Interaction.register(tag.lines, mInteractionCallback);
Interaction.register(button1, mInteractionCallback);
Interaction.register(button2, mInteractionCallback);
}
+ row.setVisibility(VISIBLE);
}
private void announceConditionSelection(ConditionTag tag) {
@@ -524,7 +700,7 @@ public class ZenModePanel extends LinearLayout {
return;
}
announceForAccessibility(mContext.getString(R.string.zen_mode_and_condition, modeText,
- tag.title.getText()));
+ tag.line1.getText()));
}
private void onClickTimeButton(View row, ConditionTag tag, boolean up) {
@@ -541,18 +717,21 @@ public class ZenModePanel extends LinearLayout {
final long bucketTime = now + bucketMinutes * MINUTES_MS;
if (up && bucketTime > time || !up && bucketTime < time) {
mBucketIndex = j;
- newCondition = ZenModeConfig.toTimeCondition(bucketTime, bucketMinutes);
+ newCondition = ZenModeConfig.toTimeCondition(mContext,
+ bucketTime, bucketMinutes, now, ActivityManager.getCurrentUser());
break;
}
}
if (newCondition == null) {
mBucketIndex = DEFAULT_BUCKET_INDEX;
- newCondition = ZenModeConfig.toTimeCondition(MINUTE_BUCKETS[mBucketIndex]);
+ newCondition = ZenModeConfig.toTimeCondition(mContext,
+ MINUTE_BUCKETS[mBucketIndex], ActivityManager.getCurrentUser());
}
} else {
// on a known index, simply increment or decrement
mBucketIndex = Math.max(0, Math.min(N - 1, mBucketIndex + (up ? 1 : -1)));
- newCondition = ZenModeConfig.toTimeCondition(MINUTE_BUCKETS[mBucketIndex]);
+ newCondition = ZenModeConfig.toTimeCondition(mContext,
+ MINUTE_BUCKETS[mBucketIndex], ActivityManager.getCurrentUser());
}
mTimeCondition = newCondition;
bind(mTimeCondition, row);
@@ -561,18 +740,24 @@ public class ZenModePanel extends LinearLayout {
announceConditionSelection(tag);
}
- private void select(Condition condition) {
+ private void select(final Condition condition) {
if (DEBUG) Log.d(mTag, "select " + condition);
+ final boolean isForever = isForever(condition);
if (mController != null) {
- mController.setExitCondition(condition);
+ AsyncTask.execute(new Runnable() {
+ @Override
+ public void run() {
+ mController.setExitCondition(isForever ? null : condition);
+ }
+ });
}
setExitCondition(condition);
- if (condition == null) {
+ if (isForever) {
mPrefs.setMinuteIndex(-1);
- } else if (ZenModeConfig.isValidCountdownConditionId(condition.id) && mBucketIndex != -1) {
+ } else if (isCountdown(condition) && mBucketIndex != -1) {
mPrefs.setMinuteIndex(mBucketIndex);
}
- mSessionExitCondition = copy(condition);
+ setSessionExitCondition(copy(condition));
}
private void fireMoreSettings() {
@@ -639,7 +824,9 @@ public class ZenModePanel extends LinearLayout {
// used as the view tag on condition rows
private static class ConditionTag {
RadioButton rb;
- TextView title;
+ View lines;
+ TextView line1;
+ TextView line2;
Condition condition;
}
@@ -690,7 +877,7 @@ public class ZenModePanel extends LinearLayout {
}
private SharedPreferences prefs() {
- return mContext.getSharedPreferences(ZenModePanel.class.getSimpleName(), 0);
+ return mContext.getSharedPreferences(mContext.getPackageName(), 0);
}
private void updateMinuteIndex() {
@@ -714,10 +901,15 @@ public class ZenModePanel extends LinearLayout {
private final SegmentedButtons.Callback mZenButtonsCallback = new SegmentedButtons.Callback() {
@Override
- public void onSelected(Object value) {
+ public void onSelected(final Object value) {
if (value != null && mZenButtons.isShown()) {
if (DEBUG) Log.d(mTag, "mZenButtonsCallback selected=" + value);
- mController.setZen((Integer) value);
+ AsyncTask.execute(new Runnable() {
+ @Override
+ public void run() {
+ mController.setZen((Integer) value);
+ }
+ });
}
}
@@ -733,4 +925,79 @@ public class ZenModePanel extends LinearLayout {
fireInteraction();
}
};
+
+ private final class TransitionHelper implements TransitionListener, Runnable {
+ private final ArraySet<View> mTransitioningViews = new ArraySet<View>();
+
+ private boolean mTransitioning;
+ private boolean mPendingUpdateConditions;
+ private boolean mPendingUpdateWidgets;
+
+ public void clear() {
+ mTransitioningViews.clear();
+ mPendingUpdateConditions = mPendingUpdateWidgets = false;
+ }
+
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println(" TransitionHelper state:");
+ pw.print(" mPendingUpdateConditions="); pw.println(mPendingUpdateConditions);
+ pw.print(" mPendingUpdateWidgets="); pw.println(mPendingUpdateWidgets);
+ pw.print(" mTransitioning="); pw.println(mTransitioning);
+ pw.print(" mTransitioningViews="); pw.println(mTransitioningViews);
+ }
+
+ public void pendingUpdateConditions() {
+ mPendingUpdateConditions = true;
+ }
+
+ public void pendingUpdateWidgets() {
+ mPendingUpdateWidgets = true;
+ }
+
+ public boolean isTransitioning() {
+ return !mTransitioningViews.isEmpty();
+ }
+
+ @Override
+ public void startTransition(LayoutTransition transition,
+ ViewGroup container, View view, int transitionType) {
+ mTransitioningViews.add(view);
+ updateTransitioning();
+ }
+
+ @Override
+ public void endTransition(LayoutTransition transition,
+ ViewGroup container, View view, int transitionType) {
+ mTransitioningViews.remove(view);
+ updateTransitioning();
+ }
+
+ @Override
+ public void run() {
+ if (DEBUG) Log.d(mTag, "TransitionHelper run"
+ + " mPendingUpdateWidgets=" + mPendingUpdateWidgets
+ + " mPendingUpdateConditions=" + mPendingUpdateConditions);
+ if (mPendingUpdateWidgets) {
+ updateWidgets();
+ }
+ if (mPendingUpdateConditions) {
+ handleUpdateConditions();
+ }
+ mPendingUpdateWidgets = mPendingUpdateConditions = false;
+ }
+
+ private void updateTransitioning() {
+ final boolean transitioning = isTransitioning();
+ if (mTransitioning == transitioning) return;
+ mTransitioning = transitioning;
+ if (DEBUG) Log.d(mTag, "TransitionHelper mTransitioning=" + mTransitioning);
+ if (!mTransitioning) {
+ if (mPendingUpdateConditions || mPendingUpdateWidgets) {
+ mHandler.post(this);
+ } else {
+ mPendingUpdateConditions = mPendingUpdateWidgets = false;
+ }
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenToast.java b/packages/SystemUI/src/com/android/systemui/volume/ZenToast.java
deleted file mode 100644
index d887712..0000000
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenToast.java
+++ /dev/null
@@ -1,163 +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.volume;
-
-import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
-import static android.provider.Settings.Global.ZEN_MODE_NO_INTERRUPTIONS;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Resources;
-import android.graphics.PixelFormat;
-import android.os.Handler;
-import android.os.Message;
-import android.os.UserHandle;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnAttachStateChangeListener;
-import android.view.WindowManager;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import com.android.systemui.R;
-
-public class ZenToast {
- private static final String ACTION_SHOW = ZenToast.class.getName() + ".SHOW";
- private static final String ACTION_HIDE = ZenToast.class.getName() + ".HIDE";
- private static final String EXTRA_ZEN = "zen";
- private static final String EXTRA_TEXT = "text";
-
- private static final int MSG_SHOW = 1;
- private static final int MSG_HIDE = 2;
-
- private final Context mContext;
- private final WindowManager mWindowManager;
-
- private View mZenToast;
-
- public ZenToast(Context context) {
- mContext = context;
- mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
- final IntentFilter filter = new IntentFilter();
- filter.addAction(ACTION_SHOW);
- filter.addAction(ACTION_HIDE);
- mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, mHandler);
- }
-
- public void show(int zen) {
- mHandler.removeMessages(MSG_HIDE);
- mHandler.removeMessages(MSG_SHOW);
- mHandler.obtainMessage(MSG_SHOW, zen, 0).sendToTarget();
- }
-
- public void hide() {
- mHandler.removeMessages(MSG_HIDE);
- mHandler.removeMessages(MSG_SHOW);
- mHandler.obtainMessage(MSG_HIDE).sendToTarget();
- }
-
- private void handleShow(int zen, String overrideText) {
- handleHide();
-
- String text;
- final int iconRes;
- switch (zen) {
- case ZEN_MODE_NO_INTERRUPTIONS:
- text = mContext.getString(R.string.zen_no_interruptions);
- iconRes = R.drawable.ic_zen_none;
- break;
- case ZEN_MODE_IMPORTANT_INTERRUPTIONS:
- text = mContext.getString(R.string.zen_important_interruptions);
- iconRes = R.drawable.ic_zen_important;
- break;
- default:
- return;
- }
- if (overrideText != null) {
- text = overrideText;
- }
- final Resources res = mContext.getResources();
- final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
- params.height = WindowManager.LayoutParams.WRAP_CONTENT;
- params.width = res.getDimensionPixelSize(R.dimen.zen_toast_width);
- params.format = PixelFormat.TRANSLUCENT;
- params.windowAnimations = R.style.ZenToastAnimations;
- params.type = WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL;
- params.setTitle(getClass().getSimpleName());
- params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
- | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
- params.gravity = Gravity.CENTER;
- params.packageName = mContext.getPackageName();
- mZenToast = LayoutInflater.from(mContext).inflate(R.layout.zen_toast, null);
- final TextView message = (TextView) mZenToast.findViewById(android.R.id.message);
- message.setText(text);
- final ImageView icon = (ImageView) mZenToast.findViewById(android.R.id.icon);
- icon.setImageResource(iconRes);
- mZenToast.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
- @Override
- public void onViewDetachedFromWindow(View v) {
- // noop
- }
-
- @Override
- public void onViewAttachedToWindow(View v) {
- mZenToast.announceForAccessibility(message.getText());
- }
- });
- mWindowManager.addView(mZenToast, params);
- final int animDuration = res.getInteger(R.integer.zen_toast_animation_duration);
- final int visibleDuration = res.getInteger(R.integer.zen_toast_visible_duration);
- mHandler.sendEmptyMessageDelayed(MSG_HIDE, animDuration + visibleDuration);
- }
-
- private void handleHide() {
- if (mZenToast != null) {
- mWindowManager.removeView(mZenToast);
- mZenToast = null;
- }
- }
-
- private final Handler mHandler = new Handler() {
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_SHOW:
- handleShow(msg.arg1, null);
- break;
- case MSG_HIDE:
- handleHide();
- break;
- }
- }
- };
-
- private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (ACTION_SHOW.equals(intent.getAction())) {
- final int zen = intent.getIntExtra(EXTRA_ZEN, ZEN_MODE_IMPORTANT_INTERRUPTIONS);
- final String text = intent.getStringExtra(EXTRA_TEXT);
- handleShow(zen, text);
- } else if (ACTION_HIDE.equals(intent.getAction())) {
- handleHide();
- }
- }
- };
-}